Excel-style combo navigation (type to filter, arrows to move) and complete tab badges

- editable_combo: every dropdown keytip opens with a filter field focused;
  typing narrows the list, Up/Down move the highlight (wrapping), Enter or
  click applies, custom text applies where valid (font size). Fixes arrows
  and typing in ALT+H FF/FS/N.
- Audited all 54 painted ribbon ids against the table: real mismatches fixed
  (Data Sort/Text-to-Columns/Remove-Duplicates/Subtotal, Review Spelling/
  Accessibility/New Comment/Notes, View Zoom/Freeze, Formulas Insert Function/
  Name Manager/Define Name/Use-in-Formula/Error Checking/Evaluate, Insert Link/
  Comment/Header-Footer/Sparklines, Home Protect Sheet).
- keytips.json: dialog kind for real dialog openers; control kind for the 6
  chart-type buttons, 7 formula-category buttons, and Orientation (HFQ added,
  was missing); M-family restructured to Excel semantics (ML/MT/ME/MO/MG/MQ
  fire directly, MI1 opens Financial) with 14 unreachable children dropped;
  383 entries total.
This commit is contained in:
James 2026-10-11 22:01:46 +03:00
parent cbd9bb51c6
commit 7ba871c6f2
5 changed files with 406 additions and 499 deletions

View File

