point_move wrote a bare reference, so on another sheet Shift+arrow after a header
drag turned 'Other sheet'!B:D into B:E, which reads as the formula's own sheet.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After a drag over C:E, point_cell held C and no anchor was set, so Shift+Right
moved the wrong end and gave D:E. The drag now records the pointer's end as the
moving one and the opposite corner as the anchor, so it gives C:F, and rows work
the same way.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses review comments 4455115540 and 4455115557. The parser stores
`'Sheet'!Name` as `Sheet!Name` with the sheet already unquoted:
- Evaluator::name split it at the first `!`, so a series named
`'a!b'!Title` showed #NAME?.
- chart_name unquoted it again, trimming spaces, so a chart reference
to `' Sales '!Data` was saved as `Sales!Data`.
Both now split at the last `!` and look the sheet up as is.
Tests: chart tests::a_series_named_by_a_name_on_a_sheet_with_a_bang_resolves,
xlsx tests::chart_book_names::chart_names_keep_spaces_around_a_quoted_sheet_name.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A press on a header while a formula waits for a reference inserted C:C, but the
drag that followed did nothing, so dragging from column C to E left C:C. The drag
now follows the pointer, as it does over cells, in either direction and for rows
too, keeping the sheet qualifier, the earlier arguments of a Ctrl press and the
far end of a Shift press.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
table_columns restarted its suffix search at 2 for every duplicate
header, lowercasing and hashing every candidate already in use, so a
table of repeated headers was exported in quadratic time. The next
suffix to try is now remembered per lowercased base.
Addresses CodeRabbit review on #3250:
- "Clear parked keytip requests when their window becomes hidden": a
window hidden from another window (View › Hide while it isn't in
front) kept the request it had parked, and showing it again opened
the control. follow_active_window now drops the waiting request of
every parked window that is hidden.
- "Correct the no-badge description in the parity table": only Alt+I,
D, V and T (no ribbon tab) paint no badges.
Test: legacy_keytips::a_window_hidden_from_another_window_drops_its_parked_request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3231, "Reparse consumed separators when a
field is not quoted": parse_tsv read "a<TAB>b"x as one quoted field,
then fell back to its raw text without the separator it had consumed,
so the cells "a, b"x and c pasted back as two. The new copy (which no
longer quotes a cell holding only a quote) makes that text. A field
whose closing quote doesn't end it, or that never closes, is now read
again as unquoted text, so its tabs and line breaks separate cells.
The text read again is bounded by the input's length, so hostile input
stays linear.
Test: a_field_that_is_not_really_quoted_keeps_its_separators.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3250, "Clear deferred key-tip requests
when parking a hidden window": a hidden window isn't drawn, so a request
it parked would fire whenever it was shown again (the Symbol gallery
opening long after the keys). Parking a hidden window now drops it; the
rest of its interface state is kept.
Test: legacy_keytips::a_hidden_window_drops_its_waiting_key_tip_request.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3229, "Use the same translucency
threshold as committed ink": the preview judged the pen by its width in
points, the committed stroke by its width on screen, so at 50% a 12 pt
highlighter turned opaque once committed and at 200% a 6 pt pen turned
translucent. Both now use chartview::ink_color, which goes by the
stroke's width in points, so the two agree and neither changes with
the zoom.
Test: ink_render::ink_translucency_follows_the_pen_not_the_zoom.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3250, "Keep deferred requests with their
window": the request waiting for the next frame was shared across
windows, so the root window, drawn first, could take a second window's
Alt+I, S. It is now parked with each window's interface state and moved
into the one-frame request when that window's ui begins.
Test: legacy_keytips::a_waiting_key_tip_request_stays_with_its_window.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3250, "Expand the ribbon for a same-tab
control request": with the Insert tab chosen but the ribbon collapsed,
Alt+I, S went to the same-tab branch and the Symbol gallery was never
drawn. A collapsed ribbon is now expanded like a tab switch, and the
request waits for the frame that draws the control.
Test: legacy_keytips::alt_i_s_expands_a_collapsed_ribbon_on_the_insert_tab.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3250, "Open a symbol picker instead of
re-parsing the cell": the legacy key ran insert.symbol with no symbol,
which set the active cell to its own text again (re-reading it: text
could turn into a number, a Python cell lost its code). It now routes to
the ribbon's Symbol control: a control key tip whose control is on
another tab shows that tab first and hands the request to the next
frame, when the control is drawn, and the Symbol button opens its
gallery on its key tip. Nothing is inserted until a symbol is picked.
Test: legacy_keytips::alt_i_s_opens_the_symbol_gallery_and_leaves_the_cell_alone
(keytips regenerated with cargo xtask keytips).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cmd/Ctrl+V dropped egui's PasteImage event, and the ribbon/menu Paste only read
text, so screenshots and images copied in a browser reported "Nothing to paste".
Both now encode the pixels as PNG (bounded to 16384 px per side, 64 Mpx) and run
insert.picture as a floating picture anchored at the active cell, as Excel does.
Text on the clipboard still wins. New host service: Services::paste_image (arboard).
Fixes#699, #3018, #3182.
Adds 43 legacy sequences to keytips.json (Insert, Edit, Format, Data,
View, Tools menus and File > Page Setup), each running the command or
dialog the modern key tip already uses. Alt+I, D, V and T are implicit
levels with no ribbon tab; badges are not painted under them, as in
Excel. The generator checks Options (a Backstage page) by its page.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3238, "Limit the remove_personal_info
doc comment to XLSX saves": only the XLSX writer leaves out the author
and company; the workbook's own serialization still holds them. The
comment now says so.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three places still split a `Sheet!Name` reference and stripped the
sheet's quotes by hand: the chart resolver (is a series name a defined
name?), the XLSX chart writer (the name's cache) and the macro editor's
example references. The first two left a doubled quote in the name, so
a series named `'Bob''s'!Title` (a name on the sheet "Bob's") wasn't
found and showed that text instead of the name's value; they also split
at the first `!`, so a quoted sheet holding one broke. All three now use
core's split_sheet, the one reading formulas use.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up: a column-handle drop on the last frozen column while
scrolled, which fails if freeze_boundary measures with x(c + 1), and a check
that frozen A1's text lies inside its clip, not only that the clip is A1.
Paste Special only knew the letters F, V, T, C and E. Excel gives every
option one: A (All), N (Validation), X (All except borders), W (Column
widths), R (Formulas and number formats), U (Values and number formats);
O, D, S, M, I for the operations; B for Skip blanks; and L for the
Paste Link button, which pastes at once. One table now maps each letter
to what it does; Enter still pastes with the chosen options.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Excel's per-workbook privacy setting (Trust Center › Privacy Options ›
Remove personal information from file properties on save, set by the
Document Inspector) is saved as <workbookPr filterPrivacy="1"/>, and
while it is on Excel leaves the author and company empty on save.
GridCraft dropped the attribute, so a cleaned workbook came back with
the setting off, and it wrote the author and company back.
Workbook.remove_personal_info now keeps the setting (read and written
as filterPrivacy), and while it is on the writer leaves dc:creator and
Company out. Other properties are written as before. Note authors and
lastModifiedBy are the rest of the issue (the latter overlaps #881).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The grid took a column's right edge to be where the next column starts,
`x(col + 1)`. At the freeze line the next column is in the scrolled pane
and moves with the scroll, so everything measured that way inside the
frozen columns shrank as the sheet scrolled right:
- text overflowing from a frozen cell was clipped at the scrolled
column's edge, until only a character or two showed;
- the frozen columns' gridlines stopped short, with a stray vertical
line drawn inside them;
- a merged range's borders under the last frozen column ended early;
- the freeze handle measured the last frozen column wrongly when
dropped while scrolled.
Geo gains `x_end`/`y_end` (a column's or row's own far edge) and
`same_col_pane`, and every such measurement uses them. Text overflows
only into empty cells of its own pane. In Page Layout view the
gridlines now stop at each page's last column instead of running into
the margin.
Fixes#2544
Addresses CodeRabbit review on #3226, "Skip hidden corner destinations"
and "Keep merged destinations inside the selected area": with column D
hidden, Ctrl+. on B2:D3 made D2 active; with B1:C1 merged and column C
selected, it made B1 active, outside the selection. A corner in a hidden
row or column, or one whose merged cell starts outside the selected
area, is now passed over like a locked corner on a protected sheet.
Test: next_corner_skips_hidden_corners_and_stays_inside_the_selected_area.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3226:
- "Check the next corner against protected-sheet selection rules": on a
sheet protected without Select Locked Cells, Ctrl+. could make a locked
corner active. It now goes on to the next corner it may select.
- "Keep the active cell at a merged cell's anchor": a corner inside a
merged cell made that cell active instead of the merge's top-left
cell. The corner now lands on the top-left cell, and corners in the
same merged cell are one stop.
Selection::next_corner_by takes where each corner leads (or None to skip
it); next_corner keeps its plain behaviour.
Tests: next_corner_skips_corners_the_sheet_protection_does_not_let_you_select,
next_corner_stops_once_on_a_merged_corner_at_its_top_left_cell.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A workbook saved by Excel in R1C1 reference style says so in
<calcPr refMode="R1C1"/>. GridCraft didn't read the attribute, so
opening and saving such a file switched it back to A1 style for the
next person who opened it in Excel. The calculation settings now keep
it (CalcSettings.r1c1, serde default false) and the writer puts it back;
A1 stays the default and isn't written. GridCraft itself still shows
and takes A1 references; the R1C1 display and entry are the rest of
the issue.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Copy wrapped any cell that held a double quote in quotes (and doubled
its quotes), so copying `say "hi"` into another app gave
`"say ""hi"""`. Excel quotes only text with a tab or line break; a cell
with just quotes goes on the clipboard as it is.
Pasting text read a field that starts with a quote as quoted even when
its closing quote doesn't end the field, so `"hi" there` (which Excel
copies as it is) pasted as `hi there`. A field is now quoted only when
its closing quote is followed by a tab, a line end or the end of the
text; otherwise it is the text as written, as Excel pastes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
While dragging with a pen, the stroke under the pointer was always
painted blue at 2 pt, whatever pen or highlighter was chosen, so the
preview and the committed ink disagreed (a highlighter previewed as a
thin blue line). The preview now uses the colour and width draw.stroke
stores (pen blue and 2 pt by default, 0.5-40 pt, whole points) and the
translucency Ink shapes are painted with above 8 pt.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The page margins are kept as f32 and were widened to f64 when written,
so a 0.3-inch margin was saved as left="0.30000001192092896" (and 1.1
as 1.100000023841858). Each margin now goes through its shortest
decimal form first, so the file says 0.3, as Excel writes it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Excel's Next Corner of Selection (Ctrl+., Control+. on a Mac) moves the
active cell clockwise to the next corner of the selected area (top-left,
top-right, bottom-right, bottom-left) and keeps the area selected; the
new active cell is also the anchor, so Shift+arrows then move the
opposite corner. GridCraft had no such command.
New command selection.nextCorner (Selection::next_corner) bound to
Ctrl+. on every platform. A one-row or one-column area goes end to end;
a single cell stays where it is.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A workbook with checkboxes saved by GridCraft made Excel repair it
("Repaired records: Format from /xl/styles.xml (Styles)") and drop every
checkbox. The feature property bag part GridCraft writes for checkboxes
left out extRef="XFComplementsMapperExtRef" on the XFComplements bag,
the bag the cell formats' xfComplement indexes into. With it, Excel
opens the file without repairing and shows the checkboxes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3221, "Check the output size before
with_subtotals builds subtotal lines": the size limit was checked only
after every subtotal line existed. A row with 4000 key columns at total
depth 4000 nested with_subtotals 4000 levels deep and overflowed the
stack (the process aborted) before the late check could refuse it.
with_subtotals now works without recursion: each line gets an id for its
key prefix at every level, a stable sort by those ids keeps each group
together in first-appearance order, and one walk adds the subtotals.
The output size (with the header and total lines when present) is
checked while the groups are counted, before any subtotal line is built.
Test: groupby_subtotals_past_the_size_limit_are_refused_before_they_are_built.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3216, "Keep the selection on the sheet it
describes": moving Two!B1:B2 with Sheet1 active selected A1:A2 on
Sheet1, where nothing moved. The selection belongs to the active sheet,
so range.move now keeps it when the moved cells are on another sheet; a
move on the active sheet still selects the moved cells.
Test: drag_and_drop_on_another_sheet_stays_on_that_sheet (now also
checks the selection).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3219, "Recognize the legacy Count
Numbers caption when the function changes": workbooks saved before
Count Numbers was captioned "Count of" carry "Count Numbers of Val"
(optionally with a number suffix). pivot.valueSettings now treats that
as an automatic caption of a Count Numbers field, so changing the
function renames it; a custom name stays.
Test: legacy_count_numbers_caption_follows_the_function.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Addresses CodeRabbit review on #3216:
- "Pass the resolved sheet to the paste step": a sheet-qualified move
(`Two!B1:B2` with Sheet1 active) was checked on Two but pasted on the
active sheet, replacing its data unasked. range.move now calls
paste_special with the range's sheet.
- "Check spilled values in the drop destination": cells showing a dynamic
array's spilled values hold no cell of their own, so the drop replaced
them without the prompt. They now count as data, as in Excel
(ISBLANK of a spill cell is FALSE, COUNTA counts it).
Tests: drag_and_drop_on_another_sheet_stays_on_that_sheet,
drag_and_drop_asks_before_replacing_spilled_values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>