mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-13 15:09:05 +00:00
Dropdown keytips open their controls instead of firing empty commands; complete View badges
- New Action::Control routes a keytip to its ribbon control (popup/dropdown open, checkbox toggle, button handler) instead of an engine command with empty params. Fixes ALT+H FF/FS/N/H/H error boxes. - Ribbon consumes a one-shot ui.keytip_invoke: font/size/number combos converted to the popup pattern (ComboBox has no open_memory), fill/font color menus force-open via SetOpenCommand, 100% zoom and Formula Bar run their handlers. - keytips.json: FF/FS/N/H/H -> control kind; View/Formulas/Data dialog openers (WQ zoom, HFM format cells, HO*, MV, AWG, sparkline types) and control WVF; MSP/HVP re-targeted; dropped 15 unreachable Home sub-items and non-Excel HWS shrink-to-fit. - View tab: badges on workbook views, gridlines/headings, formula bar. - Generated suite: 399 tests incl control-kind arm; direct tests 18.
This commit is contained in:
1 parent
7591fca086
commit
cbd9bb51c6
9 files changed
+257
-408
No files matched your search
@@ -14,6 +14,10 @@ pub enum Action {
|
||||
/// The target command does not exist yet: the sequence is reserved, its badge shows, and
|
||||
/// invoking it is a silent no-op until the command lands (see keytips.json `pending`).
|
||||
Pending(&'static str),
|
||||
/// Route the keytip to the matching ribbon control: open its dropdown or run its click
|
||||
/// handler (which carries the right params), instead of firing the engine command with
|
||||
/// empty params.
|
||||
Control(&'static str),
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
|
||||
@@ -50,7 +50,7 @@ pub fn is_group_level(seq: &str) -> bool {
|
||||
|
||||
fn matches_target(action: &Action, target: &str) -> bool {
|
||||
match action {
|
||||
Action::Command(id) | Action::Pending(id) => *id == target,
|
||||
Action::Command(id) | Action::Pending(id) | Action::Control(id) => *id == target,
|
||||
Action::Dialog(name) => format!("dialog:{name}") == target,
|
||||
Action::Tab(_) => false,
|
||||
}
|
||||
@@ -200,7 +200,7 @@ mod tests {
|
||||
#[test]
|
||||
fn item_letter_is_one_level_deep() {
|
||||
assert_eq!(item_letter("HV", "dialog:pasteSpecial"), Some("HVS"));
|
||||
assert_eq!(item_letter("HV", "edit.pasteSpecial"), Some("HVA"));
|
||||
assert_eq!(item_letter("HV", "edit.pasteSpecial"), Some("HVP"));
|
||||
assert_eq!(item_letter("H", "dialog:pasteSpecial"), None, "the item badge waits for its group letter");
|
||||
assert_eq!(item_letter("HO", "home.autofitColumnWidth"), Some("HOI"));
|
||||
}
|
||||
|
||||
@@ -72,16 +72,16 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("HEF", Entry::Finish(Action::Command("edit.clearFormats"))), // Excel: Clear Formats
|
||||
("HEL", Entry::Finish(Action::Command("edit.clearHyperlinks"))), // Excel: Clear Hyperlinks
|
||||
("HEM", Entry::Finish(Action::Command("edit.clearComments"))), // Excel: Clear Comments and Notes; containment(Clear Comments and Notes)
|
||||
("HFC", Entry::Finish(Action::Command("home.fontColor"))), // Excel: Font Color
|
||||
("HFC", Entry::Finish(Action::Control("home.fontColor"))), // Excel: Font Color; opens the font-color palette
|
||||
("HFDC", Entry::Finish(Action::Command("home.conditionalFormat"))), // Excel: Find Conditional Formatting; containment(Conditional Formatting)
|
||||
("HFDF", Entry::Finish(Action::Command("edit.find"))), // Excel: Find
|
||||
("HFDG", Entry::Finish(Action::Command("edit.goTo"))), // Excel: Go To
|
||||
("HFDF", Entry::Finish(Action::Dialog("find"))), // Excel: Find
|
||||
("HFDG", Entry::Finish(Action::Dialog("goTo"))), // Excel: Go To
|
||||
("HFDM", Entry::Finish(Action::Command("edit.find"))), // Excel: Find Notes; containment(Find)
|
||||
("HFDO", Entry::Finish(Action::Pending("home.selectObjects"))), // pending; Excel: Select Objects; Select Objects; reserved until the command lands
|
||||
("HFDP", Entry::Finish(Action::Command("arrange.selectionPane"))), // Excel: Selection Pane
|
||||
("HFDR", Entry::Finish(Action::Command("edit.replace"))), // Excel: Replace
|
||||
("HFDR", Entry::Finish(Action::Dialog("find"))), // Excel: Replace; opens Find and Replace
|
||||
("HFDS", Entry::Finish(Action::Command("edit.goToSpecial"))), // Excel: Go To Special
|
||||
("HFF", Entry::Finish(Action::Command("home.fontName"))), // Excel: Font Selection
|
||||
("HFF", Entry::Finish(Action::Control("home.fontName"))), // Excel: Font Selection; opens the font dropdown
|
||||
("HFG", Entry::Finish(Action::Command("home.increaseFontSize"))), // Excel: Increase Font Size
|
||||
("HFI", Entry::Continue), // Excel: Fill; group level: containment(Fill Up)
|
||||
("HFIA", Entry::Finish(Action::Pending("home.fillAcrossWorksheets"))), // pending; Excel: Fill Across Worksheets; Fill Across Worksheets; reserved until the command lands
|
||||
@@ -93,13 +93,13 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("HFIS", Entry::Finish(Action::Command("edit.fillSeries"))), // Excel: Fill Series; containment(Series)
|
||||
("HFIU", Entry::Finish(Action::Command("edit.fillUp"))), // Excel: Fill Up
|
||||
("HFK", Entry::Finish(Action::Command("home.decreaseFontSize"))), // Excel: Decrease Font Size
|
||||
("HFM", Entry::Finish(Action::Command("home.numberFormat"))), // Excel: More Number Formats; containment(Number Format)
|
||||
("HFM", Entry::Finish(Action::Dialog("formatCells"))), // Excel: More Number Formats; More Number Formats opens Format Cells
|
||||
("HFP", Entry::Finish(Action::Command("edit.formatPainter"))), // Excel: Format Painter
|
||||
("HFQ", Entry::Continue), // Excel: Orientation Menu; group level: containment(Orientation)
|
||||
("HFQL", Entry::Finish(Action::Pending("home.angleClockwise"))), // pending; Excel: Angle Clockwise; Angle Clockwise; reserved until the command lands
|
||||
("HFQO", Entry::Finish(Action::Pending("home.angleCounterclockwise"))), // pending; Excel: Angle Counterclockwise; Angle Counterclockwise; reserved until the command lands
|
||||
("HFS", Entry::Finish(Action::Command("home.fontSize"))), // Excel: Font Size
|
||||
("HH", Entry::Finish(Action::Command("home.fillColor"))), // Excel: Fill Color
|
||||
("HFS", Entry::Finish(Action::Control("home.fontSize"))), // Excel: Font Size; opens the font-size dropdown
|
||||
("HH", Entry::Finish(Action::Control("home.fillColor"))), // Excel: Fill Color; opens the fill-color palette
|
||||
("HIC", Entry::Finish(Action::Command("home.insertColumns"))), // Excel: Insert Columns; Insert Columns
|
||||
("HIE", Entry::Finish(Action::Command("home.insertCells"))), // Excel: Insert Copied Cells; Insert Cells (copied cells TBD)
|
||||
("HII", Entry::Finish(Action::Command("home.insertCells"))), // Excel: Insert Cells
|
||||
@@ -123,15 +123,15 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("HMC", Entry::Finish(Action::Command("home.mergeCenter"))), // Excel: Merge & Center
|
||||
("HMM", Entry::Finish(Action::Command("home.mergeCells"))), // Excel: Merge Cells
|
||||
("HMU", Entry::Finish(Action::Command("home.unmergeCells"))), // Excel: Unmerge Cells
|
||||
("HN", Entry::Finish(Action::Command("home.numberFormat"))), // Excel: Number Format
|
||||
("HN", Entry::Finish(Action::Control("home.numberFormat"))), // Excel: Number Format; opens the number-format dropdown
|
||||
("HOA", Entry::Finish(Action::Command("home.autofitRowHeight"))), // Excel: AutoFit Row Height
|
||||
("HOD", Entry::Finish(Action::Command("home.columnWidth"))), // Excel: Default Column Width; containment(Column Width)
|
||||
("HOD", Entry::Finish(Action::Dialog("defaultWidth"))), // Excel: Default Column Width; containment(Column Width)
|
||||
("HOE", Entry::Finish(Action::Command("home.formatCells"))), // Excel: Format Cells
|
||||
("HOH", Entry::Finish(Action::Command("home.rowHeight"))), // Excel: Row Height
|
||||
("HOH", Entry::Finish(Action::Dialog("rowHeight"))), // Excel: Row Height
|
||||
("HOL", Entry::Finish(Action::Command("home.lockCell"))), // Excel: Lock Cell; containment(Lock Cell)
|
||||
("HOM", Entry::Finish(Action::Command("sheet.move"))), // Excel: Move or Copy Sheet
|
||||
("HOM", Entry::Finish(Action::Dialog("moveSheet"))), // Excel: Move or Copy Sheet
|
||||
("HOP", Entry::Finish(Action::Command("review.protectSheet"))), // Excel: Protect Sheet
|
||||
("HOR", Entry::Finish(Action::Command("sheet.rename"))), // Excel: Rename Sheet
|
||||
("HOR", Entry::Finish(Action::Dialog("renameSheet"))), // Excel: Rename Sheet
|
||||
("HOT", Entry::Finish(Action::Command("sheet.tabColor"))), // Excel: Tab Color
|
||||
("HOUC", Entry::Finish(Action::Command("home.hideColumns"))), // Excel: Hide Columns
|
||||
("HOUH", Entry::Finish(Action::Command("sheet.unhide"))), // Excel: Unhide Sheet
|
||||
@@ -139,7 +139,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("HOUO", Entry::Finish(Action::Command("home.unhideRows"))), // Excel: Unhide Rows
|
||||
("HOUR", Entry::Finish(Action::Command("home.hideRows"))), // Excel: Hide Rows
|
||||
("HOUS", Entry::Finish(Action::Command("sheet.hide"))), // Excel: Hide Sheet
|
||||
("HOW", Entry::Finish(Action::Command("home.columnWidth"))), // Excel: Column Width
|
||||
("HOW", Entry::Finish(Action::Dialog("columnWidth"))), // Excel: Column Width
|
||||
("HP", Entry::Finish(Action::Command("home.percent"))), // Excel: Excel: Percentage Style
|
||||
("HSC", Entry::Finish(Action::Command("data.clearFilter"))), // Excel: Clear Filter
|
||||
("HSF", Entry::Finish(Action::Command("data.filter"))), // Excel: Filter
|
||||
@@ -155,54 +155,39 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("HUI", Entry::Finish(Action::Command("formulas.autoSum"))), // Excel: Auto Min; AutoSum: Min (variant TBD)
|
||||
("HUM", Entry::Finish(Action::Command("formulas.autoSum"))), // Excel: Auto Max; AutoSum: Max (variant TBD)
|
||||
("HV", Entry::Continue), // Excel: Paste; group level:
|
||||
("HVA", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Values and Number Formatting; paste variant, params TBD: what=TBD(values+numfmt)
|
||||
("HVB", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste No Borders; paste variant, params TBD: what=TBD(no-borders)
|
||||
("HVE", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Values and Source Formatting; paste variant, params TBD: what=TBD(values+src-fmt)
|
||||
("HVF", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Formula; paste-variant TBD
|
||||
("HVI", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Linked Picture; what=TBD(linked-picture)
|
||||
("HVK", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Keep Source Formatting; paste variant, params TBD: what=TBD(keep-source)
|
||||
("HVM", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Destination Formatting; paste variant, params TBD: what=TBD(dest)
|
||||
("HVN", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Link; paste variant, params TBD: what=TBD(link)
|
||||
("HVO", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Formula and Formatting; paste variant, params TBD: what=TBD(formula+formatting)
|
||||
("HVP", Entry::Finish(Action::Command("edit.paste"))), // Excel: Paste
|
||||
("HVR", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Formatting; paste variant, params TBD: what=formats
|
||||
("HVT", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Transpose; paste variant, params TBD: what=transpose
|
||||
("HVU", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Picture; what=TBD(picture)
|
||||
("HVV", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Values; paste variant, params TBD: what=values
|
||||
("HVW", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Keep Source Column Widths; paste variant, params TBD: what=TBD(col-widths)
|
||||
("HW", Entry::Continue), // Excel: Wrap Text Menu; group level: containment(Wrap Text)
|
||||
("HWS", Entry::Finish(Action::Command("home.shrinkToFit"))), // Excel: Shrink to Fit; landed in the catalog; live since T7 probe
|
||||
("HWW", Entry::Finish(Action::Command("home.wrapText"))), // Excel: Wrap Text
|
||||
("HX", Entry::Finish(Action::Command("edit.cut"))), // Excel: Cut
|
||||
("NC1", Entry::Finish(Action::Command("insert.chart"))), // Excel: Column Chart; Column chart (kind TBD)
|
||||
("HVP", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste
|
||||
("HW", Entry::Continue), // Excel: Wrap Text Menu; group level: containment(Wrap Text)
|
||||
("HWW", Entry::Finish(Action::Command("home.wrapText"))), // Excel: Wrap Text
|
||||
("HX", Entry::Finish(Action::Command("edit.cut"))), // Excel: Cut
|
||||
("NC1", Entry::Finish(Action::Command("insert.chart"))), // Excel: Column Chart; Column chart (kind TBD)
|
||||
("NC2", Entry::Finish(Action::Command("review.newComment"))), // Excel: New Comment
|
||||
("NCM", Entry::Finish(Action::Command("review.newComment"))), // Excel: Excel: New Comment (modern)
|
||||
("NCB", Entry::Finish(Action::Command("insert.checkbox"))), // Excel: Checkbox
|
||||
("ND", Entry::Finish(Action::Command("insert.chart"))), // Excel: Scatter Chart; Scatter (kind TBD)
|
||||
("NE", Entry::Finish(Action::Pending("insert.equation"))), // pending; Excel: Equations; Equations; reserved until the command lands
|
||||
("NF", Entry::Continue), // Excel: Online Pictures; group level: containment(Picture)
|
||||
("NFP", Entry::Continue), // Excel: From Picture Menu; group level: containment(Picture)
|
||||
("NFPF", Entry::Finish(Action::Command("insert.picture"))), // Excel: Picture From File; containment(Picture)
|
||||
("NCB", Entry::Finish(Action::Command("insert.checkbox"))), // Excel: Checkbox
|
||||
("ND", Entry::Finish(Action::Command("insert.chart"))), // Excel: Scatter Chart; Scatter (kind TBD)
|
||||
("NE", Entry::Finish(Action::Pending("insert.equation"))), // pending; Excel: Equations; Equations; reserved until the command lands
|
||||
("NF", Entry::Continue), // Excel: Online Pictures; group level: containment(Picture)
|
||||
("NFP", Entry::Continue), // Excel: From Picture Menu; group level: containment(Picture)
|
||||
("NFPF", Entry::Finish(Action::Command("insert.picture"))), // Excel: Picture From File; containment(Picture)
|
||||
("NH1", Entry::Finish(Action::Command("pageLayout.headerFooter"))), // Excel: Header & Footer
|
||||
("NHF", Entry::Finish(Action::Command("pageLayout.headerFooter"))), // Excel: Header & Footer
|
||||
("NHI", Entry::Finish(Action::Command("insert.chart"))), // Excel: Hierarchy Chart; Hierarchy (kind TBD)
|
||||
("NI1", Entry::Finish(Action::Command("insert.chart"))), // Excel: Waterfall Chart; Waterfall (kind TBD)
|
||||
("NI2", Entry::Finish(Action::Command("insert.link"))), // Excel: Link
|
||||
("NJ", Entry::Finish(Action::Pending("insert.object"))), // pending; Excel: Object; insert.object missing; reserved until the command lands
|
||||
("NM1T", Entry::Finish(Action::Command("insert.picture"))), // Excel: SmartArt Picture; containment(Picture)
|
||||
("NM2", Entry::Finish(Action::Command("insert.chart"))), // Excel: Map Chart; Map (kind TBD)
|
||||
("NN1", Entry::Finish(Action::Command("insert.chart"))), // Excel: Line Chart; Line chart (kind TBD)
|
||||
("NNS", Entry::Finish(Action::Command("insert.icons"))), // Excel: Icons
|
||||
("NP", Entry::Continue), // Excel: Picture; group level:
|
||||
("NPB", Entry::Finish(Action::Command("insert.picture"))), // Excel: Picture from File; containment(Picture)
|
||||
("NPF", Entry::Finish(Action::Command("insert.picture"))), // Excel: Place in Cell; Picture in cell (source TBD)
|
||||
("NPI", Entry::Finish(Action::Command("insert.picture"))), // Excel: Place in Cell Menu; Picture in cell (source TBD)
|
||||
("NPO", Entry::Finish(Action::Command("insert.picture"))), // Excel: Place over Cells Menu; Picture over cells (source TBD)
|
||||
("NQ", Entry::Finish(Action::Command("insert.chart"))), // Excel: Pie Chart; Pie (kind TBD)
|
||||
("NHI", Entry::Finish(Action::Command("insert.chart"))), // Excel: Hierarchy Chart; Hierarchy (kind TBD)
|
||||
("NI1", Entry::Finish(Action::Command("insert.chart"))), // Excel: Waterfall Chart; Waterfall (kind TBD)
|
||||
("NI2", Entry::Finish(Action::Command("insert.link"))), // Excel: Link
|
||||
("NJ", Entry::Finish(Action::Pending("insert.object"))), // pending; Excel: Object; insert.object missing; reserved until the command lands
|
||||
("NM1T", Entry::Finish(Action::Command("insert.picture"))), // Excel: SmartArt Picture; containment(Picture)
|
||||
("NM2", Entry::Finish(Action::Command("insert.chart"))), // Excel: Map Chart; Map (kind TBD)
|
||||
("NN1", Entry::Finish(Action::Command("insert.chart"))), // Excel: Line Chart; Line chart (kind TBD)
|
||||
("NNS", Entry::Finish(Action::Command("insert.icons"))), // Excel: Icons
|
||||
("NP", Entry::Continue), // Excel: Picture; group level:
|
||||
("NPB", Entry::Finish(Action::Command("insert.picture"))), // Excel: Picture from File; containment(Picture)
|
||||
("NPF", Entry::Finish(Action::Command("insert.picture"))), // Excel: Place in Cell; Picture in cell (source TBD)
|
||||
("NPI", Entry::Finish(Action::Command("insert.picture"))), // Excel: Place in Cell Menu; Picture in cell (source TBD)
|
||||
("NPO", Entry::Finish(Action::Command("insert.picture"))), // Excel: Place over Cells Menu; Picture over cells (source TBD)
|
||||
("NQ", Entry::Finish(Action::Command("insert.chart"))), // Excel: Pie Chart; Pie (kind TBD)
|
||||
("NR", Entry::Finish(Action::Command("insert.recommendedCharts"))), // Excel: Recommended Charts
|
||||
("NSA", Entry::Finish(Action::Command("insert.chart"))), // Excel: Statistical Chart; Statistical chart (kind TBD)
|
||||
("NSA", Entry::Finish(Action::Command("insert.chart"))), // Excel: Statistical Chart; Statistical chart (kind TBD)
|
||||
("NSC", Entry::Finish(Action::Pending("insert.screenshot"))), // pending; Excel: Screenshot Menu; Screenshot; reserved until the command lands
|
||||
("NSD", Entry::Finish(Action::Command("insert.chart"))), // Excel: Combo Chart; Combo (kind TBD)
|
||||
("NSD", Entry::Finish(Action::Command("insert.chart"))), // Excel: Combo Chart; Combo (kind TBD)
|
||||
("NSF", Entry::Finish(Action::Pending("insert.slicer"))), // pending; Excel: Slicer; insert.slicer missing (gap #8); reserved until the command lands
|
||||
("NSH", Entry::Finish(Action::Command("insert.shape"))), // Excel: Shapes
|
||||
("NSP", Entry::Finish(Action::Command("insert.recommendedPivotTables"))), // Excel: Recommended PivotTables
|
||||
@@ -215,9 +200,9 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("NW", Entry::Finish(Action::Pending("insert.wordArt"))), // pending; Excel: WordArt; WordArt; reserved until the command lands
|
||||
("NX", Entry::Finish(Action::Command("insert.textBox"))), // Excel: Text Box
|
||||
("NZK", Entry::Continue), // Excel: Sparklines Menu; group level:
|
||||
("NZKSL", Entry::Finish(Action::Command("insert.sparkline"))), // Excel: Line Sparkline
|
||||
("NZKSO", Entry::Finish(Action::Command("insert.sparkline"))), // Excel: Column Sparkline
|
||||
("NZKSW", Entry::Finish(Action::Command("insert.sparkline"))), // Excel: Win/Loss Sparkline
|
||||
("NZKSL", Entry::Finish(Action::Dialog("sparkline"))), // Excel: Line Sparkline
|
||||
("NZKSO", Entry::Finish(Action::Dialog("sparkline"))), // Excel: Column Sparkline
|
||||
("NZKSW", Entry::Finish(Action::Dialog("sparkline"))), // Excel: Win/Loss Sparkline
|
||||
("NZTX", Entry::Finish(Action::Command("insert.textBox"))), // Excel: Text Box
|
||||
("NZT", Entry::Finish(Action::Command("insert.textBox"))), // Excel: Excel: Text Box (modern)
|
||||
("PAE", Entry::Finish(Action::Command("arrange.sendBackward"))), // Excel: Send Backward
|
||||
@@ -278,7 +263,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("MR", Entry::Continue), // Excel: Recently Used Menu; group level: containment(Recently Used)
|
||||
("MRF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Recently Used - Insert Function; containment(Insert Function)
|
||||
("MS", Entry::Continue), // Excel: Use in Formula Menu; group level: containment(Use in Formula)
|
||||
("MSP", Entry::Finish(Action::Command("edit.pasteSpecial"))), // Excel: Paste Names; paste-variant TBD
|
||||
("MSP", Entry::Finish(Action::Pending("formulas.pasteName"))), // pending; Excel: Paste Names; reserved until the command lands
|
||||
("MT", Entry::Continue), // Excel: Text Menu; group level: containment(Text)
|
||||
("MTF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Text - Insert Function; containment(Insert Function)
|
||||
("MU", Entry::Continue), // Excel: AutoSum Menu; group level:
|
||||
@@ -288,7 +273,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("MUI", Entry::Finish(Action::Command("formulas.autoSum"))), // Excel: Min; AutoSum: Min (variant TBD)
|
||||
("MUM", Entry::Finish(Action::Command("formulas.autoSum"))), // Excel: Max; AutoSum: Max (variant TBD)
|
||||
("MUS", Entry::Finish(Action::Command("formulas.autoSum"))), // Excel: Sum
|
||||
("MV", Entry::Finish(Action::Command("formulas.evaluateFormula"))), // Excel: Evaluate Formula
|
||||
("MV", Entry::Finish(Action::Dialog("evaluateFormula"))), // Excel: Evaluate Formula
|
||||
("MW", Entry::Finish(Action::Command("formulas.watchWindow"))), // Excel: Watch Window
|
||||
("MX", Entry::Continue), // Excel: Calculation Options Menu; group level: containment(Calculation Options)
|
||||
("MXA", Entry::Finish(Action::Command("formulas.calculationOptions"))), // Excel: Automatic Calculation; Calculation options: Automatic
|
||||
@@ -347,7 +332,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("AVR", Entry::Finish(Action::Pending("data.clearValidationCircles"))), // pending; Excel: Clear Validation Circles; reserved until the command lands
|
||||
("AVV", Entry::Finish(Action::Command("data.validation"))), // Excel: Data Validation
|
||||
("AW", Entry::Continue), // group: group: What-If (goalSeek/scenarioManager/dataTable)
|
||||
("AWG", Entry::Finish(Action::Command("data.goalSeek"))), // Excel: Goal Seek
|
||||
("AWG", Entry::Finish(Action::Dialog("goalSeek"))), // Excel: Goal Seek
|
||||
("AWS", Entry::Finish(Action::Command("data.scenarioManager"))), // Excel: Scenario Manager
|
||||
("AWT", Entry::Finish(Action::Command("data.dataTable"))), // Excel: Data Table
|
||||
("AX", Entry::Finish(Action::Pending("data.existingConnections"))), // pending; Excel: Existing Connections; gap #12; reserved until the command lands
|
||||
@@ -377,21 +362,21 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
|
||||
("WG", Entry::Finish(Action::Command("view.zoomToSelection"))), // Excel: Zoom To Selection
|
||||
("WH", Entry::Finish(Action::Command("view.hideWindow"))), // Excel: Hide Window
|
||||
("WI", Entry::Finish(Action::Command("view.pageBreakPreview"))), // Excel: Page Break Preview
|
||||
("WJ", Entry::Finish(Action::Command("view.zoom"))), // Excel: Zoom To 100%
|
||||
("WJ", Entry::Finish(Action::Control("view.zoom"))), // Excel: Zoom To 100%; runs the 100% zoom handler (percent param)
|
||||
("WL", Entry::Finish(Action::Command("view.normal"))), // Excel: Normal View; containment(Normal)
|
||||
("WM", Entry::Continue), // Excel: Macros Menu; group level: containment(Macros)
|
||||
("WMR", Entry::Finish(Action::Command("view.macros"))), // Excel: Record Macro; Record Macro (macros menu)
|
||||
("WMV", Entry::Finish(Action::Command("view.macros"))), // Excel: View Macros; containment(Macros)
|
||||
("WN", Entry::Finish(Action::Command("view.newWindow"))), // Excel: New Window
|
||||
("WP", Entry::Finish(Action::Command("view.pageLayout"))), // Excel: Page Layout View
|
||||
("WQ", Entry::Finish(Action::Command("view.zoom"))), // Excel: Zoom
|
||||
("WQ", Entry::Finish(Action::Dialog("zoom"))), // Excel: Zoom; opens the Zoom dialog
|
||||
("WR", Entry::Finish(Action::Command("view.ruler"))), // Excel: Ruler
|
||||
("WS", Entry::Finish(Action::Command("view.split"))), // Excel: Split
|
||||
("WT", Entry::Finish(Action::Pending("view.resetWindowPosition"))), // pending; Excel: Reset Window Position; reserved until the command lands
|
||||
("WU", Entry::Finish(Action::Command("view.unhideWindow"))), // Excel: Unhide Window
|
||||
("WVC", Entry::Finish(Action::Pending("view.focusCell"))), // pending; Excel: Focus Cell Menu; Focus Cell; reserved until the command lands
|
||||
("WVF", Entry::Finish(Action::Pending("view.formulaBar"))), // pending; Excel: Toggle Formula Bar; Toggle Formula Bar; reserved until the command lands
|
||||
("WVH", Entry::Finish(Action::Command("view.headings"))), // Excel: Toggle Headings; containment(Headings)
|
||||
("WVF", Entry::Finish(Action::Control("view.formulaBar"))), // Excel: Toggle Formula Bar; toggles the formula bar
|
||||
("WVH", Entry::Finish(Action::Command("view.headings"))), // Excel: Toggle Headings; containment(Headings)
|
||||
("WVL", Entry::Finish(Action::Pending("view.synchronousScrolling"))), // pending; Excel: Synchronous Scrolling; Synchronous Scrolling; reserved until the command lands
|
||||
("WVN", Entry::Finish(Action::Pending("view.navigation"))), // pending; Excel: Navigation; Navigation pane; reserved until the command lands
|
||||
("WVR", Entry::Finish(Action::Command("view.ruler"))), // Excel: Ruler
|
||||
|
||||
@@ -445,10 +445,10 @@
|
||||
},
|
||||
{
|
||||
"seq": "HFC",
|
||||
"kind": "command",
|
||||
"kind": "control",
|
||||
"target": "home.fontColor",
|
||||
"excel": "Font Color",
|
||||
"note": ""
|
||||
"note": "opens the font-color palette"
|
||||
},
|
||||
{
|
||||
"seq": "HFDC",
|
||||
@@ -459,15 +459,15 @@
|
||||
},
|
||||
{
|
||||
"seq": "HFDF",
|
||||
"kind": "command",
|
||||
"target": "edit.find",
|
||||
"kind": "dialog",
|
||||
"target": "find",
|
||||
"excel": "Find",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"seq": "HFDG",
|
||||
"kind": "command",
|
||||
"target": "edit.goTo",
|
||||
"kind": "dialog",
|
||||
"target": "goTo",
|
||||
"excel": "Go To",
|
||||
"note": ""
|
||||
},
|
||||
@@ -495,10 +495,10 @@
|
||||
},
|
||||
{
|
||||
"seq": "HFDR",
|
||||
"kind": "command",
|
||||
"target": "edit.replace",
|
||||
"kind": "dialog",
|
||||
"target": "find",
|
||||
"excel": "Replace",
|
||||
"note": ""
|
||||
"note": "opens Find and Replace"
|
||||
},
|
||||
{
|
||||
"seq": "HFDS",
|
||||
@@ -509,10 +509,10 @@
|
||||
},
|
||||
{
|
||||
"seq": "HFF",
|
||||
"kind": "command",
|
||||
"kind": "control",
|
||||
"target": "home.fontName",
|
||||
"excel": "Font Selection",
|
||||
"note": ""
|
||||
"note": "opens the font dropdown"
|
||||
},
|
||||
{
|
||||
"seq": "HFG",
|
||||
@@ -593,10 +593,10 @@
|
||||
},
|
||||
{
|
||||
"seq": "HFM",
|
||||
"kind": "command",
|
||||
"target": "home.numberFormat",
|
||||
"kind": "dialog",
|
||||
"target": "formatCells",
|
||||
"excel": "More Number Formats",
|
||||
"note": "containment(Number Format)"
|
||||
"note": "More Number Formats opens Format Cells"
|
||||
},
|
||||
{
|
||||
"seq": "HFP",
|
||||
@@ -629,17 +629,17 @@
|
||||
},
|
||||
{
|
||||
"seq": "HFS",
|
||||
"kind": "command",
|
||||
"kind": "control",
|
||||
"target": "home.fontSize",
|
||||
"excel": "Font Size",
|
||||
"note": ""
|
||||
"note": "opens the font-size dropdown"
|
||||
},
|
||||
{
|
||||
"seq": "HH",
|
||||
"kind": "command",
|
||||
"kind": "control",
|
||||
"target": "home.fillColor",
|
||||
"excel": "Fill Color",
|
||||
"note": ""
|
||||
"note": "opens the fill-color palette"
|
||||
},
|
||||
{
|
||||
"seq": "HIC",
|
||||
@@ -800,10 +800,10 @@
|
||||
},
|
||||
{
|
||||
"seq": "HN",
|
||||
"kind": "command",
|
||||
"kind": "control",
|
||||
"target": "home.numberFormat",
|
||||
"excel": "Number Format",
|
||||
"note": ""
|
||||
"note": "opens the number-format dropdown"
|
||||
},
|
||||
{
|
||||
"seq": "HOA",
|
||||
@@ -814,8 +814,8 @@
|
||||
},
|
||||
{
|
||||
"seq": "HOD",
|
||||
"kind": "command",
|
||||
"target": "home.columnWidth",
|
||||
"kind": "dialog",
|
||||
"target": "defaultWidth",
|
||||
"excel": "Default Column Width",
|
||||
"note": "containment(Column Width)"
|
||||
},
|
||||
@@ -828,8 +828,8 @@
|
||||
},
|
||||
{
|
||||
"seq": "HOH",
|
||||
"kind": "command",
|
||||
"target": "home.rowHeight",
|
||||
"kind": "dialog",
|
||||
"target": "rowHeight",
|
||||
"excel": "Row Height",
|
||||
"note": ""
|
||||
},
|
||||
@@ -842,8 +842,8 @@
|
||||
},
|
||||
{
|
||||
"seq": "HOM",
|
||||
"kind": "command",
|
||||
"target": "sheet.move",
|
||||
"kind": "dialog",
|
||||
"target": "moveSheet",
|
||||
"excel": "Move or Copy Sheet",
|
||||
"note": ""
|
||||
},
|
||||
@@ -856,8 +856,8 @@
|
||||
},
|
||||
{
|
||||
"seq": "HOR",
|
||||
"kind": "command",
|
||||
"target": "sheet.rename",
|
||||
"kind": "dialog",
|
||||
"target": "renameSheet",
|
||||
"excel": "Rename Sheet",
|
||||
"note": ""
|
||||
},
|
||||
@@ -912,8 +912,8 @@
|
||||
},
|
||||
{
|
||||
"seq": "HOW",
|
||||
"kind": "command",
|
||||
"target": "home.columnWidth",
|
||||
"kind": "dialog",
|
||||
"target": "columnWidth",
|
||||
"excel": "Column Width",
|
||||
"note": ""
|
||||
},
|
||||
@@ -1018,124 +1018,19 @@
|
||||
"excel": "Paste",
|
||||
"note": "group level:"
|
||||
},
|
||||
{
|
||||
"seq": "HVA",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Values and Number Formatting",
|
||||
"note": "paste variant, params TBD: what=TBD(values+numfmt)"
|
||||
},
|
||||
{
|
||||
"seq": "HVB",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste No Borders",
|
||||
"note": "paste variant, params TBD: what=TBD(no-borders)"
|
||||
},
|
||||
{
|
||||
"seq": "HVE",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Values and Source Formatting",
|
||||
"note": "paste variant, params TBD: what=TBD(values+src-fmt)"
|
||||
},
|
||||
{
|
||||
"seq": "HVF",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Formula",
|
||||
"note": "paste-variant TBD"
|
||||
},
|
||||
{
|
||||
"seq": "HVI",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Linked Picture",
|
||||
"note": "what=TBD(linked-picture)"
|
||||
},
|
||||
{
|
||||
"seq": "HVK",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Keep Source Formatting",
|
||||
"note": "paste variant, params TBD: what=TBD(keep-source)"
|
||||
},
|
||||
{
|
||||
"seq": "HVM",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Destination Formatting",
|
||||
"note": "paste variant, params TBD: what=TBD(dest)"
|
||||
},
|
||||
{
|
||||
"seq": "HVN",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Link",
|
||||
"note": "paste variant, params TBD: what=TBD(link)"
|
||||
},
|
||||
{
|
||||
"seq": "HVO",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Formula and Formatting",
|
||||
"note": "paste variant, params TBD: what=TBD(formula+formatting)"
|
||||
},
|
||||
{
|
||||
"seq": "HVP",
|
||||
"kind": "command",
|
||||
"target": "edit.paste",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"seq": "HVR",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Formatting",
|
||||
"note": "paste variant, params TBD: what=formats"
|
||||
},
|
||||
{
|
||||
"seq": "HVT",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Transpose",
|
||||
"note": "paste variant, params TBD: what=transpose"
|
||||
},
|
||||
{
|
||||
"seq": "HVU",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Picture",
|
||||
"note": "what=TBD(picture)"
|
||||
},
|
||||
{
|
||||
"seq": "HVV",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Values",
|
||||
"note": "paste variant, params TBD: what=values"
|
||||
},
|
||||
{
|
||||
"seq": "HVW",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"excel": "Paste Keep Source Column Widths",
|
||||
"note": "paste variant, params TBD: what=TBD(col-widths)"
|
||||
},
|
||||
{
|
||||
"seq": "HW",
|
||||
"kind": "continue",
|
||||
"excel": "Wrap Text Menu",
|
||||
"note": "group level: containment(Wrap Text)"
|
||||
},
|
||||
{
|
||||
"seq": "HWS",
|
||||
"kind": "command",
|
||||
"target": "home.shrinkToFit",
|
||||
"excel": "Shrink to Fit",
|
||||
"note": "landed in the catalog; live since T7 probe"
|
||||
},
|
||||
{
|
||||
"seq": "HWW",
|
||||
"kind": "command",
|
||||
@@ -1440,22 +1335,22 @@
|
||||
},
|
||||
{
|
||||
"seq": "NZKSL",
|
||||
"kind": "command",
|
||||
"target": "insert.sparkline",
|
||||
"kind": "dialog",
|
||||
"target": "sparkline",
|
||||
"excel": "Line Sparkline",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"seq": "NZKSO",
|
||||
"kind": "command",
|
||||
"target": "insert.sparkline",
|
||||
"kind": "dialog",
|
||||
"target": "sparkline",
|
||||
"excel": "Column Sparkline",
|
||||
"note": ""
|
||||
},
|
||||
{
|
||||
"seq": "NZKSW",
|
||||
"kind": "command",
|
||||
"target": "insert.sparkline",
|
||||
"kind": "dialog",
|
||||
"target": "sparkline",
|
||||
"excel": "Win/Loss Sparkline",
|
||||
"note": ""
|
||||
},
|
||||
@@ -1867,9 +1762,10 @@
|
||||
{
|
||||
"seq": "MSP",
|
||||
"kind": "command",
|
||||
"target": "edit.pasteSpecial",
|
||||
"target": "formulas.pasteName",
|
||||
"excel": "Paste Names",
|
||||
"note": "paste-variant TBD"
|
||||
"note": "reserved until the command lands",
|
||||
"pending": true
|
||||
},
|
||||
{
|
||||
"seq": "MT",
|
||||
@@ -1934,8 +1830,8 @@
|
||||
},
|
||||
{
|
||||
"seq": "MV",
|
||||
"kind": "command",
|
||||
"target": "formulas.evaluateFormula",
|
||||
"kind": "dialog",
|
||||
"target": "evaluateFormula",
|
||||
"excel": "Evaluate Formula",
|
||||
"note": ""
|
||||
},
|
||||
@@ -2355,8 +2251,8 @@
|
||||
},
|
||||
{
|
||||
"seq": "AWG",
|
||||
"kind": "command",
|
||||
"target": "data.goalSeek",
|
||||
"kind": "dialog",
|
||||
"target": "goalSeek",
|
||||
"excel": "Goal Seek",
|
||||
"note": ""
|
||||
},
|
||||
@@ -2572,10 +2468,10 @@
|
||||
},
|
||||
{
|
||||
"seq": "WJ",
|
||||
"kind": "command",
|
||||
"kind": "control",
|
||||
"target": "view.zoom",
|
||||
"excel": "Zoom To 100%",
|
||||
"note": ""
|
||||
"note": "runs the 100% zoom handler (percent param)"
|
||||
},
|
||||
{
|
||||
"seq": "WL",
|
||||
@@ -2620,10 +2516,10 @@
|
||||
},
|
||||
{
|
||||
"seq": "WQ",
|
||||
"kind": "command",
|
||||
"target": "view.zoom",
|
||||
"kind": "dialog",
|
||||
"target": "zoom",
|
||||
"excel": "Zoom",
|
||||
"note": ""
|
||||
"note": "opens the Zoom dialog"
|
||||
},
|
||||
{
|
||||
"seq": "WR",
|
||||
@@ -2664,11 +2560,10 @@
|
||||
},
|
||||
{
|
||||
"seq": "WVF",
|
||||
"kind": "command",
|
||||
"kind": "control",
|
||||
"target": "view.formulaBar",
|
||||
"pending": true,
|
||||
"excel": "Toggle Formula Bar",
|
||||
"note": "Toggle Formula Bar; reserved until the command lands"
|
||||
"note": "toggles the formula bar"
|
||||
},
|
||||
{
|
||||
"seq": "WVH",
|
||||
|
||||
@@ -57,6 +57,10 @@ pub struct UiState {
|
||||
/// Motion in the UI: the selection outline glides to a new selection (View ▸ Animations).
|
||||
pub animations: bool,
|
||||
pub recent: Vec<String>,
|
||||
/// One-shot keytip request for a ribbon control: set when a keytip action resolves to
|
||||
/// [`keytips::Action::Control`], consumed (and cleared) by the control's render site.
|
||||
#[serde(skip)]
|
||||
pub keytip_invoke: 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
|
||||
@@ -95,6 +99,7 @@ impl Default for UiState {
|
||||
status_bar: true,
|
||||
animations: true,
|
||||
recent: vec![],
|
||||
keytip_invoke: None,
|
||||
language: i18n::Language::En,
|
||||
interface_scale: 100,
|
||||
}
|
||||
@@ -821,6 +826,9 @@ impl SheetApp {
|
||||
|
||||
/// Per-frame logic (control channel, screenshots). Call before `ui`.
|
||||
pub fn logic(&mut self, ctx: &egui::Context) {
|
||||
// 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.
|
||||
self.ui.keytip_invoke = None;
|
||||
if self.fonts_han.is_some_and(|han| han != theme::HanOrder::of(self.ui.language)) {
|
||||
self.fonts_ready = false;
|
||||
self.fonts_set = false;
|
||||
@@ -1022,6 +1030,10 @@ impl SheetApp {
|
||||
self.run_or_alert(id, json!({}));
|
||||
}
|
||||
}
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,6 +343,21 @@ fn anchor_keytip(app: &SheetApp, ui: &Ui, response: &egui::Response, targets: &[
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume a one-shot keytip request for control `id` (set by `Action::Control`).
|
||||
fn invoke(app: &mut SheetApp, id: &str) -> bool {
|
||||
if app.ui.keytip_invoke == Some(id) {
|
||||
app.ui.keytip_invoke = None;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Force-open command for a popup: either a keytip invoke (open it) or a click (toggle).
|
||||
fn popup_command(forced: bool, clicked: bool) -> Option<egui::SetOpenCommand> {
|
||||
if forced { Some(egui::SetOpenCommand::Bool(true)) } else { clicked.then_some(egui::SetOpenCommand::Toggle) }
|
||||
}
|
||||
|
||||
fn menu_items(app: &mut SheetApp, ui: &mut Ui, items: &[(&str, &str, serde_json::Value)]) {
|
||||
for (label, id, p) in items {
|
||||
if *label == "-" {
|
||||
@@ -534,7 +549,8 @@ fn home(app: &mut SheetApp, ui: &mut Ui) {
|
||||
if f {
|
||||
act(app, "home.fillColor", json!({"color": fill}));
|
||||
}
|
||||
egui::Popup::menu(&fa).show(|ui| {
|
||||
let forced = invoke(app, "home.fillColor");
|
||||
egui::Popup::menu(&fa).open_memory(popup_command(forced, fa.clicked())).show(|ui| {
|
||||
if let Some(c) = color_palette(ui, &theme_colors(app), "No Fill") {
|
||||
if c != "none" {
|
||||
app.grid.last_fill = c.clone();
|
||||
@@ -550,7 +566,8 @@ fn home(app: &mut SheetApp, ui: &mut Ui) {
|
||||
if c {
|
||||
act(app, "home.fontColor", json!({"color": fc}));
|
||||
}
|
||||
egui::Popup::menu(&ca).show(|ui| {
|
||||
let forced = invoke(app, "home.fontColor");
|
||||
egui::Popup::menu(&ca).open_memory(popup_command(forced, ca.clicked())).show(|ui| {
|
||||
if let Some(c) = color_palette(ui, &theme_colors(app), "Automatic") {
|
||||
if c != "none" {
|
||||
app.grid.last_font_color = c.clone();
|
||||
@@ -929,16 +946,15 @@ fn home(app: &mut SheetApp, ui: &mut Ui) {
|
||||
fn font_combo(app: &mut SheetApp, ui: &mut Ui, st: &Style) {
|
||||
let mut name = st.font.name.clone();
|
||||
let before = name.clone();
|
||||
let resp = egui::ComboBox::from_id_salt("font_name").width(150.0).selected_text(egui::RichText::new(&name).font(theme::ui_font(12.5))).show_ui(
|
||||
ui,
|
||||
|ui| {
|
||||
for f in FONTS {
|
||||
let fam = theme::cell_family(f, false, false);
|
||||
ui.selectable_value(&mut name, f.to_string(), egui::RichText::new(*f).font(egui::FontId::new(14.0, fam)));
|
||||
}
|
||||
},
|
||||
);
|
||||
control_keytip(app, ui, &resp.response, "home.fontName");
|
||||
let resp = ui.add_sized([150.0, 24.0], egui::Button::new(egui::RichText::new(&name).font(theme::ui_font(12.5))));
|
||||
control_keytip(app, ui, &resp, "home.fontName");
|
||||
let forced = invoke(app, "home.fontName");
|
||||
egui::Popup::menu(&resp).open_memory(popup_command(forced, resp.clicked())).show(|ui| {
|
||||
for f in FONTS {
|
||||
let fam = theme::cell_family(f, false, false);
|
||||
ui.selectable_value(&mut name, f.to_string(), egui::RichText::new(*f).font(egui::FontId::new(14.0, fam)));
|
||||
}
|
||||
});
|
||||
if name != before {
|
||||
act(app, "home.fontName", json!({"name": name}));
|
||||
}
|
||||
@@ -968,15 +984,14 @@ fn size_combo(app: &mut SheetApp, ui: &mut Ui, st: &Style) {
|
||||
let mut size = st.font.size;
|
||||
let before = size;
|
||||
let label = if size.fract() == 0.0 { format!("{}", size as i32) } else { format!("{size}") };
|
||||
let resp = egui::ComboBox::from_id_salt("font_size").width(52.0).selected_text(egui::RichText::new(label).font(theme::ui_font(12.5))).show_ui(
|
||||
ui,
|
||||
|ui| {
|
||||
for s in [8.0, 9.0, 10.0, 11.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, 36.0, 48.0, 72.0] {
|
||||
ui.selectable_value(&mut size, s, format!("{}", s as i32));
|
||||
}
|
||||
},
|
||||
);
|
||||
control_keytip(app, ui, &resp.response, "home.fontSize");
|
||||
let resp = ui.add_sized([52.0, 24.0], egui::Button::new(egui::RichText::new(label).font(theme::ui_font(12.5))));
|
||||
control_keytip(app, ui, &resp, "home.fontSize");
|
||||
let forced = invoke(app, "home.fontSize");
|
||||
egui::Popup::menu(&resp).open_memory(popup_command(forced, resp.clicked())).show(|ui| {
|
||||
for s in [8.0, 9.0, 10.0, 11.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0, 36.0, 48.0, 72.0] {
|
||||
ui.selectable_value(&mut size, s, format!("{}", s as i32));
|
||||
}
|
||||
});
|
||||
if size != before {
|
||||
act(app, "home.fontSize", json!({"size": size}));
|
||||
}
|
||||
@@ -989,26 +1004,24 @@ fn number_combo(app: &mut SheetApp, ui: &mut Ui, st: &Style) {
|
||||
let lang = app.ui.language;
|
||||
let sample = app.session.active().and_then(|d| d.wb.active().map(|sh| sh.value(d.selection.active))).unwrap_or_default();
|
||||
let mut picked: Option<&str> = None;
|
||||
let resp = egui::ComboBox::from_id_salt("number_format")
|
||||
.width(150.0)
|
||||
.selected_text(egui::RichText::new(lang.tr(¤t)).font(theme::ui_font(12.5)))
|
||||
.show_ui(ui, |ui| {
|
||||
for name in
|
||||
["General", "Number", "Currency", "Accounting", "Short Date", "Long Date", "Time", "Percentage", "Fraction", "Scientific", "Text"]
|
||||
{
|
||||
let code = gridcraft_engine::cmd::format::format_code_for(name);
|
||||
let preview = app.session.active().map(|d| gridcraft_engine::display::format(&sample, code, &d.wb).text).unwrap_or_default();
|
||||
let r = ui.add(egui::Button::selectable(current == name, format!("{:<12} {preview}", lang.tr(name))).min_size(vec2(240.0, 22.0)));
|
||||
if r.clicked() {
|
||||
picked = Some(name);
|
||||
}
|
||||
let resp = ui.add_sized([150.0, 24.0], egui::Button::new(egui::RichText::new(lang.tr(¤t)).font(theme::ui_font(12.5))));
|
||||
control_keytip(app, ui, &resp, "home.numberFormat");
|
||||
let forced = invoke(app, "home.numberFormat");
|
||||
egui::Popup::menu(&resp).open_memory(popup_command(forced, resp.clicked())).show(|ui| {
|
||||
for name in ["General", "Number", "Currency", "Accounting", "Short Date", "Long Date", "Time", "Percentage", "Fraction", "Scientific", "Text"]
|
||||
{
|
||||
let code = gridcraft_engine::cmd::format::format_code_for(name);
|
||||
let preview = app.session.active().map(|d| gridcraft_engine::display::format(&sample, code, &d.wb).text).unwrap_or_default();
|
||||
let r = ui.add(egui::Button::selectable(current == name, format!("{:<12} {preview}", lang.tr(name))).min_size(vec2(240.0, 22.0)));
|
||||
if r.clicked() {
|
||||
picked = Some(name);
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button(lang.tr("More Number Formats…")).clicked() {
|
||||
picked = Some("__more");
|
||||
}
|
||||
});
|
||||
anchor_keytip(app, ui, &resp.response, &["home.numberFormat"]);
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button(lang.tr("More Number Formats…")).clicked() {
|
||||
picked = Some("__more");
|
||||
}
|
||||
});
|
||||
match picked {
|
||||
Some("__more") => app.open_dialog("formatCells", json!({"tab": "Number"})),
|
||||
Some(n) => act(app, "home.numberFormat", json!({"format": n})),
|
||||
@@ -1657,7 +1670,7 @@ fn formulas(app: &mut SheetApp, ui: &mut Ui) {
|
||||
app.open_dialog("defineName", json!({}));
|
||||
}
|
||||
let use_in = small_button(ui, Icon::Function, "Use in Formula", "Use in Formula", true);
|
||||
anchor_keytip(app, ui, &use_in, &["edit.pasteSpecial"]);
|
||||
anchor_keytip(app, ui, &use_in, &["formulas.useInFormula"]);
|
||||
egui::Popup::menu(&use_in).show(|ui| {
|
||||
let names: Vec<String> = app.session.active().map(|d| d.wb.names.iter().map(|n| n.name.clone()).collect()).unwrap_or_default();
|
||||
for n in names {
|
||||
@@ -1965,7 +1978,9 @@ fn view(app: &mut SheetApp, ui: &mut Ui) {
|
||||
(Icon::PageBreak, "Page Break\nPreview", "view.pageBreakPreview"),
|
||||
(Icon::PageLayout, "Page\nLayout", "view.pageLayout"),
|
||||
] {
|
||||
if big_button(ui, icon, label, label, false).clicked() {
|
||||
let __k = big_button(ui, icon, label, label, false);
|
||||
control_keytip(app, ui, &__k, cmd);
|
||||
if __k.clicked() {
|
||||
act(app, cmd, json!({}));
|
||||
}
|
||||
}
|
||||
@@ -1977,19 +1992,27 @@ fn view(app: &mut SheetApp, ui: &mut Ui) {
|
||||
ui.vertical(|ui| {
|
||||
// Rulers exist only in Page Layout view.
|
||||
let mut ruler = !app.session.hide_ruler;
|
||||
if ui.add_enabled(page_layout, egui::Checkbox::new(&mut ruler, lang.tr("Ruler"))).changed() {
|
||||
let ruler_resp = ui.add_enabled(page_layout, egui::Checkbox::new(&mut ruler, lang.tr("Ruler")));
|
||||
control_keytip(app, ui, &ruler_resp, "view.ruler");
|
||||
if ruler_resp.changed() {
|
||||
act(app, "view.ruler", json!({"on": ruler}));
|
||||
}
|
||||
let mut fb = app.session.prefs.formula_bar;
|
||||
if ui.checkbox(&mut fb, lang.tr("Formula Bar")).changed() {
|
||||
act(app, "view.formulaBar", json!({"on": fb}));
|
||||
let resp = ui.checkbox(&mut fb, lang.tr("Formula Bar"));
|
||||
control_keytip(app, ui, &resp, "view.formulaBar");
|
||||
if resp.changed() || invoke(app, "view.formulaBar") {
|
||||
act(app, "view.formulaBar", json!({"on": !fb}));
|
||||
}
|
||||
let mut g = s.0;
|
||||
if ui.checkbox(&mut g, lang.tr("Gridlines")).changed() {
|
||||
let resp = ui.checkbox(&mut g, lang.tr("Gridlines"));
|
||||
control_keytip(app, ui, &resp, "view.gridlines");
|
||||
if resp.changed() {
|
||||
act(app, "view.gridlines", json!({"on": g}));
|
||||
}
|
||||
let mut h = s.1;
|
||||
if ui.checkbox(&mut h, lang.tr("Headings")).changed() {
|
||||
let resp = ui.checkbox(&mut h, lang.tr("Headings"));
|
||||
control_keytip(app, ui, &resp, "view.headings");
|
||||
if resp.changed() {
|
||||
act(app, "view.headings", json!({"on": h}));
|
||||
}
|
||||
});
|
||||
@@ -2001,7 +2024,7 @@ fn view(app: &mut SheetApp, ui: &mut Ui) {
|
||||
}
|
||||
let __k = big_button(ui, Icon::Search, "100%", "Zoom to 100%", false);
|
||||
control_keytip(app, ui, &__k, "view.zoom");
|
||||
if __k.clicked() {
|
||||
if __k.clicked() || invoke(app, "view.zoom") {
|
||||
act(app, "view.zoom", json!({"percent": 100}));
|
||||
}
|
||||
let __k = big_button(ui, Icon::Zoom, "Zoom to\nSelection", "Zoom to Selection", false);
|
||||
|
||||
@@ -433,6 +433,29 @@ fn pending_sequences_go_live_when_the_command_lands() {
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn control_keytips_open_dropdowns_and_run_handlers() {
|
||||
// HFF routes to the font control: its dropdown opens (popup items queryable).
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, &[Key::H, Key::F, Key::F], false);
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "HFF must not raise an error box");
|
||||
// "Calibri" appears twice: once on the combo button (the current font) and
|
||||
// once inside the opened dropdown — a single node would mean it stayed closed.
|
||||
assert!(h.query_all_by_label("Calibri").count() >= 2, "HFF must open the font dropdown");
|
||||
|
||||
// WVF toggles the formula bar: pure UI state, no engine command exists.
|
||||
let mut h = harness(blank());
|
||||
assert!(h.state().ui.formula_bar);
|
||||
sequence(&mut h, &[Key::W, Key::V, Key::F], false);
|
||||
assert!(!h.state().ui.formula_bar, "WVF must toggle the formula bar off");
|
||||
|
||||
// WJ runs the 100% handler with its percent param ({} would raise "missing percent").
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, &[Key::W, Key::J], false);
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "WJ must run view.zoom with the percent param");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_sequence_terminates_without_panicking() {
|
||||
use gridcraft_ui_egui::keytips::keytip_table::SEQUENCES;
|
||||
|
||||
@@ -543,7 +543,8 @@ fn keytip_hem_() {
|
||||
fn keytip_hfc_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HFC", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HFC must invoke home.fontColor and surface its missing-params error");
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "HFC must route to its control without an error box");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -557,14 +558,14 @@ fn keytip_hfdc_() {
|
||||
fn keytip_hfdf_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HFDF", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HFDF must invoke edit.find and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "HFDF must open the find dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hfdg_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HFDG", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HFDG must invoke edit.goTo and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "HFDG must open the goTo dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -595,7 +596,7 @@ fn keytip_hfdp_() {
|
||||
fn keytip_hfdr_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HFDR", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HFDR must invoke edit.replace and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "HFDR must open the find dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -609,7 +610,8 @@ fn keytip_hfds_() {
|
||||
fn keytip_hff_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HFF", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HFF must invoke home.fontName and surface its missing-params error");
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "HFF must route to its control without an error box");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -696,7 +698,7 @@ fn keytip_hfk_() {
|
||||
fn keytip_hfm_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HFM", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HFM must invoke home.numberFormat and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "HFM must open the formatCells dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -737,14 +739,16 @@ fn keytip_hfqo_() {
|
||||
fn keytip_hfs_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HFS", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HFS must invoke home.fontSize and surface its missing-params error");
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "HFS must route to its control without an error box");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hh_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HH", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HH must invoke home.fillColor and surface its missing-params error");
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "HH must route to its control without an error box");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -912,7 +916,8 @@ fn keytip_hmu_() {
|
||||
fn keytip_hn_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HN", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HN must invoke home.numberFormat and surface its missing-params error");
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "HN must route to its control without an error box");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -926,7 +931,7 @@ fn keytip_hoa_() {
|
||||
fn keytip_hod_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HOD", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HOD must invoke home.columnWidth and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "HOD must open the defaultWidth dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -940,7 +945,7 @@ fn keytip_hoe_() {
|
||||
fn keytip_hoh_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HOH", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HOH must invoke home.rowHeight and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "HOH must open the rowHeight dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -954,7 +959,7 @@ fn keytip_hol_() {
|
||||
fn keytip_hom_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HOM", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HOM must invoke sheet.move and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "HOM must open the moveSheet dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -968,7 +973,7 @@ fn keytip_hop_() {
|
||||
fn keytip_hor_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HOR", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HOR must invoke sheet.rename and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "HOR must open the renameSheet dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1024,7 +1029,7 @@ fn keytip_hous_() {
|
||||
fn keytip_how_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HOW", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HOW must invoke home.columnWidth and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "HOW must open the columnWidth dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1132,109 +1137,11 @@ fn keytip_hv_() {
|
||||
assert_eq!(h.state().keytips.prefix(), Some("HV"), "HV must stay armed waiting for more input");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hva_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVA", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVA must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvb_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVB", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVB must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hve_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVE", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVE must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvf_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVF", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVF must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvi_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVI", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVI must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvk_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVK", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVK must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvm_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVM", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVM must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvn_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVN", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVN must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvo_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVO", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVO must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvp_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVP", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVP must invoke edit.paste and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvr_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVR", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVR must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvt_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVT", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVT must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvu_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVU", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVU must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvv_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVV", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVV must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hvw_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HVW", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVW must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "HVP must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1244,13 +1151,6 @@ fn keytip_hw_() {
|
||||
assert_eq!(h.state().keytips.prefix(), Some("HW"), "HW must stay armed waiting for more input");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hws_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "HWS", false);
|
||||
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.shrinkToFit"), "HWS must journal home.shrinkToFit");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_hww_() {
|
||||
let mut h = harness(blank());
|
||||
@@ -1580,21 +1480,21 @@ fn keytip_nzk_() {
|
||||
fn keytip_nzksl_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "NZKSL", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "NZKSL must invoke insert.sparkline and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "NZKSL must open the sparkline dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_nzkso_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "NZKSO", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "NZKSO must invoke insert.sparkline and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "NZKSO must open the sparkline dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keytip_nzksw_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "NZKSW", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "NZKSW must invoke insert.sparkline and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "NZKSW must open the sparkline dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2024,7 +1924,10 @@ fn keytip_ms_() {
|
||||
fn keytip_msp_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "MSP", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "MSP must invoke edit.pasteSpecial and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_none(), "MSP must not open a dialog");
|
||||
assert!(h.state().message.is_none(), "MSP must not open a message box");
|
||||
assert!(h.state().toast.is_none(), "MSP must not raise a toast");
|
||||
assert!(h.state().session.journal.is_empty(), "MSP must not journal");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2094,7 +1997,7 @@ fn keytip_mus_() {
|
||||
fn keytip_mv_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "MV", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "MV must invoke formulas.evaluateFormula and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "MV must open the evaluateFormula dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2567,7 +2470,7 @@ fn keytip_aw_() {
|
||||
fn keytip_awg_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "AWG", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "AWG must invoke data.goalSeek and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "AWG must open the goalSeek dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2804,7 +2707,8 @@ fn keytip_wi_() {
|
||||
fn keytip_wj_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "WJ", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "WJ must invoke view.zoom and surface its missing-params error");
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "WJ must route to its control without an error box");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2853,7 +2757,7 @@ fn keytip_wp_() {
|
||||
fn keytip_wq_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "WQ", false);
|
||||
assert!(h.state().message.is_some() || h.state().toast.is_some(), "WQ must invoke view.zoom and surface its missing-params error");
|
||||
assert!(h.state().dialog.is_some(), "WQ must open the zoom dialog");
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2901,10 +2805,8 @@ fn keytip_wvc_() {
|
||||
fn keytip_wvf_() {
|
||||
let mut h = harness(blank());
|
||||
sequence(&mut h, "WVF", false);
|
||||
assert!(h.state().dialog.is_none(), "WVF must not open a dialog");
|
||||
assert!(h.state().message.is_none(), "WVF must not open a message box");
|
||||
assert!(h.state().toast.is_none(), "WVF must not raise a toast");
|
||||
assert!(h.state().session.journal.is_empty(), "WVF must not journal");
|
||||
assert!(h.state().message.is_none() && h.state().toast.is_none(), "WVF must route to its control without an error box");
|
||||
assert_eq!(h.state().keytips.prefix(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -3131,7 +3033,7 @@ fn keytip_9_() {
|
||||
|
||||
#[test]
|
||||
fn generated_test_count_matches_the_table() {
|
||||
const EXPECTED: usize = 412;
|
||||
const EXPECTED: usize = 397;
|
||||
assert_eq!(gridcraft_ui_egui::keytips::keytip_table::SEQUENCES.len(), EXPECTED);
|
||||
}
|
||||
|
||||
|
||||
@@ -138,7 +138,7 @@ fn parse_and_probe(source: &str) -> Result<(Vec<Entry>, Probe), String> {
|
||||
return Err(format!("{at}: tab target {target:?} is not one of {TABS:?}"));
|
||||
}
|
||||
}
|
||||
"command" | "dialog" => {
|
||||
"command" | "dialog" | "control" => {
|
||||
if target.is_empty() {
|
||||
return Err(format!("{at}: {kind} needs a `target`"));
|
||||
}
|
||||
@@ -148,10 +148,10 @@ fn parse_and_probe(source: &str) -> Result<(Vec<Entry>, Probe), String> {
|
||||
}
|
||||
// A terminal Finish entry on a strict prefix makes the longer sequence
|
||||
// unreachable (the prefix fires and clears first), e.g. HC (Copy) kills HCP.
|
||||
if kind == "command" || kind == "dialog" {
|
||||
if matches!(kind.as_str(), "command" | "dialog" | "control") {
|
||||
for cut in 1..seq.len() {
|
||||
let prefix = &seq[..cut];
|
||||
if entries.iter().any(|e| e.seq == prefix && (e.kind == "command" || e.kind == "dialog")) {
|
||||
if entries.iter().any(|e| e.seq == prefix && matches!(e.kind.as_str(), "command" | "dialog" | "control")) {
|
||||
return Err(format!(
|
||||
"{at}: seq {seq:?} is unreachable — {prefix:?} already fires first; the deeper item needs a menu (item-letter), not a trie sequence"
|
||||
));
|
||||
@@ -221,6 +221,7 @@ fn render_table(entries: &[Entry]) -> String {
|
||||
"command" if e.pending => format!("Entry::Finish(Action::Pending({:?}))", e.target),
|
||||
"command" => format!("Entry::Finish(Action::Command({:?}))", e.target),
|
||||
"dialog" => format!("Entry::Finish(Action::Dialog({:?}))", e.target),
|
||||
"control" => format!("Entry::Finish(Action::Control({:?}))", e.target),
|
||||
"continue" => "Entry::Continue".to_string(),
|
||||
// All kinds are validated during parse; anything else cannot occur.
|
||||
_ => String::new(),
|
||||
@@ -290,6 +291,10 @@ fn render_tests(entries: &[Entry], probe: &Probe) -> String {
|
||||
seq = e.seq,
|
||||
dialog = e.target,
|
||||
),
|
||||
"control" => format!(
|
||||
"sequence(&mut h, {seq:?}, false);\n assert!(h.state().message.is_none() && h.state().toast.is_none(), \"{seq} must route to its control without an error box\");\n assert_eq!(h.state().keytips.prefix(), None);",
|
||||
seq = e.seq,
|
||||
),
|
||||
"continue" => format!(
|
||||
"sequence(&mut h, {seq:?}, false);\n assert_eq!(h.state().keytips.prefix(), Some({seq:?}), \"{seq} must stay armed waiting for more input\");",
|
||||
seq = e.seq,
|
||||
|
||||
Reference in new issue
Block a user