Commit Graph
2668 Commits
Author SHA1 Message Date
Brandon Thomas a6b7cf5c2a Merge PR #3243 2026-11-03 09:55:37 -05:00
Brandon Thomas 85f09f05d6 Merge PR #3240 2026-11-03 09:55:36 -05:00
Brandon Thomas cb4b7af1e3 Merge PR #3229 2026-11-03 09:55:36 -05:00
Brandon Thomas 0670337908 Merge PR #3231 2026-11-03 09:55:36 -05:00
Brandon Thomas 2e27cbbf05 Merge PR #3226 2026-11-03 09:55:36 -05:00
Brandon Thomas 0a12da6e52 Merge PR #3221 2026-11-03 09:55:36 -05:00
Brandon Thomas 5ff51dd142 Merge PR #3219 2026-11-03 09:55:36 -05:00
Brandon Thomas affae49e7c Merge PR #3216 2026-11-03 09:55:36 -05:00
Brandon Thomas 89d658406e Merge PR #3238 2026-11-03 09:55:36 -05:00
Brandon Thomas a10d220cea Merge PR #3234 2026-11-03 09:55:35 -05:00
Brandon Thomas b148f06e9c Merge PR #3228 2026-11-03 09:55:35 -05:00
Brandon Thomas b5d888d3cc Merge PR #3225 2026-11-03 09:55:35 -05:00
Brandon Thomas 76145982c5 Merge PR #3217 2026-11-03 09:55:35 -05:00
Hector VillarrealandClaude Opus 5.5 2caf435a38 #2012: split a parsed sheet-qualified name at the last !, sheet as is
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>
2026-11-03 07:21:22 -06:00
Dylan M. Taylor 92cdc2a975 xlsx: rename repeated table headers without retrying handed-out suffixes (#3213)
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.
2026-11-03 06:45:15 -05:00
Hector VillarrealandClaude Opus 5.5 533c0dadaf #2225: a field that only looks quoted keeps the tabs and line breaks in it
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>
2026-11-03 04:19:49 -06:00
Hector VillarrealandClaude Opus 5.5 2adfd5571f #1786: ink is translucent by its width in points, while drawn and once committed
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>
2026-11-03 03:58:23 -06:00
Hector VillarrealandClaude Opus 5.5 a774d18350 #3089: the personal-information flag's doc names the XLSX writer
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>
2026-11-03 01:53:43 -06:00
Hector VillarrealandClaude Opus 5.5 e89ba799e9 #2012: chart names and macro examples unquote sheet names like formulas do
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>
2026-11-03 01:49:01 -06:00
Hector VillarrealandClaude Opus 5.5 c35ab0f4f4 #1482: every Paste Special option has its Excel access key
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>
2026-11-03 01:41:55 -06:00
Hector VillarrealandClaude Opus 5.5 f0557ec443 #3089: keep "remove personal information on save" and honour it
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>
2026-11-03 01:25:29 -06:00
Hector VillarrealandClaude Opus 5.5 914a76a9d8 #1442: Ctrl+. skips hidden corners and stays inside the selected area
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>
2026-11-03 01:21:40 -06:00
Hector VillarrealandClaude Opus 5.5 5acb4af789 #1442: Ctrl+. skips corners the sheet can't select and stops once on a merged corner
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>
2026-11-03 01:08:01 -06:00
Hector VillarrealandClaude Opus 5.5 c121fb5901 #2809: keep a workbook's R1C1 reference style through open and save
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>
2026-11-03 01:07:29 -06:00
Hector VillarrealandClaude Opus 5.5 fe965e7581 #2225: copy quotes only cells with a tab or line break
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>
2026-11-03 01:02:40 -06:00
Hector VillarrealandClaude Opus 5.5 2fe3bfc077 #1786: the live pen stroke is drawn as the ink it commits
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>
2026-11-03 00:59:09 -06:00
Hector VillarrealandClaude Opus 5.5 49f2ef515b #1609: write page margins as their decimals
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>
2026-11-03 00:52:58 -06:00
Hector VillarrealandClaude Opus 5.5 8b2550c9cf #1442: Ctrl+. moves to the next corner of the selection
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>
2026-11-03 00:44:15 -06:00
Hector VillarrealandClaude Opus 5.5 d5c892f238 #2630: mark the checkbox mapping bag with its extRef
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>
2026-11-03 00:37:00 -06:00
Hector VillarrealandClaude Opus 5.5 ef8ac5e6db #1456: GROUPBY refuses oversized subtotals before building them, without recursion
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>
2026-11-03 00:36:42 -06:00
Hector VillarrealandClaude Opus 5.5 ba1c62469d #3136: a move on another sheet keeps the active sheet's selection
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>
2026-11-03 00:31:59 -06:00
Hector VillarrealandClaude Opus 5.5 044599c6ef #1448: a legacy "Count Numbers of" caption follows a new function
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>
2026-11-03 00:27:25 -06:00
Hector VillarrealandClaude Opus 5.5 6874e0b689 #3136: range.move pastes on the range's sheet and asks before replacing spilled values
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>
2026-11-03 00:13:27 -06:00
Hector VillarrealandClaude Opus 5.5 1fd10711a5 #1456: GROUPBY total depth 2 or more adds subtotals
GROUPBY's total_depth only ever added one Total line. In Excel a depth
of N adds a subtotal line for each group of the first N - 1 key columns
(after the group, or before it when the depth is negative) and labels
the last line "Grand Total"; a depth greater than the number of key
columns, or above 1 with field_relationship 1 (a flat table), is
#VALUE!. The subtotals aggregate the group's source rows, so AVERAGE and
COUNT subtotals are right too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:10:34 -06:00
Hector VillarrealandClaude Opus 5.5 7bea3d1d6a #1448: PivotTable ranks are dense; Count Numbers is captioned "Count of"
Show Values As › Rank Largest to Smallest counted every larger sibling,
so ties skipped ranks: 5, 4, 3, 3, 4 ranked 1, 2, 4, 4, 2 where Excel
gives 1, 2, 3, 3, 2. It now counts the distinct larger values.

A new Count Numbers value field was captioned "Count Numbers of Units";
Excel captions it "Count of Units", as GridCraft's XLSX reader already
names a value field saved without a name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 00:06:14 -06:00
Hector VillarrealandClaude Opus 5.5 283789c1f4 #2339: write the XLDAPR extension URI in lower case next to rich values
A workbook with an in-cell picture and a dynamic-array formula wrote
the XLDAPR futureMetadata extension URI in upper case
({BDBB8CDC-…}), and Excel refuses such a workbook. The metadata part
written for dynamic arrays alone, which Excel opens, spells the same
URI in lower case; the rich-value writer now does too.

Together with #1533 (content types and the XLRICHVALUE URI) this lets
Excel open a workbook with an in-cell picture and a spilled formula.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:54:23 -06:00
Hector VillarrealandClaude Opus 5.5 5361e2c08a #3136: ask before a drag and drop replaces data
Dragging the selection's border onto cells that hold data replaced them
without asking, so dragging column B onto column A silently destroyed A.
Excel asks "There's already data here. Do you want to replace it?" first
(its "Alert before overwriting cells" option, on by default).

The drop now runs a new `range.move {range?, to, overwrite?}` command:
a cut and paste that returns a confirm question when the destination
holds data other than the moved cells; the grid shows it with OK and
Cancel, and OK runs it again with `overwrite: true`. A refused move
leaves the sheet and the clipboard as they were.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 23:43:02 -06:00
Dylan M. Taylor 178f3eb745 Protection: authorize the sheet a column move changes, and refuse edits that unlock locked cells (#3196)
- sheet.moveColumns resolved its target sheet from `sheet`, or a
  `Sheet!` range or cell, but its two protection checks were passed
  only the `sheet` parameter, so a qualified range moved columns of
  a protected sheet while the checks authorized the active one. Both
  checks now get the resolved sheet index.
- check_protection compared only cell content, so a sort or duplicate
  removal moving an unlocked cell with equal content onto a locked one
  left the content as it was but made the cell editable. The commit
  check now also refuses a change that leaves a previously locked cell
  unlocked (effective lock, row and column formats included).
2026-11-02 23:45:20 -05:00
Dylan M. Taylor e8ccb5eb15 check: find volatile defined names with a worklist instead of repeated passes (#3187) 2026-11-02 23:33:31 -05:00
Brandon Thomas e1604d1791 Merge PR #3132 2026-11-02 22:36:51 -05:00
Brandon Thomas 74d9641e25 Merge PR #3173 2026-11-02 22:33:26 -05:00
ljkx 342f4b401d Check list validation against the list before the entry
A source that takes in the cell being checked (=$A1:$A2 on A2:A3 is A2:A3 at A3) listed the value being typed, so any new value passed. The list is now read from the workbook as it was before the entry, as in Excel.
2026-11-03 04:26:30 +01:00
Brandon Thomas 95d9ed1234 Merge PR #3161 2026-11-02 22:21:00 -05:00
Brandon Thomas 3d3ff84087 Merge PR #3179 2026-11-02 22:21:00 -05:00
ljkx 179664610c Read relative list validation sources from the cell being checked
A list source such as =$B1:$B2 is written for the rule's top-left cell.
list_items evaluated it there for every cell, so the dropdown and the
check showed and accepted the top-left cell's items everywhere. It now
takes the cell and shifts the source to it, as Excel does.
2026-11-03 03:23:48 +01:00
Brandon ThomasandClaude Opus 5.5 85e51e009c Release 0.16.0
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.16.0
2026-11-02 19:25:34 -05:00
ljkx fb6dc688bc Rewrite rule formulas for the new top-left cell when cells are removed from a rule
Clearing validation from cells (or setting a new rule, or pasting a
validation over them) and Clear Rules on selected cells shrink a rule's
areas. When that removes the rule's top-left cell, the relative formulas
were left written for the old cell, so the cells left checked other
cells: clearing A1 off a custom `A1>10` on A1:A9 made A2 check A1.

Rewrite the relative references of validation formulas, and of
conditional format expressions and comparison values, for the new
top-left cell. Absolute references and literal lists stay as they are.
2026-11-03 00:18:50 +01:00
Aimar Mendez e5f265bb74 Ribbon test: pin macOS for the AutoSum tooltip, so it passes on a Mac
Since #2729 the ribbon picks a tooltip's shortcut by the OS egui reports
(`ctx.os()`), which egui_kittest pins to Nix unless told. The test still
chose the expected text with cfg!(target_os = "macos"), so on a Mac it
looked for "AutoSum (⌘⇧T)" while the harness drew "AutoSum (Alt+=)",
and `cargo xtask ci` failed there while the Linux gate stayed green.

The harness now pins macOS and the test expects the ⌘⇧T tooltip on every
host; widgets::tests already covers the Windows/Linux "Alt+=" text.

Verified on macOS 27 (arm64): `cargo xtask ci` passes all 10 steps (with
CRAFT_FONTS_DIR at the gate's craft-fonts commit); on main (7bb397a3)
it fails at test with "tooltip with the shortcut".
2026-11-02 22:19:21 +00:00
PranavandFredApps 7bb397a3c1 UI: RefEdit reference boxes in dialogs (Goal Seek, Solver, Data Analysis) (#494)
* UI: RefEdit reference boxes in dialogs; Goal Seek picks its cells on the grid

A reference box (RefEdit) is a text field that takes a range by selection
on the grid. While a box is the one being picked into (it was clicked into
or collapsed), clicking or dragging on the sheet writes the absolute
reference into it, with the sheet name when the selection is on another
sheet; typing still edits it, and the box takes the keyboard back after a
pick. Focusing another text field of the dialog ends picking.

The collapse button beside each box shrinks the dialog to that box, at the
dialog's top-left corner, so the sheet is free for selecting. Escape,
Enter or the expand button restore the dialog. The sheet and selection
from before picking are restored before the dialog runs its command
(references without a sheet name mean the sheet the dialog was opened
on) and when it closes. The button is focusable, works with Space and
Enter, and is named for screen readers; the icons are drawn in code.

Goal Seek's Set cell and By changing cell are reference boxes. The
snapshot example's --refedit key=range collapses the open dialog to a box
and selects a range, to render the collapsed state.

* UI: Solver and Data Analysis take their ranges with RefEdit boxes

Form dialogs gain a Ref field, drawn as a reference box with its collapse
button. Data Analysis uses it for the input, variable, X and Y and bin
ranges, and for Output Range, where picking or typing a reference also
chooses that output option. Solver Parameters uses reference boxes for
the objective, the variable cells and both sides of a new constraint.

* UI: Data Analysis keeps New Worksheet Ply when chosen after picking an Output Range

The Output Range box chose its option on every frame while it was the box
being picked into, so clicking New Worksheet Ply after using the box
reverted to Output Range on the next frame. The box now chooses Output
Range only when it is clicked into or its text changes (typed or picked),
and choosing another output option ends picking into it, so a later
selection on the grid doesn't fill it either.

* UI: RefEdit boxes remember the dialog's sheet by name

The sheet a dialog was opened on was kept by index, so a sheet inserted
or moved while picking (the ribbon stays usable) made a range on the new
sheet lose its sheet name, and closing the dialog went back to whichever
sheet now had that index. The sheet is now kept by name, as New
Formatting Rule's formula box does.

* UI: switching sheets while picking into a RefEdit box doesn't fill it

A reference on another sheet carries the sheet name, so after a click on
another sheet tab the selection never matched the one last seen, and the
switch alone wrote that sheet's remembered selection into the box,
replacing a typed reference. The box now notes which sheet its last seen
selection was on: showing another sheet only refreshes it, and a
selection made on that sheet still fills the box.

* UI: a RefEdit box for one reference takes only the area being selected

A selection of several areas (Ctrl+click) was written into every box as
a comma-joined union, which Goal Seek, Solver's objective and
constraints, and Data Analysis then refused as an invalid reference.
Solver's variable cells still take every area; the other boxes take the
area being selected.

* UI: RefEdit boxes follow the dialog's sheet when it is renamed

The dialog's own sheet was kept by name only, so renaming it while a
reference box picked made references on it carry the new name as if it
were another sheet, and running or closing the dialog could not put the
selection back. The sheet's index and the sheet count are now kept with
the name: when the name disappears while the count stays the same, the
sheet at that index was renamed and its new name is taken. Inserting,
moving or deleting other sheets still follows the name.

* UI: a deleted dialog sheet is never taken for another sheet

The dialog's own sheet was taken as renamed whenever its name was gone
and the sheet count matched the last frame's, so deleting it was read as
a rename a frame later (or after a sheet was inserted), and the
surviving sheet at that index became the dialog's sheet: unqualified
references ran against it and Cancel put the old selection on it.

The sheet names of the last frame are now kept in order. A rename is
taken only when the list keeps its length and only the home sheet's entry
changed; any other loss of the name marks the sheet as deleted for good.
Once it is deleted, picks carry their sheet's name, closing leaves the
selection where it is, and OK is refused with a message, since
references without a sheet name would otherwise run against whichever
sheet is shown.

* UI: RefEdit dialogs keep their sheet until they close

OK put the saved selection back and dropped the dialog's sheet with it.
When the command then failed, the dialog stayed open without its sheet:
a later deletion of that sheet went unnoticed and a second OK ran
references without a sheet name against the sheet shown, as it also did
when the person had only moved to another sheet after the failed OK.

The sheet is now kept, and followed through renames and deletion, until
the dialog closes; only the saved selection is put back once. A later
OK or Cancel shows the dialog's sheet again (or is refused once it is
deleted), and picking again on that sheet saves its selection anew.

* UI: Solver's Model Range is a RefEdit box

* UI: a collapsed RefEdit box keeps picking when something else is clicked

* UI: a dialog opened over a picking RefEdit dialog puts the selection back

* UI: arrow keys select on the grid while a dialog is collapsed to a RefEdit box

* UI: F4 cycles the anchors of the reference in a RefEdit box

* UI: tests for Enter while collapsed and OK after a cross-sheet pick

* Docs: RefEdit boxes' keyboard route, Model Range and click-elsewhere behaviour

* UI: a confirmed RefEdit dialog stays on the sheet its command showed

* UI: scripted dialog cancel and confirm end a RefEdit pick like the buttons

* UI: RefEdit boxes run their selection and sheet commands through the app

* Docs: RefEdit dialogs after OK, scripted cancel and confirm

* UI: a RefEdit dialog's OK stops when the picked sheet's draft cannot be saved

* Docs: ui.dialog.confirm applies to command-backed dialogs; a draft that cannot be saved stops OK

* UI: Cancel of a RefEdit dialog is never a confirmation, and says when it cannot go home

* Docs: cancelling a RefEdit dialog when its sheet cannot be shown again

* UI: a RefEdit dialog follows its sheet's rename only through the engine's recorded steps

* UI: F4 in a RefEdit box splits areas only at commas outside quoted sheet names

* Docs: how a RefEdit dialog follows its sheet's rename

* UI: a RefEdit dialog's sheet is told from a new sheet given its name

The dialog followed its sheet by name and read the recorded steps only once the name was gone, so a sheet deleted and recreated under its own name between two frames was taken for the dialog's sheet, and OK resolved unqualified references against the replacement. The home sheet is now followed through every recorded step since the dialog last looked: a sheet with its name is the same sheet only while its file id is unchanged (a sheet inserted in the session has none), a rename keeps the file id and the cells, and a deleted sheet is remembered as it was, so only an undo that brings that very sheet back revives it. When the steps are not accounted for, a sheet with the name is taken for it, as before. A sheet never saved to a file and replaced inside one undo group is the one case nothing in the model tells apart; the code comment says so.

* Docs: how a RefEdit dialog tells its sheet from a new sheet given its name

* UI: a RefEdit dialog follows a sheet that came from a file by its file id

A sheet read from a file keeps the file's sheetId, which no step changes and which a sheet inserted in the session lacks, so the dialog's sheet is found by it wherever a recorded step puts it and whatever the step renames it to: a group that renames the sheet and inserts a new one under its old name follows the rename instead of refusing OK. When the step also copied the sheet (a copy keeps the id), the one keeping the name is taken. A sheet never saved to a file is still followed by name, with the rename rule as before.

* Docs: a RefEdit dialog follows a sheet that came from a file by its file id

* UI: a RefEdit box the dialog stops showing is no longer picked into

A reference box compares the grid selection with the one it last saw only while it is drawn, so a box hidden while it was the one being picked into (Solver's Options view, the constraint value once the operator is int or bin) took a selection made meanwhile when it came back. After the dialog's window is drawn, a target box that was not drawn in the frame ends the pick, whatever hid it; Model Range no longer needs its own rule when the Load/Save area is hidden.

* UI: the Name Box keeps its keys while a dialog is collapsed to a RefEdit box

The collapsed picker took Escape, Enter and the arrow keys out of the frame's input whoever had the keyboard. The Name Box, drawn before the dialogs, had already read an arrow when the picker removed the same event and moved the grid selection, and the box then took that selection and the keyboard; its Escape expanded the dialog instead of only leaving the box. The picker now acts on those keys only when no other text field owns the keyboard, judged by the focus at the end of the frame before as well as now, since egui drops the focus for Escape before anything is drawn. The other field's Escape and Enter are still used up, so the dialog neither closes nor confirms on them.

* Docs: a sentence break in the ui.dialog row of the control protocol

* UI: a RefEdit dialog keeps its sheet by the sheet's identity, not by file id or history

The dialog's sheet was followed by name through the recorded undo steps, with the file's sheet id telling a saved sheet from a new one; a copy made with sheet.move keeps that id, so once the original was deleted the copy, as the only sheet of the id, was taken for the dialog's sheet and OK ran against it. The model now gives every sheet an identity while the app runs (SheetUid: fresh for every sheet added, inserted, copied or read, kept through renames, moves, undo and redo), so Home keeps the sheet's uid and finds the sheet by it each time it is needed. The history walk, the rename rule and the file-id and Arc comparisons are gone, with the undetectable single-step case they left: a sheet given the name, a replacement inside one undo group and a copy are all other sheets, and an undo that brings the original back makes it the dialog's again. Nothing of the unaccounted-steps fallback is kept, since the identity always answers. Regression test: copy the dialog's sheet, delete the original, OK is refused; undo, OK runs on the original. The identity tests no longer need a workbook that came through a file.

* UI: Solver's Reset All, Load, Save and Replace act on the dialog's sheet, as Solve does

Only Solve went through the dialog's home-sheet checks before running (dialogs::show): Reset All, Load Model, Save Model and Replace ran their commands straight from the form, on whichever sheet was active. After a reference box picked on another sheet and the dialog expanded, that sheet was still shown, so Reset All removed its model instead of the dialog's sheet's, and Load and Save read and wrote the model range on it. The four buttons now run only after the same checks as Solve: the dialog's sheet must still exist (else the command is refused with the deleted-sheet message) and is shown again with the selection from before picking put back (a failure to show it, a text-box draft that could not be saved, is shown as the dialog's error and the command does not run). Goal Seek has no other buttons, and Data Analysis's Replace goes through the confirm path that Solve uses, so neither needed a change. Regression tests: after a cross-sheet pick, Reset All clears the dialog's sheet's model and leaves the other sheet's; Save and Replace write the dialog's sheet; Load reads it.

* UI: Protect Sheet opened while a reference box picks on another sheet acts on the dialog's sheet

* Docs: a scripted Goal Seek after a cross-sheet pick cancels the dialog before data.goalSeek

* UI: the Protect Sheet labels follow the dialog's sheet while a reference box picks on another

* Docs: a scripted Goal Seek checks that the dialog's sheet still exists after cancel

* Control: ui.dialog.cancel replies sheetDeleted when the dialog's own sheet is gone, even if its name was reused

* M2: allow shared-runner variance in the pathological math timing gate

* M2: refuse sheet protection actions after a reference dialog home is deleted

---------

Co-authored-by: FredApps <FredApps@users.noreply.github.com>
2026-11-02 21:23:18 +01:00
FredApps ef927e2e71 Merge pull request #1576 from danielmigueltejedor/fix/gnome-window-decorations
Restore GNOME Wayland window controls without bundled fonts
2026-11-02 19:33:06 +01:00