mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-05 01:58:58 +00:00
Merge PR #3250
This commit is contained in:
commit
56cc52eb39
14 files changed
+848
-8
No files matched your search
@@ -107,7 +107,9 @@ impl SheetApp {
|
||||
pub fn logic(&mut self, ctx: &egui::Context) {
|
||||
self.ensure_text_width(ctx);
|
||||
// A keytip control request lives one frame: the ribbon consumes it during `ui`, and
|
||||
// anything that never reached a control (collapsed ribbon, tab switch) is dropped.
|
||||
// anything that never reached a control (collapsed ribbon, tab switch) is dropped. One
|
||||
// made with a switch to the control's tab waits, with its window, for the next `ui` of
|
||||
// that window.
|
||||
self.ui.keytip_invoke = None;
|
||||
// Floating pictures' textures go with their workbook, even when no sheet is painted.
|
||||
let open: Vec<u64> = self.session.documents().iter().map(|d| d.uid).collect();
|
||||
@@ -194,6 +196,9 @@ impl SheetApp {
|
||||
let t0 = now_ms();
|
||||
// A script may have switched windows since the last frame.
|
||||
self.follow_active_window();
|
||||
// This window's key tip request that waited for this frame (its tab or ribbon was just
|
||||
// shown); any other window's request was dropped before it.
|
||||
self.ui.keytip_invoke = self.ui.keytip_invoke_next.take();
|
||||
text_box::before_ui(self, &ctx);
|
||||
// Another window came forward without going through a command (which settles drafts
|
||||
// first): the draft goes into its own window instead of floating over this one. If its
|
||||
|
||||
@@ -59,7 +59,22 @@ impl SheetApp {
|
||||
// Route to the ribbon control that owns this keytip: it opens its dropdown or
|
||||
// runs its click handler with the right params (the engine command alone would
|
||||
// be missing them).
|
||||
keytips::Action::Control(id) => self.ui.keytip_invoke = Some(id),
|
||||
//
|
||||
// A control on another tab (a legacy menu key such as Alt+I, S for the Symbol
|
||||
// gallery) shows its tab first, so the control is drawn and takes the request.
|
||||
keytips::Action::Control(id) => {
|
||||
// A collapsed ribbon draws no controls either: it is expanded the same way.
|
||||
let other_tab = gridcraft_engine::find_command(id).and_then(|c| c.menu.first()).filter(|t| **t != self.ui.ribbon_tab);
|
||||
if other_tab.is_some() || self.ui.ribbon_collapsed {
|
||||
if let Some(tab) = other_tab {
|
||||
self.run_or_alert("ui.ribbonTab", json!({"tab": tab}));
|
||||
}
|
||||
self.ui.ribbon_collapsed = false;
|
||||
self.ui.keytip_invoke_next = Some(id);
|
||||
} else {
|
||||
self.ui.keytip_invoke = Some(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,13 @@ pub fn is_group_level(seq: &str) -> bool {
|
||||
matches!(lookup(seq), Some(Entry::Continue)) || (lookup(seq).is_none() && has_deeper(seq))
|
||||
}
|
||||
|
||||
/// False under a legacy Excel 2003 menu letter that has no ribbon tab (Alt+I, D, V, T): Excel
|
||||
/// paints no badges for those, so the ribbon stays as it is while the sequence is typed.
|
||||
pub fn paints_badges(prefix: &str) -> bool {
|
||||
let Some(root) = prefix.get(..1) else { return true };
|
||||
SEQUENCES.iter().any(|(seq, entry)| matches!(entry, Entry::Tab(_)) && seq.starts_with(root))
|
||||
}
|
||||
|
||||
fn matches_target(action: &Action, target: &str) -> bool {
|
||||
match action {
|
||||
Action::Command(id) | Action::CommandWith(id, _) | Action::Pending(id) | Action::Control(id) => *id == target,
|
||||
@@ -214,6 +221,22 @@ mod tests {
|
||||
assert!(!has_deeper("X"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn legacy_menu_letters_finish_without_badges() {
|
||||
assert_eq!(lookup("IR"), Some(Entry::Finish(Action::Command("home.insertRows"))));
|
||||
assert_eq!(lookup("DS"), Some(Entry::Finish(Action::Dialog("sort"))));
|
||||
assert_eq!(lookup("EAA"), Some(Entry::Finish(Action::Command("edit.clearAll"))));
|
||||
for root in ["I", "D", "V", "T"] {
|
||||
assert!(is_group_level(root), "{root} waits for the menu letter");
|
||||
assert!(!paints_badges(root), "{root} has no tab, so no badges");
|
||||
}
|
||||
assert!(paints_badges(""), "tab badges");
|
||||
assert!(paints_badges("HO"));
|
||||
assert!(paints_badges("J"), "the Draw tab's two-letter unit");
|
||||
assert!(paints_badges("E"), "E and O stay aliases of Home");
|
||||
assert_eq!(sequences_for("home.insertRows"), vec!["HIR", "IR"], "the ribbon sequence stays first");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn group_extension_paints_only_continue_levels() {
|
||||
assert_eq!(group_extension("H", &["dialog:pasteSpecial"]), vec!["HV"]);
|
||||
|
||||
@@ -372,5 +372,48 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("FN", Entry::Finish(Action::Command("file.new"))), // Excel: New page; New page
|
||||
("FO", Entry::Finish(Action::Command("file.open"))), // Excel: Open page; fires immediately in Excel; deeper aliases dropped as unreachable
|
||||
("FP", Entry::Finish(Action::Command("file.print"))), // Excel: Print page; containment(Print)
|
||||
("Q", Entry::Finish(Action::Pending("ui.search"))), // pending; Excel: Search / Tell Me; Alt+Q search box does not exist yet (issue #49)
|
||||
("IR", Entry::Finish(Action::Command("home.insertRows"))), // Excel: Insert > Rows; Legacy Excel 2003 menu path (#941).
|
||||
("IC", Entry::Finish(Action::Command("home.insertColumns"))), // Excel: Insert > Columns; Legacy Excel 2003 menu path (#941).
|
||||
("IE", Entry::Finish(Action::Dialog("insertCells"))), // Excel: Insert > Cells; Legacy Excel 2003 menu path (#941).
|
||||
("IW", Entry::Finish(Action::Command("home.insertSheet"))), // Excel: Insert > Worksheet; Legacy Excel 2003 menu path (#941).
|
||||
("IF", Entry::Finish(Action::Dialog("insertFunction"))), // Excel: Insert > Function; Legacy Excel 2003 menu path (#941).
|
||||
("IND", Entry::Finish(Action::Dialog("defineName"))), // Excel: Insert > Name > Define; Legacy Excel 2003 menu path (#941).
|
||||
("IS", Entry::Finish(Action::Control("insert.symbol"))), // Excel: Insert > Symbol; Legacy Excel 2003 menu path (#941).
|
||||
("ED", Entry::Finish(Action::Dialog("deleteCells"))), // Excel: Edit > Delete; Legacy Excel 2003 menu path (#941).
|
||||
("EM", Entry::Finish(Action::Dialog("moveSheet"))), // Excel: Edit > Move or Copy Sheet; Legacy Excel 2003 menu path (#941).
|
||||
("EL", Entry::Finish(Action::Command("home.deleteSheet"))), // Excel: Edit > Delete Sheet; Legacy Excel 2003 menu path (#941).
|
||||
("EAA", Entry::Finish(Action::Command("edit.clearAll"))), // Excel: Edit > Clear > All; Legacy Excel 2003 menu path (#941).
|
||||
("EAC", Entry::Finish(Action::Command("edit.clearContents"))), // Excel: Edit > Clear > Contents; Legacy Excel 2003 menu path (#941).
|
||||
("EAM", Entry::Finish(Action::Command("edit.clearComments"))), // Excel: Edit > Clear > Comments; Legacy Excel 2003 menu path (#941).
|
||||
("EF", Entry::Finish(Action::Dialog("find"))), // Excel: Edit > Find; Legacy Excel 2003 menu path (#941).
|
||||
("EG", Entry::Finish(Action::Dialog("goTo"))), // Excel: Edit > Go To; Legacy Excel 2003 menu path (#941).
|
||||
("ORA", Entry::Finish(Action::Command("home.autofitRowHeight"))), // Excel: Format > Row > AutoFit; Legacy Excel 2003 menu path (#941).
|
||||
("ORE", Entry::Finish(Action::Dialog("rowHeight"))), // Excel: Format > Row > Height; Legacy Excel 2003 menu path (#941).
|
||||
("ORH", Entry::Finish(Action::Command("home.hideRows"))), // Excel: Format > Row > Hide; Legacy Excel 2003 menu path (#941).
|
||||
("ORU", Entry::Finish(Action::Command("home.unhideRows"))), // Excel: Format > Row > Unhide; Legacy Excel 2003 menu path (#941).
|
||||
("OCW", Entry::Finish(Action::Dialog("columnWidth"))), // Excel: Format > Column > Width; Legacy Excel 2003 menu path (#941).
|
||||
("OCH", Entry::Finish(Action::Command("home.hideColumns"))), // Excel: Format > Column > Hide; Legacy Excel 2003 menu path (#941).
|
||||
("OCU", Entry::Finish(Action::Command("home.unhideColumns"))), // Excel: Format > Column > Unhide; Legacy Excel 2003 menu path (#941).
|
||||
("OHR", Entry::Finish(Action::Dialog("renameSheet"))), // Excel: Format > Sheet > Rename; Legacy Excel 2003 menu path (#941).
|
||||
("OHH", Entry::Finish(Action::Command("sheet.hide"))), // Excel: Format > Sheet > Hide; Legacy Excel 2003 menu path (#941).
|
||||
("OHU", Entry::Finish(Action::Command("sheet.unhide"))), // Excel: Format > Sheet > Unhide; Legacy Excel 2003 menu path (#941).
|
||||
("OE", Entry::Finish(Action::Command("home.formatCells"))), // Excel: Format > Cells; Legacy Excel 2003 menu path (#941).
|
||||
("OD", Entry::Finish(Action::Command("home.conditionalFormat"))), // Excel: Format > Conditional Formatting; Legacy Excel 2003 menu path (#941).
|
||||
("DFF", Entry::Finish(Action::Command("data.filter"))), // Excel: Data > Filter > AutoFilter; Legacy Excel 2003 menu path (#941).
|
||||
("DS", Entry::Finish(Action::Dialog("sort"))), // Excel: Data > Sort; Legacy Excel 2003 menu path (#941).
|
||||
("DGG", Entry::Finish(Action::Command("data.group"))), // Excel: Data > Group and Outline > Group; Legacy Excel 2003 menu path (#941).
|
||||
("DGU", Entry::Finish(Action::Command("data.ungroup"))), // Excel: Data > Group and Outline > Ungroup; Legacy Excel 2003 menu path (#941).
|
||||
("DGS", Entry::Finish(Action::Command("data.showDetail"))), // Excel: Data > Group and Outline > Show Detail; Legacy Excel 2003 menu path (#941).
|
||||
("DGH", Entry::Finish(Action::Command("data.hideDetail"))), // Excel: Data > Group and Outline > Hide Detail; Legacy Excel 2003 menu path (#941).
|
||||
("DL", Entry::Finish(Action::Command("data.validation"))), // Excel: Data > Validation; Legacy Excel 2003 menu path (#941).
|
||||
("DT", Entry::Finish(Action::Command("data.dataTable"))), // Excel: Data > Table (what-if data table); Legacy Excel 2003 menu path (#941).
|
||||
("VN", Entry::Finish(Action::Command("view.normal"))), // Excel: View > Normal; Legacy Excel 2003 menu path (#941).
|
||||
("VP", Entry::Finish(Action::Command("view.pageBreakPreview"))), // Excel: View > Page Break Preview; Legacy Excel 2003 menu path (#941).
|
||||
("VZ", Entry::Finish(Action::Dialog("zoom"))), // Excel: View > Zoom; Legacy Excel 2003 menu path (#941).
|
||||
("TO", Entry::Finish(Action::Dialog("options"))), // Excel: Tools > Options; Legacy Excel 2003 menu path (#941).
|
||||
("TUA", Entry::Finish(Action::Command("formulas.removeArrows"))), // Excel: Tools > Formula Auditing > Remove All Arrows; Legacy Excel 2003 menu path (#941).
|
||||
("TUD", Entry::Finish(Action::Command("formulas.traceDependents"))), // Excel: Tools > Formula Auditing > Trace Dependents; Legacy Excel 2003 menu path (#941).
|
||||
("TUT", Entry::Finish(Action::Command("formulas.tracePrecedents"))), // Excel: Tools > Formula Auditing > Trace Precedents; Legacy Excel 2003 menu path (#941).
|
||||
("FU", Entry::Finish(Action::Dialog("pageSetup"))), // Excel: File > Page Setup; Legacy Excel 2003 menu path (#941).
|
||||
("Q", Entry::Finish(Action::Pending("ui.search"))), // pending; Excel: Search / Tell Me; Alt+Q search box does not exist yet (issue #49)
|
||||
];
|
||||
@@ -2582,6 +2582,307 @@
|
||||
"excel": "Print page",
|
||||
"note": "containment(Print)"
|
||||
},
|
||||
{
|
||||
"seq": "IR",
|
||||
"kind": "command",
|
||||
"target": "home.insertRows",
|
||||
"excel": "Insert > Rows",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "IC",
|
||||
"kind": "command",
|
||||
"target": "home.insertColumns",
|
||||
"excel": "Insert > Columns",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "IE",
|
||||
"kind": "dialog",
|
||||
"target": "insertCells",
|
||||
"excel": "Insert > Cells",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "IW",
|
||||
"kind": "command",
|
||||
"target": "home.insertSheet",
|
||||
"excel": "Insert > Worksheet",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "IF",
|
||||
"kind": "dialog",
|
||||
"target": "insertFunction",
|
||||
"excel": "Insert > Function",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "IND",
|
||||
"kind": "dialog",
|
||||
"target": "defineName",
|
||||
"excel": "Insert > Name > Define",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "IS",
|
||||
"kind": "control",
|
||||
"target": "insert.symbol",
|
||||
"excel": "Insert > Symbol",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "ED",
|
||||
"kind": "dialog",
|
||||
"target": "deleteCells",
|
||||
"excel": "Edit > Delete",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "EM",
|
||||
"kind": "dialog",
|
||||
"target": "moveSheet",
|
||||
"excel": "Edit > Move or Copy Sheet",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "EL",
|
||||
"kind": "command",
|
||||
"target": "home.deleteSheet",
|
||||
"excel": "Edit > Delete Sheet",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "EAA",
|
||||
"kind": "command",
|
||||
"target": "edit.clearAll",
|
||||
"excel": "Edit > Clear > All",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "EAC",
|
||||
"kind": "command",
|
||||
"target": "edit.clearContents",
|
||||
"excel": "Edit > Clear > Contents",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "EAM",
|
||||
"kind": "command",
|
||||
"target": "edit.clearComments",
|
||||
"excel": "Edit > Clear > Comments",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "EF",
|
||||
"kind": "dialog",
|
||||
"target": "find",
|
||||
"excel": "Edit > Find",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "EG",
|
||||
"kind": "dialog",
|
||||
"target": "goTo",
|
||||
"excel": "Edit > Go To",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "ORA",
|
||||
"kind": "command",
|
||||
"target": "home.autofitRowHeight",
|
||||
"excel": "Format > Row > AutoFit",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "ORE",
|
||||
"kind": "dialog",
|
||||
"target": "rowHeight",
|
||||
"excel": "Format > Row > Height",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "ORH",
|
||||
"kind": "command",
|
||||
"target": "home.hideRows",
|
||||
"excel": "Format > Row > Hide",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "ORU",
|
||||
"kind": "command",
|
||||
"target": "home.unhideRows",
|
||||
"excel": "Format > Row > Unhide",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "OCW",
|
||||
"kind": "dialog",
|
||||
"target": "columnWidth",
|
||||
"excel": "Format > Column > Width",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "OCH",
|
||||
"kind": "command",
|
||||
"target": "home.hideColumns",
|
||||
"excel": "Format > Column > Hide",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "OCU",
|
||||
"kind": "command",
|
||||
"target": "home.unhideColumns",
|
||||
"excel": "Format > Column > Unhide",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "OHR",
|
||||
"kind": "dialog",
|
||||
"target": "renameSheet",
|
||||
"excel": "Format > Sheet > Rename",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "OHH",
|
||||
"kind": "command",
|
||||
"target": "sheet.hide",
|
||||
"excel": "Format > Sheet > Hide",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "OHU",
|
||||
"kind": "command",
|
||||
"target": "sheet.unhide",
|
||||
"excel": "Format > Sheet > Unhide",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "OE",
|
||||
"kind": "command",
|
||||
"target": "home.formatCells",
|
||||
"excel": "Format > Cells",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "OD",
|
||||
"kind": "command",
|
||||
"target": "home.conditionalFormat",
|
||||
"excel": "Format > Conditional Formatting",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "DFF",
|
||||
"kind": "command",
|
||||
"target": "data.filter",
|
||||
"excel": "Data > Filter > AutoFilter",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "DS",
|
||||
"kind": "dialog",
|
||||
"target": "sort",
|
||||
"excel": "Data > Sort",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "DGG",
|
||||
"kind": "command",
|
||||
"target": "data.group",
|
||||
"excel": "Data > Group and Outline > Group",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "DGU",
|
||||
"kind": "command",
|
||||
"target": "data.ungroup",
|
||||
"excel": "Data > Group and Outline > Ungroup",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "DGS",
|
||||
"kind": "command",
|
||||
"target": "data.showDetail",
|
||||
"excel": "Data > Group and Outline > Show Detail",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "DGH",
|
||||
"kind": "command",
|
||||
"target": "data.hideDetail",
|
||||
"excel": "Data > Group and Outline > Hide Detail",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "DL",
|
||||
"kind": "command",
|
||||
"target": "data.validation",
|
||||
"excel": "Data > Validation",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "DT",
|
||||
"kind": "command",
|
||||
"target": "data.dataTable",
|
||||
"excel": "Data > Table (what-if data table)",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "VN",
|
||||
"kind": "command",
|
||||
"target": "view.normal",
|
||||
"excel": "View > Normal",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "VP",
|
||||
"kind": "command",
|
||||
"target": "view.pageBreakPreview",
|
||||
"excel": "View > Page Break Preview",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "VZ",
|
||||
"kind": "dialog",
|
||||
"target": "zoom",
|
||||
"excel": "View > Zoom",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "TO",
|
||||
"kind": "dialog",
|
||||
"target": "options",
|
||||
"excel": "Tools > Options",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "TUA",
|
||||
"kind": "command",
|
||||
"target": "formulas.removeArrows",
|
||||
"excel": "Tools > Formula Auditing > Remove All Arrows",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "TUD",
|
||||
"kind": "command",
|
||||
"target": "formulas.traceDependents",
|
||||
"excel": "Tools > Formula Auditing > Trace Dependents",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "TUT",
|
||||
"kind": "command",
|
||||
"target": "formulas.tracePrecedents",
|
||||
"excel": "Tools > Formula Auditing > Trace Precedents",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "FU",
|
||||
"kind": "dialog",
|
||||
"target": "pageSetup",
|
||||
"excel": "File > Page Setup",
|
||||
"note": "Legacy Excel 2003 menu path (#941)."
|
||||
},
|
||||
{
|
||||
"seq": "Q",
|
||||
"kind": "command",
|
||||
|
||||
@@ -102,6 +102,10 @@ pub struct UiState {
|
||||
/// [`keytips::Action::Control`], consumed (and cleared) by the control's render site.
|
||||
#[serde(skip)]
|
||||
pub keytip_invoke: Option<&'static str>,
|
||||
/// A keytip control request for the next frame: its tab was switched to this frame, so its
|
||||
/// control is drawn only then.
|
||||
#[serde(skip)]
|
||||
pub keytip_invoke_next: Option<&'static str>,
|
||||
/// Interface language. English by default (so tests and headless renders never depend on the
|
||||
/// host's locale); the desktop app starts from [`i18n::Language::system`] when `ui.json` has
|
||||
/// no usable language (see [`i18n::saved_language`]). An unknown saved value reads as English
|
||||
@@ -143,6 +147,7 @@ impl Default for UiState {
|
||||
autosave: false,
|
||||
recent: vec![],
|
||||
keytip_invoke: None,
|
||||
keytip_invoke_next: None,
|
||||
language: i18n::Language::En,
|
||||
interface_scale: 100,
|
||||
}
|
||||
|
||||
@@ -27,7 +27,15 @@ pub(super) fn act(app: &mut SheetApp, id: &str, params: serde_json::Value) {
|
||||
|
||||
/// Keytips are painted, not focusable widgets: the grid retains keyboard focus.
|
||||
pub(super) fn keytip(app: &SheetApp, ui: &Ui, sequence: &str, at: egui::Pos2) {
|
||||
let Some(rest) = app.keytips.prefix().and_then(|prefix| sequence.strip_prefix(prefix)).filter(|s| !s.is_empty()) else { return };
|
||||
let Some(rest) = app
|
||||
.keytips
|
||||
.prefix()
|
||||
.filter(|prefix| keytip_table::paints_badges(prefix))
|
||||
.and_then(|prefix| sequence.strip_prefix(prefix))
|
||||
.filter(|s| !s.is_empty())
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let painter = ui.ctx().layer_painter(egui::LayerId::new(egui::Order::Tooltip, egui::Id::unique("ribbon_keytips"))).with_clip_rect(ui.clip_rect());
|
||||
let text = painter.layout_no_wrap(rest.into(), theme::ui_bold(10.5), t.text);
|
||||
|
||||
@@ -213,7 +213,9 @@ pub(super) fn insert(app: &mut SheetApp, ui: &mut Ui) {
|
||||
});
|
||||
let sym = big_button(ui, Icon::Symbol, "Symbol", "Symbol", true);
|
||||
control_keytip(app, ui, &sym, "insert.symbol");
|
||||
egui::Popup::menu(&sym).show(|ui| {
|
||||
// Opened by its key tip too (Alt+I, S): the gallery picks the symbol, nothing is inserted before.
|
||||
let forced = invoke(app, "insert.symbol");
|
||||
egui::Popup::menu(&sym).open_memory(popup_command(forced, sym.clicked())).show(|ui| {
|
||||
ui.set_width(260.0);
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for ch in [
|
||||
|
||||
@@ -31,6 +31,8 @@ pub struct WindowUi {
|
||||
ribbon_tab: String,
|
||||
/// The status bar's Ready/Enter/Edit, which follows the window's cell draft.
|
||||
mode: gridcraft_engine::Mode,
|
||||
/// A key tip's control request waiting for the frame that draws this window's ribbon.
|
||||
keytip_invoke_next: Option<&'static str>,
|
||||
}
|
||||
|
||||
impl WindowUi {
|
||||
@@ -70,6 +72,7 @@ impl SheetApp {
|
||||
open_switch_windows,
|
||||
ribbon_tab,
|
||||
mode,
|
||||
keytip_invoke_next,
|
||||
} = w;
|
||||
std::mem::swap(&mut self.editor, editor);
|
||||
std::mem::swap(&mut self.text_box_editor, text_box_editor);
|
||||
@@ -86,6 +89,7 @@ impl SheetApp {
|
||||
std::mem::swap(&mut self.open_switch_windows, open_switch_windows);
|
||||
std::mem::swap(&mut self.ui.ribbon_tab, ribbon_tab);
|
||||
std::mem::swap(&mut self.session.mode, mode);
|
||||
std::mem::swap(&mut self.ui.keytip_invoke_next, keytip_invoke_next);
|
||||
}
|
||||
|
||||
/// The window whose interface state is in the per-window fields.
|
||||
@@ -98,6 +102,14 @@ impl SheetApp {
|
||||
/// in (a window never seen before starts fresh). A closed window's state is dropped.
|
||||
pub fn follow_active_window(&mut self) {
|
||||
let active = self.session.active_window();
|
||||
// A window hidden while parked (from another window's View › Hide) isn't drawn either:
|
||||
// its waiting key tip request goes, as it does for a window hidden as it is left.
|
||||
let session = &self.session;
|
||||
for (id, parked) in &mut self.parked {
|
||||
if session.window(*id).is_some_and(|w| w.hidden) {
|
||||
parked.keytip_invoke_next = None;
|
||||
}
|
||||
}
|
||||
if active == self.ui_window {
|
||||
return;
|
||||
}
|
||||
@@ -108,7 +120,12 @@ impl SheetApp {
|
||||
};
|
||||
let mut outgoing = WindowUi::fresh(&self.grid, &self.ui.ribbon_tab);
|
||||
self.swap_window_ui(&mut outgoing);
|
||||
if self.session.window(previous).is_some() {
|
||||
if let Some(window) = self.session.window(previous) {
|
||||
// A hidden window isn't drawn: a key tip request waiting for its next frame would
|
||||
// fire whenever it is shown again, so it goes.
|
||||
if window.hidden {
|
||||
outgoing.keytip_invoke_next = None;
|
||||
}
|
||||
self.parked.insert(previous, outgoing);
|
||||
}
|
||||
if let Some(mut incoming) = active.and_then(|w| self.parked.remove(&w)) {
|
||||
|
||||
@@ -2754,6 +2754,308 @@ fn keytip_fp_() {
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "file.print"), "FP must journal file.print");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ir_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "IR", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.insertRows"), "IR must journal home.insertRows");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ic_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "IC", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.insertColumns"), "IC must journal home.insertColumns");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ie_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "IE", false);
|
||||
assert!(h.state().dialog.is_some(), "IE must open the insertCells dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_iw_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "IW", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.insertSheet"), "IW must journal home.insertSheet");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_if_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "IF", false);
|
||||
assert!(h.state().dialog.is_some(), "IF must open the insertFunction dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ind_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "IND", false);
|
||||
assert!(h.state().dialog.is_some(), "IND must open the defineName dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_is_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "IS", false);
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "IS must route to its control without an error box");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ed_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "ED", false);
|
||||
assert!(h.state().dialog.is_some(), "ED must open the deleteCells dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_em_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "EM", false);
|
||||
assert!(h.state().dialog.is_some(), "EM must open the moveSheet dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_el_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "EL", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "EL must invoke home.deleteSheet and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_eaa_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "EAA", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "edit.clearAll"), "EAA must journal edit.clearAll");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_eac_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "EAC", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "edit.clearContents"), "EAC must journal edit.clearContents");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_eam_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "EAM", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "edit.clearComments"), "EAM must journal edit.clearComments");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ef_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "EF", false);
|
||||
assert!(h.state().dialog.is_some(), "EF must open the find dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_eg_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "EG", false);
|
||||
assert!(h.state().dialog.is_some(), "EG must open the goTo dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ora_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "ORA", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.autofitRowHeight"), "ORA must journal home.autofitRowHeight");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ore_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "ORE", false);
|
||||
assert!(h.state().dialog.is_some(), "ORE must open the rowHeight dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_orh_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "ORH", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.hideRows"), "ORH must journal home.hideRows");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_oru_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "ORU", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.unhideRows"), "ORU must journal home.unhideRows");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ocw_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "OCW", false);
|
||||
assert!(h.state().dialog.is_some(), "OCW must open the columnWidth dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_och_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "OCH", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.hideColumns"), "OCH must journal home.hideColumns");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ocu_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "OCU", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.unhideColumns"), "OCU must journal home.unhideColumns");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ohr_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "OHR", false);
|
||||
assert!(h.state().dialog.is_some(), "OHR must open the renameSheet dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ohh_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "OHH", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "OHH must invoke sheet.hide and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ohu_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "OHU", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "OHU must invoke sheet.unhide and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_oe_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "OE", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.formatCells"), "OE must journal home.formatCells");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_od_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "OD", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.conditionalFormat"), "OD must journal home.conditionalFormat");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_dff_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "DFF", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.filter"), "DFF must journal data.filter");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_ds_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "DS", false);
|
||||
assert!(h.state().dialog.is_some(), "DS must open the sort dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_dgg_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "DGG", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.group"), "DGG must journal data.group");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_dgu_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "DGU", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.ungroup"), "DGU must journal data.ungroup");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_dgs_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "DGS", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.showDetail"), "DGS must journal data.showDetail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_dgh_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "DGH", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.hideDetail"), "DGH must journal data.hideDetail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_dl_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "DL", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.validation"), "DL must journal data.validation");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_dt_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "DT", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "DT must invoke data.dataTable and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_vn_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "VN", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "view.normal"), "VN must journal view.normal");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_vp_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "VP", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "view.pageBreakPreview"), "VP must journal view.pageBreakPreview");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_vz_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "VZ", false);
|
||||
assert!(h.state().dialog.is_some(), "VZ must open the zoom dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_to_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "TO", false);
|
||||
assert!(h.state().backstage.is_some(), "TO must open the Options page");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_tua_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "TUA", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.removeArrows"), "TUA must journal formulas.removeArrows");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_tud_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "TUD", false);
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "TUD must run formulas.traceDependents cleanly");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_tut_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "TUT", false);
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "TUT must run formulas.tracePrecedents cleanly");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_fu_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "FU", false);
|
||||
assert!(h.state().dialog.is_some(), "FU must open the pageSetup dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_q_() {
|
||||
let mut h = harness(blank());
|
||||
@@ -2766,7 +3068,7 @@ fn keytip_q_() {
|
||||
|
||||
#[test]
|
||||
fn generated_test_count_matches_the_table() {
|
||||
const EXPECTED: usize = 367;
|
||||
const EXPECTED: usize = 410;
|
||||
assert_eq!(gridcraft_ui_egui::keytips::keytip_table::SEQUENCES.len(), EXPECTED);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
//! Excel 2003 menu access keys that open a ribbon control rather than run a command (#941).
|
||||
|
||||
use egui::os::OperatingSystem;
|
||||
use egui::{Event, Key, Modifiers};
|
||||
use egui_kittest::kittest::Queryable as _;
|
||||
use gridcraft_engine::Session;
|
||||
use gridcraft_ui_egui::SheetApp;
|
||||
use serde_json::json;
|
||||
|
||||
fn press(h: &mut egui_kittest::Harness<'static, SheetApp>, key: Key) {
|
||||
let ev = |pressed| Event::Key { key, physical_key: None, pressed, repeat: false, modifiers: Modifiers::NONE };
|
||||
h.input_mut().events.extend([ev(true), Event::Text(key.name().to_ascii_lowercase()), ev(false)]);
|
||||
h.run_steps(2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alt_i_s_opens_the_symbol_gallery_and_leaves_the_cell_alone() {
|
||||
// Insert > Symbol picks a symbol; running insert.symbol with no symbol set the active cell
|
||||
// to its own text again, re-reading it (a Python cell lost its code, text could turn into a
|
||||
// number). From the Home tab the keys show the Insert tab's gallery instead.
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
s.execute("cell.set", json!({"cell": "A1", "input": "'007"})).unwrap();
|
||||
let mut h = crate::common::os_harness(s, OperatingSystem::Nix);
|
||||
h.run_steps(4);
|
||||
for key in [Key::F10, Key::I, Key::S] {
|
||||
press(&mut h, key);
|
||||
}
|
||||
h.run_steps(4);
|
||||
assert!(!h.state().session.journal.iter().any(|(id, _)| id == "insert.symbol"), "nothing is inserted until a symbol is picked");
|
||||
assert_eq!(h.state().ui.ribbon_tab, "Insert");
|
||||
assert!(h.query_by_label("€").is_some(), "the Symbol gallery is open");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn alt_i_s_expands_a_collapsed_ribbon_on_the_insert_tab() {
|
||||
// With the Insert tab already chosen but the ribbon collapsed, the gallery's control isn't
|
||||
// drawn: the keys expand the ribbon first.
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
let mut h = crate::common::os_harness(s, OperatingSystem::Nix);
|
||||
h.state_mut().run("ui.ribbonTab", json!({"tab": "Insert"})).unwrap();
|
||||
h.state_mut().ui.ribbon_collapsed = true;
|
||||
h.run_steps(4);
|
||||
for key in [Key::F10, Key::I, Key::S] {
|
||||
press(&mut h, key);
|
||||
}
|
||||
h.run_steps(4);
|
||||
assert!(!h.state().ui.ribbon_collapsed);
|
||||
assert!(h.query_by_label("€").is_some(), "the Symbol gallery is open");
|
||||
assert!(!h.state().session.journal.iter().any(|(id, _)| id == "insert.symbol"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_waiting_key_tip_request_stays_with_its_window() {
|
||||
// A request waiting for the frame that draws its control belongs to the window whose keys
|
||||
// made it: another window drawn first must not take it.
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
let mut h = crate::common::os_harness(s, OperatingSystem::Nix);
|
||||
h.run_steps(2);
|
||||
let first = h.state().session.active_window().unwrap();
|
||||
h.state_mut().run("view.newWindow", json!({})).unwrap();
|
||||
let second = h.state().session.active_window().unwrap();
|
||||
h.state_mut().show_window(second);
|
||||
h.state_mut().ui.keytip_invoke_next = Some("insert.symbol");
|
||||
h.state_mut().show_window(first);
|
||||
assert_eq!(h.state().ui.keytip_invoke_next, None, "the first window doesn't see the second window's request");
|
||||
h.state_mut().show_window(second);
|
||||
assert_eq!(h.state().ui.keytip_invoke_next, Some("insert.symbol"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_hidden_window_drops_its_waiting_key_tip_request() {
|
||||
// A hidden window isn't drawn, so its waiting request would fire when it was shown again,
|
||||
// long after the keys: hiding the window drops it.
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
let mut h = crate::common::os_harness(s, OperatingSystem::Nix);
|
||||
h.run_steps(2);
|
||||
let first = h.state().session.active_window().unwrap();
|
||||
h.state_mut().run("view.newWindow", json!({})).unwrap();
|
||||
let second = h.state().session.active_window().unwrap();
|
||||
h.state_mut().show_window(second);
|
||||
h.state_mut().ui.keytip_invoke_next = Some("insert.symbol");
|
||||
h.state_mut().run("view.hideWindow", json!({"window": second})).unwrap();
|
||||
h.state_mut().show_window(first);
|
||||
h.state_mut().run("view.unhideWindow", json!({"window": second})).unwrap();
|
||||
h.state_mut().show_window(second);
|
||||
assert_eq!(h.state().ui.keytip_invoke_next, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_window_hidden_from_another_window_drops_its_parked_request() {
|
||||
// Hidden while another window is in front (its request already parked), the window still
|
||||
// loses the request, so showing it again doesn't open the control.
|
||||
let mut s = Session::new();
|
||||
s.new_workbook();
|
||||
let mut h = crate::common::os_harness(s, OperatingSystem::Nix);
|
||||
h.run_steps(2);
|
||||
let first = h.state().session.active_window().unwrap();
|
||||
h.state_mut().run("view.newWindow", json!({})).unwrap();
|
||||
let second = h.state().session.active_window().unwrap();
|
||||
h.state_mut().show_window(second);
|
||||
h.state_mut().ui.keytip_invoke_next = Some("insert.symbol");
|
||||
h.state_mut().enter_window(first);
|
||||
h.state_mut().run("view.hideWindow", json!({"window": second})).unwrap();
|
||||
h.state_mut().show_window(first);
|
||||
h.state_mut().run("view.unhideWindow", json!({"window": second})).unwrap();
|
||||
h.state_mut().show_window(second);
|
||||
assert_eq!(h.state().ui.keytip_invoke_next, None);
|
||||
}
|
||||
@@ -64,6 +64,7 @@ mod interaction;
|
||||
mod interface_scale;
|
||||
mod keyboard_shortcuts;
|
||||
mod keytips_generated;
|
||||
mod legacy_keytips;
|
||||
mod macro_editor;
|
||||
mod macros_ui;
|
||||
mod menu_hover;
|
||||
|
||||
+2
-1
@@ -21,7 +21,7 @@ Sources: `crates/ui-egui/src` (ribbon, grid, editor, formula bar, dialogs, panes
|
||||
| Formulas › Function Library | Insert Function, AutoSum, Recently Used and a menu per function category | yes (10-24): a big Insert Function button and three columns of labelled small buttons, the layout Excel uses when the window is not wide enough for a big button per category; each category's book icon has its own colour; a category menu shows each function's description on hover and ends with Insert Function…, which opens the dialog on that category; More Functions cascades into Statistical, Engineering, Cube, Information, Compatibility and Web; AutoSum is a split button whose menu (Sum, Average, Count Numbers, Max, Min, More Functions…) Home › Editing shares; Recently Used has the MR key tip | the wide layout with a big button per category is not offered; a function counts as recently used once its formula is entered, not when Insert Function's OK is pressed |
|
||||
| Ribbon: tabs, groups, split buttons, galleries, contextual tabs | yes | yes (Table Design, Chart Design, Format, PivotTable tabs) | done; missing contextual tabs for pictures, shapes, sparklines, header/footer, slicers |
|
||||
| Ribbon collapse, simplified ribbon, customize ribbon/QAT | yes | partial | customization missing |
|
||||
| Key tips (Alt / F10 then letters) | yes, every ribbon control | **initial** (#43, 10-10): Alt or F10 shows key tips on Home for alignment, Paste Special and AutoFit plus legacy Alt sequences; other tabs and controls not yet (#33, #49) | 3–5 h |
|
||||
| Key tips (Alt / F10 then letters) | yes, every ribbon control | **initial** (#43, 10-10): Alt or F10 shows key tips on Home for alignment, Paste Special and AutoFit plus the Excel 2003 menu keys (Alt+I, E, O, D, V, T and Alt+F, U, such as Alt+I, R or Alt+D, S); Alt+I, D, V and T, which have no ribbon tab, paint no badges, as in Excel (#941); other tabs and controls not yet (#33, #49) | 3–5 h |
|
||||
| Formula bar with Name Box, expand, function hints | yes | yes; grows with its content from one to five lines (Alt+Enter, wrapped text, or viewing a multi-line cell), then scrolls with the caret in view, the Name Box and buttons staying on the first line; the chevron pins it at five; the in-cell editor shows every line | done; no drag-to-resize handle |
|
||||
| Sheet tabs: rename, reorder, colour, hide, scroll | yes | yes | Ctrl-drag (Cmd or Option on macOS) drops a copy (#971); active tab revealed on navigation and resize (#681); manual wheel/trackpad browsing preserved; rename focus bug on macOS (#145) |
|
||||
| Formula bar with Name Box, expand, function hints | yes | yes | done; dynamic-array spill children show the source formula muted and read-only, using current spill ownership |
|
||||
@@ -238,6 +238,7 @@ scrollbar bugs 2–4 · pixel/feel pass against observed Excel 7–12.
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-03 | minor | Excel 2003 menu access keys: 43 legacy sequences (Insert, Edit, Format, Data, View, Tools and File ▸ Page Setup, e.g. Alt+I, R inserts rows, Alt+E, A, A clears all, Alt+D, S sorts, Alt+T, O opens Options) run the existing commands and dialogs; Alt+I, D, V and T paint no badges, as in Excel (#941, #2311) |
|
||||
| 2026-11-03 | minor | Paste Special: every option has Excel's access key (A, F, V, T, C, N, X, W, R, U; O, D, S, M, I; B, E; L pastes the link at once), not only F, V, T, C and E (#1482) |
|
||||
| 2026-11-03 | minor | Draw tab: the stroke drawn under the pointer while dragging a pen is the ink that will be committed, in the pen's colour and thickness (translucent for a highlighter), instead of always blue at 2 pt (#1786) |
|
||||
| 2026-11-03 | minor | Copy puts a cell that holds a quote on the clipboard as it is (`say "hi"`), quoting only text with a tab or line break, as Excel does; pasting text reads a field as quoted only when its closing quote ends it, so `"hi" there` pastes with its quotes (#2225) |
|
||||
|
||||
@@ -323,6 +323,11 @@ fn render_tests(entries: &[Entry], probe: &Probe) -> String {
|
||||
id = e.target,
|
||||
),
|
||||
},
|
||||
// Options is a Backstage page rather than a dialog box.
|
||||
"dialog" if e.target == "options" => format!(
|
||||
"sequence(&mut h, {seq:?}, false);\n assert!(h.state().backstage.is_some(), \"{seq} must open the Options page\");",
|
||||
seq = e.seq,
|
||||
),
|
||||
"dialog" => format!(
|
||||
"sequence(&mut h, {seq:?}, false);\n assert!(h.state().dialog.is_some(), \"{seq} must open the {dialog} dialog\");",
|
||||
seq = e.seq,
|
||||
|
||||
Reference in new issue
Block a user