@ -64,7 +64,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("HBY", Entry::Finish(Action::Command("home.borders"))), // Excel: Line Style; Line Style (submenu TBD)
("HC", Entry::Finish(Action::Command("edit.copy"))), // Excel: Copy; fires immediately in Excel; deeper aliases dropped as unreachable
("HDC", Entry::Finish(Action::Command("home.deleteColumns"))), // Excel: Delete Columns; Delete Columns
("HDD", Entry::Finish(Action::Command("home.deleteCells"))), // Excel: Delete Cells
("HDD", Entry::Finish(Action::Dialog("deleteCells"))), // Excel: Delete Cells
("HDR", Entry::Finish(Action::Command("home.deleteRows"))), // Excel: Delete Rows; Delete Rows
("HDS", Entry::Finish(Action::Command("home.deleteSheet"))), // Excel: Delete Sheet
("HEA", Entry::Finish(Action::Command("edit.clearAll"))), // Excel: Clear All
@ -80,7 +80,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("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::Dialog("find"))), // Excel: Replace; opens Find and Replace
("HFDS", Entry::Finish(Action::Command("edit.goToSpecial"))), // Excel: Go To Special
("HFDS", Entry::Finish(Action::Dialog("goToSpecial"))), // Excel: Go To Special
("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)
@ -90,24 +90,22 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("HFIJ", Entry::Finish(Action::Command("home.justify"))), // Excel: Fill Justify; containment(Justify)
("HFIL", Entry::Finish(Action::Command("edit.fillLeft"))), // Excel: Fill Left
("HFIR", Entry::Finish(Action::Command("edit.fillRight"))), // Excel: Fill Right
("HFIS", Entry::Finish(Action::Command("edit.fillSeries"))), // Excel: Fill Series; containment(Series)
("HFIS", Entry::Finish(Action::Dialog("series"))), // 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::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::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
("HIR", Entry::Finish(Action::Command("home.insertRows"))), // Excel: Insert Rows; Insert Rows
("HIS", Entry::Finish(Action::Command("home.insertSheet"))), // Excel: Insert Sheet
("HJ", Entry::Continue), // Excel: Cell Styles; group level:
("HJN", Entry::Finish(Action::Command("home.newCellStyle"))), // Excel: New Cell Style
("HK", Entry::Finish(Action::Command("home.comma"))), // Excel: Comma Style
("HFQ", Entry::Finish(Action::Control("home.orientation"))), // Excel: Orientation Menu; group level: containment(Orientation)
("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::Dialog("insertCells"))), // Excel: Insert Copied Cells; Insert Cells (copied cells TBD)
("HII", Entry::Finish(Action::Dialog("insertCells"))), // Excel: Insert Cells
("HIR", Entry::Finish(Action::Command("home.insertRows"))), // Excel: Insert Rows; Insert Rows
("HIS", Entry::Finish(Action::Command("home.insertSheet"))), // Excel: Insert Sheet
("HJ", Entry::Continue), // Excel: Cell Styles; group level:
("HJN", Entry::Finish(Action::Command("home.newCellStyle"))), // Excel: New Cell Style
("HK", Entry::Finish(Action::Command("home.comma"))), // Excel: Comma Style
("HL", Entry::Continue), // Excel: Conditional Formatting; group level: containment(Conditional Formatting)
("HLC", Entry::Continue), // Excel: Clear Rules Menu; group level: containment(Clear Rules)
("HLCE", Entry::Finish(Action::Command("home.clearRules"))), // Excel: Clear Rules From Sheet; containment(Clear Rules)
@ -130,7 +128,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("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::Dialog("moveSheet"))), // Excel: Move or Copy Sheet
("HOP", Entry::Finish(Action::Command("review.protectSheet"))), // Excel: Protect Sheet
("HOP", Entry::Finish(Action::Dialog("protectSheet"))), // Excel: Protect 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
@ -159,33 +157,33 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("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)
("NC1", Entry::Finish(Action::Control("insert.chart.column"))), // 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)
("NCM", Entry::Finish(Action::Dialog("comment"))), // 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)
("ND", Entry::Finish(Action::Control("insert.chart.scatter"))), // 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
("NH1", Entry::Finish(Action::Dialog("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)
("NHI", Entry::Finish(Action::Control("insert.chart.hierarchy"))), // 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
("NI2", Entry::Finish(Action::Dialog("insertLink"))), // 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)
("NN1", Entry::Finish(Action::Control("insert.chart.line"))), // 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)
("NQ", Entry::Finish(Action::Control("insert.chart.pie"))), // 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::Control("insert.chart.statistic"))), // 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)
("NSF", Entry::Finish(Action::Pending("insert.slicer"))), // pending; Excel: Slicer; insert.slicer missing (gap #8); reserved until the command lands
@ -219,7 +217,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("PPH", Entry::Finish(Action::Command("pageLayout.printHeadings"))), // Excel: Print Headings
("PR", Entry::Continue), // Excel: Print Area; group level:
("PRS", Entry::Finish(Action::Command("pageLayout.printArea"))), // Excel: Set Print Area
("PSP", Entry::Finish(Action::Command("pageLayout.pageSetup"))), // Excel: Page Setup
("PSP", Entry::Finish(Action::Dialog("pageSetup"))), // Excel: Page Setup
("PSZ", Entry::Finish(Action::Command("pageLayout.size"))), // Excel: Size
("PTC", Entry::Finish(Action::Command("pageLayout.themeColors"))), // Excel: Theme Colors; containment(Colors)
("PTE", Entry::Finish(Action::Command("pageLayout.themeEffects"))), // Excel: Theme Effects; containment(Effects)
@ -234,55 +232,41 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("MB", Entry::Finish(Action::Command("formulas.calculateNow"))), // Excel: Calculate Now
("MC", Entry::Finish(Action::Command("formulas.createFromSelection"))), // Excel: Create From Selection
("MD", Entry::Finish(Action::Command("formulas.traceDependents"))), // Excel: Trace Dependents
("ME", Entry::Continue), // Excel: Date & Time Menu; group level: containment(Date & Time)
("MEF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Date & Time - Insert Function; containment(Insert Function)
("MF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Insert Function
("MG", Entry::Continue), // Excel: Math & Trig Menu; group level: containment(Math & Trig)
("MGF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Math & Trig - Insert Function; containment(Insert Function)
("ME", Entry::Finish(Action::Control("formulas.dateTime"))), // Excel: Date & Time Menu; group level: containment(Date & Time)
("MF", Entry::Finish(Action::Dialog("insertFunction"))), // Excel: Insert Function
("MG", Entry::Finish(Action::Control("formulas.mathTrig"))), // Excel: Math & Trig Menu; group level: containment(Math & Trig)
("MH", Entry::Finish(Action::Command("formulas.showFormulas"))), // Excel: Show Formulas
("MI", Entry::Continue), // Excel: Financial Menu; group level: containment(Financial)
("MI1", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Excel: Financial; first item of the dropdown
("MIF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Financial - Insert Function; containment(Insert Function)
("MI1", Entry::Finish(Action::Control("formulas.financial"))), // Excel: Excel: Financial; first item of the dropdown
("MJ", Entry::Finish(Action::Command("formulas.calculateSheet"))), // Excel: Calculate Sheet
("MK", Entry::Continue), // Excel: Error Checking Menu; group level: containment(Error Checking)
("MKK", Entry::Finish(Action::Command("formulas.errorChecking"))), // Excel: Error Checking
("ML", Entry::Continue), // Excel: Logical Menu; group level: containment(Logical)
("MLF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Logical - Insert Function; containment(Insert Function)
("MM", Entry::Finish(Action::Command("formulas.defineName"))), // Excel: Define Name
("MN", Entry::Finish(Action::Command("formulas.nameManager"))), // Excel: Name Manager
("MO", Entry::Continue), // Excel: Lookup & Reference Menu; group level: containment(Lookup & Reference)
("MOF", Entry::Finish(Action::Command("formulas.lookupReference"))), // Excel: Lookup & Reference - Insert Function; containment(Lookup & Reference)
("MP", Entry::Finish(Action::Command("formulas.tracePrecedents"))), // Excel: Trace Precedents
("MQ", Entry::Continue), // Excel: More Functions Menu; group level: containment(More Functions)
("MQCF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Cube - Insert Function; containment(Insert Function)
("MQEF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Engineering - Insert Function; containment(Insert Function)
("MQIF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Information - Insert Function; containment(Insert Function)
("MQOF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Compatibility - Insert Function; containment(Insert Function)
("MQSF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Statistical - Insert Function; containment(Insert Function)
("MQWF", Entry::Finish(Action::Command("formulas.insertFunction"))), // Excel: Web - Insert Function; containment(Insert Function)
("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::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:
("MUA", Entry::Finish(Action::Command("formulas.autoSum"))), // Excel: Average; AutoSum: Average (variant TBD)
("MUC", Entry::Finish(Action::Command("formulas.autoSum"))), // Excel: Count Numbers; AutoSum: Count (variant TBD)
("MUF", Entry::Finish(Action::Command("formulas.moreFunctions"))), // Excel: More Functions (AutoSum); containment(More Functions)
("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::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)
("MKK", Entry::Finish(Action::Dialog("errorChecking"))), // Excel: Error Checking
("ML", Entry::Finish(Action::Control("formulas.logical"))), // Excel: Logical Menu; group level: containment(Logical)
("MM", Entry::Finish(Action::Dialog("defineName"))), // Excel: Define Name
("MN", Entry::Finish(Action::Dialog("nameManager"))), // Excel: Name Manager
("MO", Entry::Finish(Action::Control("formulas.lookup"))), // Excel: Lookup & Reference Menu; group level: containment(Lookup & Reference)
("MP", Entry::Finish(Action::Command("formulas.tracePrecedents"))), // Excel: Trace Precedents
("MQ", Entry::Finish(Action::Control("formulas.moreFunctions"))), // Excel: More Functions Menu; group level: containment(More Functions)
("MS", Entry::Continue), // Excel: Use in Formula Menu; group level: containment(Use in Formula)
("MSP", Entry::Finish(Action::Pending("formulas.pasteName"))), // pending; Excel: Paste Names; reserved until the command lands
("MT", Entry::Finish(Action::Control("formulas.text"))), // Excel: Text Menu; group level: containment(Text)
("MU", Entry::Continue), // Excel: AutoSum Menu; group level:
("MUA", Entry::Finish(Action::Command("formulas.autoSum"))), // Excel: Average; AutoSum: Average (variant TBD)
("MUC", Entry::Finish(Action::Command("formulas.autoSum"))), // Excel: Count Numbers; AutoSum: Count (variant TBD)
("MUF", Entry::Finish(Action::Command("formulas.moreFunctions"))), // Excel: More Functions (AutoSum); containment(More Functions)
("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::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
("MXM", Entry::Finish(Action::Command("formulas.calculationOptions"))), // Excel: Manual Calculation; Calculation options: Manual
("MXP", Entry::Finish(Action::Command("formulas.calculationOptions"))), // Excel: Partial Calculation; Calculation options: Partial
("AB", Entry::Finish(Action::Command("data.subtotal"))), // Excel: Subtotal
("AB", Entry::Finish(Action::Dialog("subtotal"))), // Excel: Subtotal
("AC", Entry::Finish(Action::Command("data.clearFilter"))), // Excel: Clear Filter
("AD", Entry::Finish(Action::Command("data.consolidate"))), // Excel: Consolidate
("AE", Entry::Finish(Action::Command("data.textToColumns"))), // Excel: Text to Columns
("AE", Entry::Finish(Action::Dialog("textToColumns"))), // Excel: Text to Columns
("AF1", Entry::Continue), // Excel: Data from Picture Menu; group level: feature gap; reserved until the command lands
("AF1C", Entry::Finish(Action::Command("insert.picture"))), // Excel: Picture from Clipboard (source TBD)
("AF1P", Entry::Finish(Action::Command("insert.picture"))), // Excel: Picture from File (source TBD)
@ -299,7 +283,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("AH", Entry::Finish(Action::Command("data.hideDetail"))), // Excel: Hide Detail
("AJ", Entry::Finish(Action::Command("data.showDetail"))), // Excel: Show Detail
("AK", Entry::Finish(Action::Command("data.queriesConnections"))), // Excel: Edit Links; Edit Links (Queries & Connections);
("AM", Entry::Finish(Action::Command("data.removeDuplicates"))), // Excel: Remove Duplicates
("AM", Entry::Finish(Action::Dialog("removeDuplicates"))), // Excel: Remove Duplicates
("AO", Entry::Finish(Action::Command("data.queriesConnections"))), // Excel: Queries & Connections
("AP1", Entry::Finish(Action::Command("file.properties"))), // Excel: Properties
("APN", Entry::Continue), // Excel: Get Data (Power Query) Menu; group level: Power Query variants = GAP
@ -322,7 +306,7 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("ARS", Entry::Finish(Action::Pending("data.refreshStatus"))), // pending; Excel: Refresh Status; gap #12; reserved until the command lands
("ASA", Entry::Finish(Action::Command("data.sortAscending"))), // Excel: Sort A to Z
("ASD", Entry::Finish(Action::Command("data.sortDescending"))), // Excel: Sort Z to A
("ASS", Entry::Finish(Action::Command("data.sort"))), // Excel: Sort
("ASS", Entry::Finish(Action::Dialog("sort"))), // Excel: Sort
("AT", Entry::Finish(Action::Command("data.filter"))), // Excel: Filter
("AU", Entry::Continue), // Excel: Ungroup Menu; group level: containment(Ungroup)
("AUC", Entry::Finish(Action::Pending("data.clearOutline"))), // pending; Excel: Clear Outline; feature gap; reserved until the command lands
@ -337,18 +321,18 @@ pub const SEQUENCES: &[(&str, Entry)] = &[
("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
("AY", Entry::Finish(Action::Command("data.reapply"))), // Excel: Reapply Filter; containment(Reapply)
("RA1", Entry::Finish(Action::Command("review.checkAccessibility"))), // Excel: Check Accessibility
("RA1", Entry::Finish(Action::Dialog("accessibility"))), // Excel: Check Accessibility
("RA2", Entry::Finish(Action::Pending("review.showComments"))), // pending; Excel: Show All Comments; Show All Comments; reserved until the command lands
("RC", Entry::Finish(Action::Command("review.newComment"))), // Excel: New Comment
("RC", Entry::Finish(Action::Dialog("comment"))), // Excel: New Comment
("RD", Entry::Finish(Action::Command("review.deleteComment"))), // Excel: Delete Comment
("RG", Entry::Finish(Action::Pending("review.showChanges"))), // pending; Excel: Track Changes; reserved until the command lands
("RL", Entry::Finish(Action::Pending("review.translate"))), // pending; Excel: Translate; feature gap; reserved until the command lands
("RN", Entry::Finish(Action::Pending("review.nextComment"))), // pending; Excel: Next Comment; Next Comment; reserved until the command lands
("RO", Entry::Finish(Action::Command("review.protectWorkbook"))), // Excel: Protect and Share Workbook
("RPS", Entry::Finish(Action::Command("review.protectSheet"))), // Excel: Protect Sheet
("RPS", Entry::Finish(Action::Dialog("protectSheet"))), // Excel: Protect Sheet
("RPW", Entry::Finish(Action::Command("review.protectWorkbook"))), // Excel: Protect Workbook
("RRS", Entry::Finish(Action::Pending("review.smartLookup"))), // pending; Excel: Smart Lookup; feature gap; reserved until the command lands
("RS", Entry::Finish(Action::Command("review.spelling"))), // Excel: Spelling
("RS", Entry::Finish(Action::Dialog("spelling"))), // Excel: Spelling
("RTN", Entry::Finish(Action::Command("review.newNote"))), // Excel: New Note
("RV", Entry::Finish(Action::Pending("review.previousComment"))), // pending; Excel: Previous Comment; Previous Comment; reserved until the command lands
("RW", Entry::Finish(Action::Pending("review.shareWorkbook"))), // pending; Excel: Share Workbook; reserved until the command lands

View File

@ -389,8 +389,8 @@
},
{
"seq": "HDD",
"kind": "command",
"target": "home.deleteCells",
"kind": "dialog",
"target": "deleteCells",
"excel": "Delete Cells",
"note": ""
},
@ -502,8 +502,8 @@
},
{
"seq": "HFDS",
"kind": "command",
"target": "edit.goToSpecial",
"kind": "dialog",
"target": "goToSpecial",
"excel": "Go To Special",
"note": ""
},
@ -572,8 +572,8 @@
},
{
"seq": "HFIS",
"kind": "command",
"target": "edit.fillSeries",
"kind": "dialog",
"target": "series",
"excel": "Fill Series",
"note": "containment(Series)"
},
@ -607,25 +607,10 @@
},
{
"seq": "HFQ",
"kind": "continue",
"kind": "control",
"excel": "Orientation Menu",
"note": "group level: containment(Orientation)"
},
{
"seq": "HFQL",
"kind": "command",
"target": "home.angleClockwise",
"pending": true,
"excel": "Angle Clockwise",
"note": "Angle Clockwise; reserved until the command lands"
},
{
"seq": "HFQO",
"kind": "command",
"target": "home.angleCounterclockwise",
"pending": true,
"excel": "Angle Counterclockwise",
"note": "Angle Counterclockwise; reserved until the command lands"
"note": "group level: containment(Orientation)",
"target": "home.orientation"
},
{
"seq": "HFS",
@ -650,15 +635,15 @@
},
{
"seq": "HIE",
"kind": "command",
"target": "home.insertCells",
"kind": "dialog",
"target": "insertCells",
"excel": "Insert Copied Cells",
"note": "Insert Cells (copied cells TBD)"
},
{
"seq": "HII",
"kind": "command",
"target": "home.insertCells",
"kind": "dialog",
"target": "insertCells",
"excel": "Insert Cells",
"note": ""
},
@ -849,8 +834,8 @@
},
{
"seq": "HOP",
"kind": "command",
"target": "review.protectSheet",
"kind": "dialog",
"target": "protectSheet",
"excel": "Protect Sheet",
"note": ""
},
@ -1047,8 +1032,8 @@
},
{
"seq": "NC1",
"kind": "command",
"target": "insert.chart",
"kind": "control",
"target": "insert.chart.column",
"excel": "Column Chart",
"note": "Column chart (kind TBD)"
},
@ -1061,8 +1046,8 @@
},
{
"seq": "NCM",
"kind": "command",
"target": "review.newComment",
"kind": "dialog",
"target": "comment",
"excel": "Excel: New Comment (modern)"
},
{
@ -1074,8 +1059,8 @@
},
{
"seq": "ND",
"kind": "command",
"target": "insert.chart",
"kind": "control",
"target": "insert.chart.scatter",
"excel": "Scatter Chart",
"note": "Scatter (kind TBD)"
},
@ -1108,8 +1093,8 @@
},
{
"seq": "NH1",
"kind": "command",
"target": "pageLayout.headerFooter",
"kind": "dialog",
"target": "headerFooter",
"excel": "Header & Footer",
"note": ""
},
@ -1122,8 +1107,8 @@
},
{
"seq": "NHI",
"kind": "command",
"target": "insert.chart",
"kind": "control",
"target": "insert.chart.hierarchy",
"excel": "Hierarchy Chart",
"note": "Hierarchy (kind TBD)"
},
@ -1136,8 +1121,8 @@
},
{
"seq": "NI2",
"kind": "command",
"target": "insert.link",
"kind": "dialog",
"target": "insertLink",
"excel": "Link",
"note": ""
},
@ -1165,8 +1150,8 @@
},
{
"seq": "NN1",
"kind": "command",
"target": "insert.chart",
"kind": "control",
"target": "insert.chart.line",
"excel": "Line Chart",
"note": "Line chart (kind TBD)"
},
@ -1213,8 +1198,8 @@
},
{
"seq": "NQ",
"kind": "command",
"target": "insert.chart",
"kind": "control",
"target": "insert.chart.pie",
"excel": "Pie Chart",
"note": "Pie (kind TBD)"
},
@ -1227,8 +1212,8 @@
},
{
"seq": "NSA",
"kind": "command",
"target": "insert.chart",
"kind": "control",
"target": "insert.chart.statistic",
"excel": "Statistical Chart",
"note": "Statistical chart (kind TBD)"
},
@ -1464,8 +1449,8 @@
},
{
"seq": "PSP",
"kind": "command",
"target": "pageLayout.pageSetup",
"kind": "dialog",
"target": "pageSetup",
"excel": "Page Setup",
"note": ""
},
@ -1568,36 +1553,24 @@
},
{
"seq": "ME",
"kind": "continue",
"kind": "control",
"excel": "Date & Time Menu",
"note": "group level: containment(Date & Time)"
},
{
"seq": "MEF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Date & Time - Insert Function",
"note": "containment(Insert Function)"
"note": "group level: containment(Date & Time)",
"target": "formulas.dateTime"
},
{
"seq": "MF",
"kind": "command",
"target": "formulas.insertFunction",
"kind": "dialog",
"target": "insertFunction",
"excel": "Insert Function",
"note": ""
},
{
"seq": "MG",
"kind": "continue",
"kind": "control",
"excel": "Math & Trig Menu",
"note": "group level: containment(Math & Trig)"
},
{
"seq": "MGF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Math & Trig - Insert Function",
"note": "containment(Insert Function)"
"note": "group level: containment(Math & Trig)",
"target": "formulas.mathTrig"
},
{
"seq": "MH",
@ -1614,17 +1587,10 @@
},
{
"seq": "MI1",
"kind": "command",
"target": "formulas.insertFunction",
"kind": "control",
"target": "formulas.financial",
"excel": "Excel: Financial; first item of the dropdown"
},
{
"seq": "MIF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Financial - Insert Function",
"note": "containment(Insert Function)"
},
{
"seq": "MJ",
"kind": "command",
@ -1640,50 +1606,38 @@
},
{
"seq": "MKK",
"kind": "command",
"target": "formulas.errorChecking",
"kind": "dialog",
"target": "errorChecking",
"excel": "Error Checking",
"note": ""
},
{
"seq": "ML",
"kind": "continue",
"kind": "control",
"excel": "Logical Menu",
"note": "group level: containment(Logical)"
},
{
"seq": "MLF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Logical - Insert Function",
"note": "containment(Insert Function)"
"note": "group level: containment(Logical)",
"target": "formulas.logical"
},
{
"seq": "MM",
"kind": "command",
"target": "formulas.defineName",
"kind": "dialog",
"target": "defineName",
"excel": "Define Name",
"note": ""
},
{
"seq": "MN",
"kind": "command",
"target": "formulas.nameManager",
"kind": "dialog",
"target": "nameManager",
"excel": "Name Manager",
"note": ""
},
{
"seq": "MO",
"kind": "continue",
"kind": "control",
"excel": "Lookup & Reference Menu",
"note": "group level: containment(Lookup & Reference)"
},
{
"seq": "MOF",
"kind": "command",
"target": "formulas.lookupReference",
"excel": "Lookup & Reference - Insert Function",
"note": "containment(Lookup & Reference)"
"note": "group level: containment(Lookup & Reference)",
"target": "formulas.lookup"
},
{
"seq": "MP",
@ -1694,64 +1648,10 @@
},
{
"seq": "MQ",
"kind": "continue",
"kind": "control",
"excel": "More Functions Menu",
"note": "group level: containment(More Functions)"
},
{
"seq": "MQCF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Cube - Insert Function",
"note": "containment(Insert Function)"
},
{
"seq": "MQEF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Engineering - Insert Function",
"note": "containment(Insert Function)"
},
{
"seq": "MQIF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Information - Insert Function",
"note": "containment(Insert Function)"
},
{
"seq": "MQOF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Compatibility - Insert Function",
"note": "containment(Insert Function)"
},
{
"seq": "MQSF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Statistical - Insert Function",
"note": "containment(Insert Function)"
},
{
"seq": "MQWF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Web - Insert Function",
"note": "containment(Insert Function)"
},
{
"seq": "MR",
"kind": "continue",
"excel": "Recently Used Menu",
"note": "group level: containment(Recently Used)"
},
{
"seq": "MRF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Recently Used - Insert Function",
"note": "containment(Insert Function)"
"note": "group level: containment(More Functions)",
"target": "formulas.moreFunctions"
},
{
"seq": "MS",
@ -1769,16 +1669,10 @@
},
{
"seq": "MT",
"kind": "continue",
"kind": "control",
"excel": "Text Menu",
"note": "group level: containment(Text)"
},
{
"seq": "MTF",
"kind": "command",
"target": "formulas.insertFunction",
"excel": "Text - Insert Function",
"note": "containment(Insert Function)"
"note": "group level: containment(Text)",
"target": "formulas.text"
},
{
"seq": "MU",
@ -1871,8 +1765,8 @@
},
{
"seq": "AB",
"kind": "command",
"target": "data.subtotal",
"kind": "dialog",
"target": "subtotal",
"excel": "Subtotal",
"note": ""
},
@ -1892,8 +1786,8 @@
},
{
"seq": "AE",
"kind": "command",
"target": "data.textToColumns",
"kind": "dialog",
"target": "textToColumns",
"excel": "Text to Columns",
"note": ""
},
@ -2014,8 +1908,8 @@
},
{
"seq": "AM",
"kind": "command",
"target": "data.removeDuplicates",
"kind": "dialog",
"target": "removeDuplicates",
"excel": "Remove Duplicates",
"note": ""
},
@ -2183,8 +2077,8 @@
},
{
"seq": "ASS",
"kind": "command",
"target": "data.sort",
"kind": "dialog",
"target": "sort",
"excel": "Sort",
"note": ""
},
@ -2287,8 +2181,8 @@
},
{
"seq": "RA1",
"kind": "command",
"target": "review.checkAccessibility",
"kind": "dialog",
"target": "accessibility",
"excel": "Check Accessibility",
"note": ""
},
@ -2302,8 +2196,8 @@
},
{
"seq": "RC",
"kind": "command",
"target": "review.newComment",
"kind": "dialog",
"target": "comment",
"excel": "New Comment",
"note": ""
},
@ -2346,8 +2240,8 @@
},
{
"seq": "RPS",
"kind": "command",
"target": "review.protectSheet",
"kind": "dialog",
"target": "protectSheet",
"excel": "Protect Sheet",
"note": ""
},
@ -2368,8 +2262,8 @@
},
{
"seq": "RS",
"kind": "command",
"target": "review.spelling",
"kind": "dialog",
"target": "spelling",
"excel": "Spelling",
"note": ""
},

View File

@ -358,6 +358,101 @@ fn popup_command(forced: bool, clicked: bool) -> Option<egui::SetOpenCommand> {
if forced { Some(egui::SetOpenCommand::Bool(true)) } else { clicked.then_some(egui::SetOpenCommand::Toggle) }
}
/// Per-control state of an [`editable_combo`]: the filter text and highlighted row.
#[derive(Clone, Default)]
struct ComboState {
text: String,
sel: i32,
was_open: bool,
}
/// What the user did with an [`editable_combo`].
enum ComboResult {
None,
/// A row was applied: clicked or Enter on the highlighted row.
Picked(String),
/// Enter with free text that matches no row (e.g. a custom font size).
Entered(String),
}
/// Excel-style dropdown: a filter field (type to narrow the list) plus arrow/Enter selection.
/// `items` are `(value, display)`; the value is what comes back. When `forced` (keytip invoke)
/// or clicked, the popup opens, focuses the filter field, and selects its text.
fn editable_combo(
ui: &mut Ui,
anchor: &egui::Response,
salt: &'static str,
current_value: &str,
items: &[(&str, String)],
forced: bool,
) -> ComboResult {
let popup_id = anchor.id.with("popup");
let state_id = anchor.id.with(salt).with("__combo_state");
let mut st = ui.data_mut(|d| d.get_temp::<ComboState>(state_id)).unwrap_or_default();
let open_now = egui::Popup::is_id_open(ui.ctx(), popup_id);
if open_now && !st.was_open {
st.text = String::new();
st.sel = items.iter().position(|(v, _)| *v == current_value).map(|i| i as i32).unwrap_or(0);
}
st.was_open = open_now;
let mut result = ComboResult::None;
egui::Popup::menu(anchor).open_memory(popup_command(forced, anchor.clicked())).show(|ui| {
let text_id = anchor.id.with(salt).with("__combo_text");
let te = ui.add(egui::TextEdit::singleline(&mut st.text).id(text_id).desired_width(220.0).hint_text("Type to filter…"));
if open_now && !te.has_focus() {
ui.ctx().memory_mut(|m| m.request_focus(text_id));
// Select all once focus lands so typing replaces the filter.
if let Some(mut s) = egui::text_edit::TextEditState::load(ui.ctx(), text_id) {
let len = st.text.chars().count();
s.cursor.set_char_range(Some(egui::text::CCursorRange::two(egui::text::CCursor::new(0), egui::text::CCursor::new(len))));
s.store(ui.ctx(), text_id);
}
}
let lower = st.text.to_lowercase();
let filtered: Vec<usize> = items.iter().enumerate().filter(|(_, (_, d))| d.to_lowercase().contains(&lower)).map(|(i, _)| i).collect();
ui.input(|i| {
if !filtered.is_empty() {
let n = filtered.len() as i32;
if i.key_pressed(egui::Key::ArrowDown) {
st.sel = (st.sel + 1).rem_euclid(n);
}
if i.key_pressed(egui::Key::ArrowUp) {
st.sel = (st.sel - 1).rem_euclid(n);
}
}
if i.key_pressed(egui::Key::Enter) {
if filtered.is_empty() {
let t = st.text.trim().to_string();
if !t.is_empty() {
result = ComboResult::Entered(t);
}
} else {
let idx = filtered[st.sel.clamp(0, filtered.len() as i32 - 1) as usize];
result = ComboResult::Picked(items[idx].0.to_string());
}
}
});
ui.separator();
egui::ScrollArea::vertical().max_height(240.0).show(ui, |ui| {
for i in &filtered {
let (value, disp) = &items[*i];
let row = ui.add(egui::Button::selectable(*i as i32 == st.sel, egui::RichText::new(disp)).min_size(vec2(220.0, 20.0)));
if row.clicked() {
result = ComboResult::Picked(value.to_string());
}
if row.hovered() {
st.sel = *i as i32;
}
}
});
if !matches!(result, ComboResult::None) {
ui.close();
}
});
ui.data_mut(|d| d.insert_temp(state_id, st));
result
}
fn menu_items(app: &mut SheetApp, ui: &mut Ui, items: &[(&str, &str, serde_json::Value)]) {
for (label, id, p) in items {
if *label == "-" {
@ -594,10 +689,9 @@ fn home(app: &mut SheetApp, ui: &mut Ui) {
}
}
let o = small_button(ui, Icon::Orientation, "", "Orientation", true);
for sequence in keytip_table::group_extension(app.keytips.prefix().unwrap_or_default(), &["home.orientation", "dialog:formatCells"]) {
keytip(app, ui, sequence, o.rect.center_bottom());
}
egui::Popup::menu(&o).show(|ui| {
control_keytip(app, ui, &o, "home.orientation");
let forced_o = invoke(app, "home.orientation");
egui::Popup::menu(&o).open_memory(popup_command(forced_o, o.clicked())).show(|ui| {
menu_items(
app,
ui,
@ -944,19 +1038,13 @@ 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 name = st.font.name.clone();
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}));
let items: Vec<(&str, String)> = FONTS.iter().map(|f| (*f, f.to_string())).collect();
if let ComboResult::Picked(value) = editable_combo(ui, &resp, "font", &name, &items, forced) {
act(app, "home.fontName", json!({"name": value}));
}
}
@ -981,19 +1069,24 @@ pub const FONTS: &[&str] = &[
];
fn size_combo(app: &mut SheetApp, ui: &mut Ui, st: &Style) {
let mut size = st.font.size;
let before = size;
let size = st.font.size;
let label = if size.fract() == 0.0 { format!("{}", size as i32) } else { format!("{size}") };
let resp = ui.add_sized([52.0, 24.0], egui::Button::new(egui::RichText::new(label).font(theme::ui_font(12.5))));
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));
let sizes = [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];
let labels: Vec<String> = sizes.iter().map(|s| format!("{}", *s as i32)).collect();
let items: Vec<(&str, String)> = labels.iter().map(|s| (s.as_str(), s.clone())).collect();
let current = if size.fract() == 0.0 { format!("{}", size as i32) } else { format!("{size}") };
let mut apply = |s: &str| {
if let Ok(v) = s.parse::<f32>() {
act(app, "home.fontSize", json!({"size": v}));
}
});
if size != before {
act(app, "home.fontSize", json!({"size": size}));
};
match editable_combo(ui, &resp, "size", &current, &items, forced) {
ComboResult::Picked(value) => apply(&value),
ComboResult::Entered(text) => apply(&text),
_ => {}
}
}
@ -1003,29 +1096,24 @@ fn number_combo(app: &mut SheetApp, ui: &mut Ui, st: &Style) {
let current = format!("{kind:?}");
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 = ui.add_sized([150.0, 24.0], egui::Button::new(egui::RichText::new(lang.tr(&current)).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 names = ["General", "Number", "Currency", "Accounting", "Short Date", "Long Date", "Time", "Percentage", "Fraction", "Scientific", "Text"];
let items: Vec<(&str, String)> = names
.iter()
.map(|n| {
let code = gridcraft_engine::cmd::format::format_code_for(n);
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);
}
(*n, format!("{:<12} {preview}", lang.tr(n)))
})
.chain(std::iter::once(("__more", format!("{:>12}", lang.tr("More Number Formats…")))))
.collect();
if let ComboResult::Picked(value) = editable_combo(ui, &resp, "number", &current, &items, forced) {
match value.as_str() {
"__more" => app.open_dialog("formatCells", json!({"tab": "Number"})),
n => act(app, "home.numberFormat", json!({"format": n})),
}
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})),
None => {}
}
}
@ -1326,11 +1414,11 @@ fn insert(app: &mut SheetApp, ui: &mut Ui) {
}
ui.vertical(|ui| {
ui.horizontal(|ui| {
for (icon, tip, kind, subs) in [
for (icon, tip, cid, subs) in [
(
Icon::ChartBar,
"Column or Bar",
"column",
"insert.chart.column",
vec![
("Clustered Column", "column", ""),
("Stacked Column", "column", "stacked"),
@ -1342,7 +1430,7 @@ fn insert(app: &mut SheetApp, ui: &mut Ui) {
(
Icon::ChartLine,
"Line or Area",
"line",
"insert.chart.line",
vec![
("Line", "line", ""),
("Line with Markers", "line", "markers"),
@ -1351,11 +1439,12 @@ fn insert(app: &mut SheetApp, ui: &mut Ui) {
("Stacked Area", "area", "stacked"),
],
),
(Icon::ChartPie, "Pie or Doughnut", "pie", vec![("Pie", "pie", ""), ("Doughnut", "doughnut", "")]),
(Icon::ChartPie, "Pie or Doughnut", "insert.chart.pie", vec![("Pie", "pie", ""), ("Doughnut", "doughnut", "")]),
] {
let r = small_button(ui, icon, "", tip, true);
let _ = kind;
egui::Popup::menu(&r).show(|ui| {
control_keytip(app, ui, &r, cid);
let forced_c = invoke(app, cid);
egui::Popup::menu(&r).open_memory(popup_command(forced_c, r.clicked())).show(|ui| {
for (label, k, s) in &subs {
if ui.button(*label).clicked() {
act(app, "insert.chart", json!({"type": k, "subtype": s}));
@ -1365,16 +1454,18 @@ fn insert(app: &mut SheetApp, ui: &mut Ui) {
}
});
ui.horizontal(|ui| {
for (icon, tip, subs) in [
for (icon, tip, cid, subs) in [
(
Icon::ChartScatter,
"Scatter or Bubble",
"insert.chart.scatter",
vec![("Scatter", "scatter", ""), ("Scatter with Lines", "scatter", "lines"), ("Bubble", "bubble", "")],
),
(Icon::Chart, "Statistic", vec![("Histogram", "histogram", ""), ("Box & Whisker", "boxWhisker", "")]),
(Icon::Chart, "Statistic", "insert.chart.statistic", vec![("Histogram", "histogram", ""), ("Box & Whisker", "boxWhisker", "")]),
(
Icon::Theme,
"Hierarchy & more",
"insert.chart.hierarchy",
vec![
("Treemap", "treemap", ""),
("Sunburst", "sunburst", ""),
@ -1387,7 +1478,9 @@ fn insert(app: &mut SheetApp, ui: &mut Ui) {
),
] {
let r = small_button(ui, icon, "", tip, true);
egui::Popup::menu(&r).show(|ui| {
control_keytip(app, ui, &r, cid);
let forced_c = invoke(app, cid);
egui::Popup::menu(&r).open_memory(popup_command(forced_c, r.clicked())).show(|ui| {
for (label, k, s) in &subs {
if ui.button(*label).clicked() {
act(app, "insert.chart", json!({"type": k, "subtype": s}));
@ -1403,7 +1496,7 @@ fn insert(app: &mut SheetApp, ui: &mut Ui) {
}
sep(ui);
let sp = big_button(ui, Icon::Sparkline, "Sparklines", "Sparklines", true);
anchor_keytip(app, ui, &sp, &["insert.sparkline"]);
anchor_keytip(app, ui, &sp, &["dialog:sparkline"]);
egui::Popup::menu(&sp).show(|ui| {
for (label, kind) in [("Line", "line"), ("Column", "column"), ("Win/Loss", "winLoss")] {
if ui.button(label).clicked() {
@ -1418,12 +1511,12 @@ fn insert(app: &mut SheetApp, ui: &mut Ui) {
}
sep(ui);
let __k = big_button(ui, Icon::Link, "Link", "Insert Link (⌘K)", false);
control_keytip(app, ui, &__k, "insertLink");
control_keytip(app, ui, &__k, "dialog:insertLink");
if __k.clicked() {
app.open_dialog("insertLink", json!({}));
}
let __k = big_button(ui, Icon::Comment, "Comment", "New Comment", false);
control_keytip(app, ui, &__k, "comment");
control_keytip(app, ui, &__k, "dialog:comment");
if __k.clicked() {
app.open_dialog("comment", json!({"threaded": true}));
}
@ -1434,7 +1527,7 @@ fn insert(app: &mut SheetApp, ui: &mut Ui) {
act(app, "insert.textBox", json!({}));
}
let __k = big_button(ui, Icon::PageLayout, "Header &\nFooter", "Header & Footer", false);
control_keytip(app, ui, &__k, "headerFooter");
control_keytip(app, ui, &__k, "dialog:headerFooter");
if __k.clicked() {
// As in Excel: Page Layout view, editing the header in place (the dialog is under Page Setup).
crate::paper_view::start_editing(app);
@ -1618,7 +1711,7 @@ fn page_layout(app: &mut SheetApp, ui: &mut Ui) {
fn formulas(app: &mut SheetApp, ui: &mut Ui) {
let __k = big_button(ui, Icon::Function, "Insert\nFunction", "Insert Function (⇧F3)", false);
control_keytip(app, ui, &__k, "insertFunction");
control_keytip(app, ui, &__k, "dialog:insertFunction");
if __k.clicked() {
app.open_dialog("insertFunction", json!({}));
}
@ -1627,17 +1720,19 @@ fn formulas(app: &mut SheetApp, ui: &mut Ui) {
if s.clicked() {
act(app, "formulas.autoSum", json!({}));
}
for (label, cat) in [
("Financial", "Financial"),
("Logical", "Logical"),
("Text", "Text"),
("Date &\nTime", "DateTime"),
("Lookup &\nReference", "Lookup"),
("Math &\nTrig", "MathTrig"),
("More\nFunctions", "Statistical"),
for (label, cat, cid) in [
("Financial", "Financial", "formulas.financial"),
("Logical", "Logical", "formulas.logical"),
("Text", "Text", "formulas.text"),
("Date &\nTime", "DateTime", "formulas.dateTime"),
("Lookup &\nReference", "Lookup", "formulas.lookup"),
("Math &\nTrig", "MathTrig", "formulas.mathTrig"),
("More\nFunctions", "Statistical", "formulas.moreFunctions"),
] {
let r = big_button(ui, Icon::Book, label, label, true);
egui::Popup::menu(&r).show(|ui| {
control_keytip(app, ui, &r, cid);
let forced_c = invoke(app, cid);
egui::Popup::menu(&r).open_memory(popup_command(forced_c, r.clicked())).show(|ui| {
ui.set_max_height(420.0);
egui::ScrollArea::vertical().show(ui, |ui| {
for f in gridcraft_engine::cmd::formulas::function_list() {
@ -1659,18 +1754,18 @@ fn formulas(app: &mut SheetApp, ui: &mut Ui) {
}
sep(ui);
let __k = big_button(ui, Icon::Name, "Name\nManager", "Name Manager", false);
control_keytip(app, ui, &__k, "nameManager");
control_keytip(app, ui, &__k, "dialog:nameManager");
if __k.clicked() {
app.open_dialog("nameManager", json!({}));
}
ui.vertical(|ui| {
let __k = small_button(ui, Icon::Name, "Define Name", "Define Name", false);
control_keytip(app, ui, &__k, "defineName");
control_keytip(app, ui, &__k, "dialog:defineName");
if __k.clicked() {
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, &["formulas.useInFormula"]);
anchor_keytip(app, ui, &use_in, &["formulas.pasteName"]);
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 {
@ -1709,12 +1804,12 @@ fn formulas(app: &mut SheetApp, ui: &mut Ui) {
act(app, "formulas.showFormulas", json!({}));
}
let __k = small_button(ui, Icon::Validation, "Error Checking", "Error Checking", false);
control_keytip(app, ui, &__k, "errorChecking");
control_keytip(app, ui, &__k, "dialog:errorChecking");
if __k.clicked() {
app.open_dialog("errorChecking", json!({}));
}
let __k = small_button(ui, Icon::Calc, "Evaluate Formula", "Evaluate Formula", false);
control_keytip(app, ui, &__k, "evaluateFormula");
control_keytip(app, ui, &__k, "dialog:evaluateFormula");
if __k.clicked() {
app.open_dialog("evaluateFormula", json!({}));
}
@ -1772,7 +1867,7 @@ fn data(app: &mut SheetApp, ui: &mut Ui) {
}
});
let __k = big_button(ui, Icon::SortFilter, "Sort", "Custom Sort", false);
control_keytip(app, ui, &__k, "sort");
control_keytip(app, ui, &__k, "dialog:sort");
if __k.clicked() {
app.open_dialog("sort", json!({}));
}
@ -1795,7 +1890,7 @@ fn data(app: &mut SheetApp, ui: &mut Ui) {
});
sep(ui);
let __k = big_button(ui, Icon::TextColumns, "Text to\nColumns", "Text to Columns", false);
control_keytip(app, ui, &__k, "textToColumns");
control_keytip(app, ui, &__k, "dialog:textToColumns");
if __k.clicked() {
app.open_dialog("textToColumns", json!({}));
}
@ -1805,14 +1900,12 @@ fn data(app: &mut SheetApp, ui: &mut Ui) {
act(app, "edit.flashFill", json!({}));
}
let __k = big_button(ui, Icon::Duplicates, "Remove\nDuplicates", "Remove Duplicates", false);
control_keytip(app, ui, &__k, "removeDuplicates");
control_keytip(app, ui, &__k, "dialog:removeDuplicates");
if __k.clicked() {
app.open_dialog("removeDuplicates", json!({}));
}
let dv = big_button(ui, Icon::Validation, "Data\nValidation", "Data Validation", true);
for sequence in
keytip_table::group_extension(app.keytips.prefix().unwrap_or_default(), &["dialog:dataValidation", "data.circleInvalid", "data.validation"])
{
for sequence in keytip_table::group_extension(app.keytips.prefix().unwrap_or_default(), &["data.validation", "data.circleInvalid"]) {
keytip(app, ui, sequence, dv.rect.center_bottom());
}
egui::Popup::menu(&dv).show(|ui| {
@ -1860,7 +1953,7 @@ fn data(app: &mut SheetApp, ui: &mut Ui) {
act(app, "data.ungroup", json!({}));
}
let __k = small_button(ui, Icon::Subtotal, "Subtotal", "Subtotal", false);
control_keytip(app, ui, &__k, "subtotal");
control_keytip(app, ui, &__k, "dialog:subtotal");
if __k.clicked() {
app.open_dialog("subtotal", json!({}));
}
@ -1885,7 +1978,7 @@ fn data(app: &mut SheetApp, ui: &mut Ui) {
fn review(app: &mut SheetApp, ui: &mut Ui) {
let __k = big_button(ui, Icon::Spell, "Spelling", "Spelling (F7)", false);
control_keytip(app, ui, &__k, "spelling");
control_keytip(app, ui, &__k, "dialog:spelling");
if __k.clicked() {
app.open_dialog("spelling", json!({}));
}
@ -1895,13 +1988,13 @@ fn review(app: &mut SheetApp, ui: &mut Ui) {
app.open_dialog("statistics", json!({}));
}
let __k = big_button(ui, Icon::Validation, "Check\nAccessibility", "Check Accessibility", false);
control_keytip(app, ui, &__k, "accessibility");
control_keytip(app, ui, &__k, "dialog:accessibility");
if __k.clicked() {
app.open_dialog("accessibility", json!({}));
}
sep(ui);
let __k = big_button(ui, Icon::Comment, "New\nComment", "New Comment", false);
control_keytip(app, ui, &__k, "comment");
control_keytip(app, ui, &__k, "dialog:comment");
if __k.clicked() {
app.open_dialog("comment", json!({"threaded": true}));
}
@ -1935,7 +2028,7 @@ fn review(app: &mut SheetApp, ui: &mut Ui) {
}
}
let notes = big_button(ui, Icon::Note, "Notes", "Notes", true);
anchor_keytip(app, ui, &notes, &["review.newNote"]);
anchor_keytip(app, ui, &notes, &["review.nextComment"]);
egui::Popup::menu(&notes).show(|ui| {
menu_items(
app,
@ -1952,7 +2045,7 @@ fn review(app: &mut SheetApp, ui: &mut Ui) {
sep(ui);
let protected = app.session.active().and_then(|d| d.wb.active().map(|s| s.is_protected())).unwrap_or(false);
let __k = big_button(ui, Icon::Lock, if protected { "Unprotect\nSheet" } else { "Protect\nSheet" }, "Protect Sheet", false);
control_keytip(app, ui, &__k, "review.protectSheet");
control_keytip(app, ui, &__k, "dialog:protectSheet");
if __k.clicked() {
if protected {
app.open_dialog("unprotectSheet", json!({}));
@ -2018,7 +2111,7 @@ fn view(app: &mut SheetApp, ui: &mut Ui) {
});
sep(ui);
let __k = big_button(ui, Icon::Zoom, "Zoom", "Zoom…", false);
control_keytip(app, ui, &__k, "zoom");
control_keytip(app, ui, &__k, "dialog:zoom");
if __k.clicked() {
app.open_dialog("zoom", json!({}));
}
@ -2035,10 +2128,9 @@ fn view(app: &mut SheetApp, ui: &mut Ui) {
}
sep(ui);
let fp = big_button(ui, Icon::Freeze, "Freeze\nPanes", "Freeze Panes", true);
for sequence in keytip_table::group_extension(
app.keytips.prefix().unwrap_or_default(),
&["view.freezePanes", "view.freezeTopRow", "view.freezeFirstColumn", "view.unfreezePanes"],
) {
for sequence in
keytip_table::group_extension(app.keytips.prefix().unwrap_or_default(), &["view.freezePanes", "view.freezeTopRow", "view.freezeFirstColumn"])
{
keytip(app, ui, sequence, fp.rect.center_bottom());
}
egui::Popup::menu(&fp).show(|ui| {

View File

@ -456,6 +456,47 @@ fn control_keytips_open_dropdowns_and_run_handlers() {
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
fn editable_combos_filter_by_typing_and_move_by_arrows() {
// Open the font dropdown (HFF), type to filter, then Enter to apply.
let mut h = harness(blank());
sequence(&mut h, &[Key::H, Key::F, Key::F], false);
h.run_steps(3); // let the filter field gain focus
events(&mut h, [Event::Text("arial".into())]);
h.run_steps(3);
// "arial" matches Arial only — Georgia must be filtered out of the list.
assert_eq!(h.query_all_by_label("Georgia").count(), 0, "typing must narrow the list");
assert!(h.query_all_by_label("Arial").count() >= 1, "the match must stay visible");
key(&mut h, Key::Enter);
h.run_steps(3);
assert!(h.state().message.is_none() && h.state().toast.is_none(), "applying must not raise an error");
// The popup closed and the combo button now shows the applied font.
assert_eq!(h.query_all_by_label("Arial").count(), 1, "Enter must apply Arial and close the popup");
assert_eq!(h.query_all_by_label("Georgia").count(), 0);
// Arrows alone move through the full list: Calibri -> Cambria -> Candara -> Consolas.
let mut h = harness(blank());
sequence(&mut h, &[Key::H, Key::F, Key::F], false);
h.run_steps(3);
for _ in 0..3 {
key(&mut h, Key::ArrowDown);
}
key(&mut h, Key::Enter);
h.run_steps(3);
assert!(h.state().message.is_none() && h.state().toast.is_none());
assert_eq!(h.query_all_by_label("Consolas").count(), 1, "Down x3 from Calibri must land on Consolas");
// Size combo: type a custom size not in the list, Enter applies it.
let mut h = harness(blank());
sequence(&mut h, &[Key::H, Key::F, Key::S], false);
h.run_steps(3);
events(&mut h, [Event::Text("13.5".into())]);
key(&mut h, Key::Enter);
h.run_steps(3);
assert!(h.state().message.is_none() && h.state().toast.is_none(), "custom sizes must apply");
assert_eq!(h.query_all_by_label("13.5").count(), 1, "the button must show the custom size");
}
#[test]
fn every_sequence_terminates_without_panicking() {
use gridcraft_ui_egui::keytips::keytip_table::SEQUENCES;

View File

@ -487,7 +487,7 @@ fn keytip_hdc_() {
fn keytip_hdd_() {
let mut h = harness(blank());
sequence(&mut h, "HDD", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.deleteCells"), "HDD must journal home.deleteCells");
assert!(h.state().dialog.is_some(), "HDD must open the deleteCells dialog");
}
#[test]
@ -603,7 +603,7 @@ fn keytip_hfdr_() {
fn keytip_hfds_() {
let mut h = harness(blank());
sequence(&mut h, "HFDS", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "edit.goToSpecial"), "HFDS must journal edit.goToSpecial");
assert!(h.state().dialog.is_some(), "HFDS must open the goToSpecial dialog");
}
#[test]
@ -677,7 +677,7 @@ fn keytip_hfir_() {
fn keytip_hfis_() {
let mut h = harness(blank());
sequence(&mut h, "HFIS", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "edit.fillSeries"), "HFIS must journal edit.fillSeries");
assert!(h.state().dialog.is_some(), "HFIS must open the series dialog");
}
#[test]
@ -712,27 +712,8 @@ fn keytip_hfp_() {
fn keytip_hfq_() {
let mut h = harness(blank());
sequence(&mut h, "HFQ", false);
assert_eq!(h.state().keytips.prefix(), Some("HFQ"), "HFQ must stay armed waiting for more input");
}
#[test]
fn keytip_hfql_() {
let mut h = harness(blank());
sequence(&mut h, "HFQL", false);
assert!(h.state().dialog.is_none(), "HFQL must not open a dialog");
assert!(h.state().message.is_none(), "HFQL must not open a message box");
assert!(h.state().toast.is_none(), "HFQL must not raise a toast");
assert!(h.state().session.journal.is_empty(), "HFQL must not journal");
}
#[test]
fn keytip_hfqo_() {
let mut h = harness(blank());
sequence(&mut h, "HFQO", false);
assert!(h.state().dialog.is_none(), "HFQO must not open a dialog");
assert!(h.state().message.is_none(), "HFQO must not open a message box");
assert!(h.state().toast.is_none(), "HFQO must not raise a toast");
assert!(h.state().session.journal.is_empty(), "HFQO must not journal");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "HFQ must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -762,14 +743,14 @@ fn keytip_hic_() {
fn keytip_hie_() {
let mut h = harness(blank());
sequence(&mut h, "HIE", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.insertCells"), "HIE must journal home.insertCells");
assert!(h.state().dialog.is_some(), "HIE must open the insertCells dialog");
}
#[test]
fn keytip_hii_() {
let mut h = harness(blank());
sequence(&mut h, "HII", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "home.insertCells"), "HII must journal home.insertCells");
assert!(h.state().dialog.is_some(), "HII must open the insertCells dialog");
}
#[test]
@ -966,7 +947,7 @@ fn keytip_hom_() {
fn keytip_hop_() {
let mut h = harness(blank());
sequence(&mut h, "HOP", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "review.protectSheet"), "HOP must journal review.protectSheet");
assert!(h.state().dialog.is_some(), "HOP must open the protectSheet dialog");
}
#[test]
@ -1169,7 +1150,8 @@ fn keytip_hx_() {
fn keytip_nc1_() {
let mut h = harness(blank());
sequence(&mut h, "NC1", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "insert.chart"), "NC1 must journal insert.chart");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "NC1 must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1183,7 +1165,7 @@ fn keytip_nc2_() {
fn keytip_ncm_() {
let mut h = harness(blank());
sequence(&mut h, "NCM", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "review.newComment"), "NCM must journal review.newComment");
assert!(h.state().dialog.is_some(), "NCM must open the comment dialog");
}
#[test]
@ -1197,7 +1179,8 @@ fn keytip_ncb_() {
fn keytip_nd_() {
let mut h = harness(blank());
sequence(&mut h, "ND", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "insert.chart"), "ND must journal insert.chart");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "ND must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1235,7 +1218,7 @@ fn keytip_nfpf_() {
fn keytip_nh1_() {
let mut h = harness(blank());
sequence(&mut h, "NH1", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "pageLayout.headerFooter"), "NH1 must journal pageLayout.headerFooter");
assert!(h.state().dialog.is_some(), "NH1 must open the headerFooter dialog");
}
#[test]
@ -1249,7 +1232,8 @@ fn keytip_nhf_() {
fn keytip_nhi_() {
let mut h = harness(blank());
sequence(&mut h, "NHI", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "insert.chart"), "NHI must journal insert.chart");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "NHI must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1263,7 +1247,7 @@ fn keytip_ni1_() {
fn keytip_ni2_() {
let mut h = harness(blank());
sequence(&mut h, "NI2", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "insert.link"), "NI2 must journal insert.link");
assert!(h.state().dialog.is_some(), "NI2 must open the insertLink dialog");
}
#[test]
@ -1294,7 +1278,8 @@ fn keytip_nm2_() {
fn keytip_nn1_() {
let mut h = harness(blank());
sequence(&mut h, "NN1", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "insert.chart"), "NN1 must journal insert.chart");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "NN1 must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1343,7 +1328,8 @@ fn keytip_npo_() {
fn keytip_nq_() {
let mut h = harness(blank());
sequence(&mut h, "NQ", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "insert.chart"), "NQ must journal insert.chart");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "NQ must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1357,7 +1343,8 @@ fn keytip_nr_() {
fn keytip_nsa_() {
let mut h = harness(blank());
sequence(&mut h, "NSA", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "insert.chart"), "NSA must journal insert.chart");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "NSA must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1613,7 +1600,7 @@ fn keytip_prs_() {
fn keytip_psp_() {
let mut h = harness(blank());
sequence(&mut h, "PSP", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "pageLayout.pageSetup"), "PSP must journal pageLayout.pageSetup");
assert!(h.state().dialog.is_some(), "PSP must open the pageSetup dialog");
}
#[test]
@ -1721,35 +1708,23 @@ fn keytip_md_() {
fn keytip_me_() {
let mut h = harness(blank());
sequence(&mut h, "ME", false);
assert_eq!(h.state().keytips.prefix(), Some("ME"), "ME must stay armed waiting for more input");
}
#[test]
fn keytip_mef_() {
let mut h = harness(blank());
sequence(&mut h, "MEF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MEF must journal formulas.insertFunction");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "ME must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
fn keytip_mf_() {
let mut h = harness(blank());
sequence(&mut h, "MF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MF must journal formulas.insertFunction");
assert!(h.state().dialog.is_some(), "MF must open the insertFunction dialog");
}
#[test]
fn keytip_mg_() {
let mut h = harness(blank());
sequence(&mut h, "MG", false);
assert_eq!(h.state().keytips.prefix(), Some("MG"), "MG must stay armed waiting for more input");
}
#[test]
fn keytip_mgf_() {
let mut h = harness(blank());
sequence(&mut h, "MGF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MGF must journal formulas.insertFunction");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "MG must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1770,14 +1745,8 @@ fn keytip_mi_() {
fn keytip_mi1_() {
let mut h = harness(blank());
sequence(&mut h, "MI1", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MI1 must journal formulas.insertFunction");
}
#[test]
fn keytip_mif_() {
let mut h = harness(blank());
sequence(&mut h, "MIF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MIF must journal formulas.insertFunction");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "MI1 must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1798,49 +1767,37 @@ fn keytip_mk_() {
fn keytip_mkk_() {
let mut h = harness(blank());
sequence(&mut h, "MKK", false);
assert!(h.state().message.is_none() && h.state().toast.is_none(), "MKK must run formulas.errorChecking cleanly");
assert!(h.state().dialog.is_some(), "MKK must open the errorChecking dialog");
}
#[test]
fn keytip_ml_() {
let mut h = harness(blank());
sequence(&mut h, "ML", false);
assert_eq!(h.state().keytips.prefix(), Some("ML"), "ML must stay armed waiting for more input");
}
#[test]
fn keytip_mlf_() {
let mut h = harness(blank());
sequence(&mut h, "MLF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MLF must journal formulas.insertFunction");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "ML must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
fn keytip_mm_() {
let mut h = harness(blank());
sequence(&mut h, "MM", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.defineName"), "MM must journal formulas.defineName");
assert!(h.state().dialog.is_some(), "MM must open the defineName dialog");
}
#[test]
fn keytip_mn_() {
let mut h = harness(blank());
sequence(&mut h, "MN", false);
assert!(h.state().message.is_none() && h.state().toast.is_none(), "MN must run formulas.nameManager cleanly");
assert!(h.state().dialog.is_some(), "MN must open the nameManager dialog");
}
#[test]
fn keytip_mo_() {
let mut h = harness(blank());
sequence(&mut h, "MO", false);
assert_eq!(h.state().keytips.prefix(), Some("MO"), "MO must stay armed waiting for more input");
}
#[test]
fn keytip_mof_() {
let mut h = harness(blank());
sequence(&mut h, "MOF", false);
assert!(h.state().message.is_none() && h.state().toast.is_none(), "MOF must run formulas.lookupReference cleanly");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "MO must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1854,63 +1811,8 @@ fn keytip_mp_() {
fn keytip_mq_() {
let mut h = harness(blank());
sequence(&mut h, "MQ", false);
assert_eq!(h.state().keytips.prefix(), Some("MQ"), "MQ must stay armed waiting for more input");
}
#[test]
fn keytip_mqcf_() {
let mut h = harness(blank());
sequence(&mut h, "MQCF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MQCF must journal formulas.insertFunction");
}
#[test]
fn keytip_mqef_() {
let mut h = harness(blank());
sequence(&mut h, "MQEF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MQEF must journal formulas.insertFunction");
}
#[test]
fn keytip_mqif_() {
let mut h = harness(blank());
sequence(&mut h, "MQIF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MQIF must journal formulas.insertFunction");
}
#[test]
fn keytip_mqof_() {
let mut h = harness(blank());
sequence(&mut h, "MQOF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MQOF must journal formulas.insertFunction");
}
#[test]
fn keytip_mqsf_() {
let mut h = harness(blank());
sequence(&mut h, "MQSF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MQSF must journal formulas.insertFunction");
}
#[test]
fn keytip_mqwf_() {
let mut h = harness(blank());
sequence(&mut h, "MQWF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MQWF must journal formulas.insertFunction");
}
#[test]
fn keytip_mr_() {
let mut h = harness(blank());
sequence(&mut h, "MR", false);
assert_eq!(h.state().keytips.prefix(), Some("MR"), "MR must stay armed waiting for more input");
}
#[test]
fn keytip_mrf_() {
let mut h = harness(blank());
sequence(&mut h, "MRF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MRF must journal formulas.insertFunction");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "MQ must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -1934,14 +1836,8 @@ fn keytip_msp_() {
fn keytip_mt_() {
let mut h = harness(blank());
sequence(&mut h, "MT", false);
assert_eq!(h.state().keytips.prefix(), Some("MT"), "MT must stay armed waiting for more input");
}
#[test]
fn keytip_mtf_() {
let mut h = harness(blank());
sequence(&mut h, "MTF", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "formulas.insertFunction"), "MTF must journal formulas.insertFunction");
assert!(h.state().message.is_none() && h.state().toast.is_none(), "MT must route to its control without an error box");
assert_eq!(h.state().keytips.prefix(), None);
}
#[test]
@ -2039,7 +1935,7 @@ fn keytip_mxp_() {
fn keytip_ab_() {
let mut h = harness(blank());
sequence(&mut h, "AB", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.subtotal"), "AB must journal data.subtotal");
assert!(h.state().dialog.is_some(), "AB must open the subtotal dialog");
}
#[test]
@ -2060,7 +1956,7 @@ fn keytip_ad_() {
fn keytip_ae_() {
let mut h = harness(blank());
sequence(&mut h, "AE", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.textToColumns"), "AE must journal data.textToColumns");
assert!(h.state().dialog.is_some(), "AE must open the textToColumns dialog");
}
#[test]
@ -2200,7 +2096,7 @@ fn keytip_ak_() {
fn keytip_am_() {
let mut h = harness(blank());
sequence(&mut h, "AM", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.removeDuplicates"), "AM must journal data.removeDuplicates");
assert!(h.state().dialog.is_some(), "AM must open the removeDuplicates dialog");
}
#[test]
@ -2394,7 +2290,7 @@ fn keytip_asd_() {
fn keytip_ass_() {
let mut h = harness(blank());
sequence(&mut h, "ASS", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "data.sort"), "ASS must journal data.sort");
assert!(h.state().dialog.is_some(), "ASS must open the sort dialog");
}
#[test]
@ -2508,7 +2404,7 @@ fn keytip_ay_() {
fn keytip_ra1_() {
let mut h = harness(blank());
sequence(&mut h, "RA1", false);
assert!(h.state().message.is_none() && h.state().toast.is_none(), "RA1 must run review.checkAccessibility cleanly");
assert!(h.state().dialog.is_some(), "RA1 must open the accessibility dialog");
}
#[test]
@ -2525,7 +2421,7 @@ fn keytip_ra2_() {
fn keytip_rc_() {
let mut h = harness(blank());
sequence(&mut h, "RC", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "review.newComment"), "RC must journal review.newComment");
assert!(h.state().dialog.is_some(), "RC must open the comment dialog");
}
#[test]
@ -2576,7 +2472,7 @@ fn keytip_ro_() {
fn keytip_rps_() {
let mut h = harness(blank());
sequence(&mut h, "RPS", false);
assert!(h.state().session.journal.iter().any(|(id, _)| id == "review.protectSheet"), "RPS must journal review.protectSheet");
assert!(h.state().dialog.is_some(), "RPS must open the protectSheet dialog");
}
#[test]
@ -2600,7 +2496,7 @@ fn keytip_rrs_() {
fn keytip_rs_() {
let mut h = harness(blank());
sequence(&mut h, "RS", false);
assert!(h.state().message.is_some() || h.state().toast.is_some(), "RS must invoke review.spelling and surface its missing-params error");
assert!(h.state().dialog.is_some(), "RS must open the spelling dialog");
}
#[test]
@ -3033,7 +2929,7 @@ fn keytip_9_() {
#[test]
fn generated_test_count_matches_the_table() {
const EXPECTED: usize = 397;
const EXPECTED: usize = 381;
assert_eq!(gridcraft_ui_egui::keytips::keytip_table::SEQUENCES.len(), EXPECTED);
}