GUI shortcut architecture
Goal
Perastage uses a centralized GUI shortcut registry to keep keyboard behavior explicit, validated and consistent across modules.
Central registry
The canonical shortcut map lives in:
gui/shortcut_registry.hgui/shortcut_registry.cpp
Each shortcut entry defines:
- Key combination (
keyCodefor non-modifier shortcuts). - Action (
ShortcutAction). - Owner module (
ownerModule). - Scope (
ShortcutScope:Global,Viewer2D,Viewer3D,Cli). - Focus policy (
ShortcutFocusPolicy: allowed or blocked in editable widgets). - Priority (used when multiple entries share the same key across scopes).
Current shortcut map
Menu accelerators are owned by their menu entries and are not routed through ShortcutAction. The Edit menu includes Ctrl+G for Group and Ctrl+U for Ungroup; both commands are global menu accelerators, operate on the active cross-table selection, and keep the existing editable-widget behavior provided by wxWidgets menu accelerator handling.
| Key | Action | Owner | Scope | Focus policy |
|---|---|---|---|---|
Z |
FitView |
viewer2d |
Viewer2D |
Block in editable widgets |
Z |
FitView |
viewer3d |
Viewer3D |
Block in editable widgets |
P |
CliPrefillPos |
cli |
Cli |
Block in editable widgets |
R |
CliPrefillRot |
cli |
Cli |
Block in editable widgets |
F |
CliPrefillFixture |
cli |
Cli |
Block in editable widgets |
1 |
SelectFixturesTab |
gui |
Global |
Block in editable widgets |
2 |
SelectTrussesTab |
gui |
Global |
Block in editable widgets |
3 |
SelectSupportsTab |
gui |
Global |
Block in editable widgets |
4 |
SelectObjectsTab |
gui |
Global |
Block in editable widgets |
NumPad1 |
ViewportFront |
viewer2d |
Viewer2D |
Block in editable widgets |
NumPad3 |
ViewportSide |
viewer2d |
Viewer2D |
Block in editable widgets |
NumPad7 |
ViewportTop |
viewer2d |
Viewer2D |
Block in editable widgets |
NumPad1 |
ViewportFront |
viewer3d |
Viewer3D |
Block in editable widgets |
NumPad3 |
ViewportSide |
viewer3d |
Viewer3D |
Block in editable widgets |
NumPad7 |
ViewportTop |
viewer3d |
Viewer3D |
Block in editable widgets |
NumPad5 |
ViewportReset3D |
viewer3d |
Viewer3D |
Block in editable widgets |
Priority and resolution rules
Resolution is performed by ResolveShortcut(...).
- Normalize key code to uppercase.
- Filter shortcuts with matching key.
- Apply focus gating through
CanExecuteShortcut(...):- reject when modifiers are active,
- reject when policy blocks editable-focus and focus is in editable text,
- require scope/focus match (for example,
Viewer2Drequires focus in 2D viewer).
- Select the highest-priority shortcut among eligible entries.
- Return one execution decision for
MainWindow::OnGlobalCharHook(...).
This makes shortcut behavior deterministic and keeps one single decision point for "can this shortcut run under current focus?".
Startup/test validation
ValidateShortcutRegistry() detects collisions by (scope, normalized key).
- Called at
MainWindowstartup (debug assertion + error log). - Covered by
tests/shortcut_registry_test.cpp.
Execution flow in GUI
MainWindow::OnGlobalCharHook(...)builds aShortcutExecutionContextfrom focus state.- Editable focus is detected via
gui::IsEditableWidgetFocused(...)ingui/editable_focus_utils.{h,cpp}. - The helper covers
wxTextEntry-based controls (for examplewxTextCtrland custom text-entry widgets), combo boxes without thewxCB_READONLYstyle, spin controls, and activewxGridcell editors. Read-only combo focus does not suppress shortcuts, even on ports where the combo's generic text-entry abstraction reports editable.
- Editable focus is detected via
- The hook asks
ResolveShortcut(...)for one decision. MainWindow::ApplyShortcutDecision(...)executes the routed action:ApplyFitShortcut()for focused viewer fit (selection-aware: when there is an active selection, fit targets selected fixtures/trusses/scene objects; otherwise it falls back to fitting the full scene),FocusConsoleForQuickCommand(...)for CLI-prefill actions,- notebook-tab selection commands for
1..4, ApplyViewportShortcut(...)for top/front/side numpad view actions in the active viewer,- 3D viewport reset for
NumPad5.
- Viewport orientation parity rule:
ViewportFrontinViewer2DandViewer3Dmust represent the same front-facing convention (camera from negative Y looking toward the origin) so shortcut behavior is consistent across both viewers.
Viewer-specific direct handling for these migrated shortcuts is intentionally avoided, so routing stays centralized in GUI.
Continuous placement cancellation
Escape remains a viewer-local interaction key rather than a registered global
shortcut. While continuous placement from Add Fixture, Add Truss, or Add Object
owns the focused 2D or 3D viewer, it takes precedence over measure-tool
cancellation and discards only the element currently attached to the pointer.
Elements already confirmed with left-click remain in the scene. Right-click
provides the same cancellation behavior without changing shortcut routing.
A left-button press is confirmed as placement only when it is released without
navigation movement. Holding and dragging keeps the normal viewer-local
navigation behavior: pan in the 2D viewer, and orbit or Shift-pan in the 3D
viewer. Releasing the button after navigation immediately realigns the pending
element beneath the pointer without confirming it. This mouse gesture does not
add a shortcut-registry entry.
Middle Mouse drag also pans either viewport during continuous placement. Its release realigns the provisional element beneath the pointer and never confirms or changes the current selection.
The global Ctrl+Z/Undo command is session-aware while continuous placement is
active. It restores the previous placement snapshot, removes the latest
confirmed element, and keeps that element as the provisional pointer-following
copy. When Undo reaches the first confirmed element, or when no element has yet
been confirmed, it restores the pre-dialog scene and ends placement. Redo is
ignored until the placement session ends so it cannot replace the scene behind
an active provisional element.
Scene clipboard accelerators
Ctrl+X, Ctrl+C, and Ctrl+V are standard Edit-menu accelerators for scene
Cut, Copy, and Paste. They use the central mixed scene selection rather than
the active table's local row state. These modifier shortcuts deliberately do
not belong to the no-modifier shortcut registry.
Editable text controls and active cell editors retain precedence. The scene handlers skip their menu command in that context so native editing clipboard behavior remains available. A non-editing table may invoke the scene commands.
Consistent editable-focus guard across local key handlers
Panels that handle local key events also reuse gui::IsEditableWidgetFocused(...)
before executing panel-level actions, avoiding divergent focus checks:
Viewer2DPanel::OnKeyDown(...)Viewer3DPanel::OnKeyDown(...)LayoutViewerPanel::OnKeyDown(...)
The modal 2D View Editor additionally routes Z fit, ←, →, ↑,
↓, and their Alt zoom variants through the embedded Viewer2DPanel even
when focus is in the editor side panels. Editable text controls keep priority
and receive their normal key input. This makes keyboard fit, pan, and zoom
behavior match the standalone 2D viewport while preserving text-editing safety.
This keeps key behavior consistent while editing text or cell editors, and prevents global-style actions from stealing keys during edit sessions.
Mouse modifier gestures tied to selection behavior
The following mouse gestures are implemented directly in viewer panels (not in the keyboard shortcut registry), and must stay aligned between viewers when they represent equivalent actions:
Viewer2D: Left Mouse drag retains its existing view-navigation and selection priorities, Middle Mouse drag always pans the view, and the wheel zooms.Viewer3D: Left Mouse drag orbits,Shift + Left Mouse dragand Middle Mouse drag pan, and the wheel zooms.The 3D navigation preferences invert horizontal yaw and vertical pitch independently. The legacy
viewer3d_invert_orbitsetting remains the vertical preference, whileviewer3d_invert_orbit_horizontalcontrols only horizontal movement and defaults to off when absent. Both are user-level preferences and are preserved when a project package is opened, regardless of values stored by the project author.Ctrl + Left Drag: rectangle selection in the active table context.Ctrl + Shift + Left Drag: transversal rectangle selection across all selectable object tables in bothViewer2DandViewer3D.
Truss-line fixture distribution
The Tools menu owns Alt+D, which opens the fixture distribution dialog for
the current fixture selection. The default exact-spacing system supports a
configurable center-to-center or edge-to-edge gap and can place fixtures either
between two limits from the outside inward or from one start point in a chosen
direction. The dialog also exposes the existing full-truss and uniform
two-point systems.
This combination is a wx menu accelerator rather than an entry in the
no-modifier shortcut registry. They do not alter existing shortcut priority.
During two-point selection, the active scene viewport owns Esc and uses it
only to cancel that transient interaction. Both distribution commands require
focus inside an existing scene viewport and never open, close, or switch panes.
Local shortcuts in "Create scene from text" autocomplete
RiderTextDialog adds local key handling in the multiline text box for
autocomplete navigation, and keeps Esc scoped to autocomplete behavior:
↑/↓: move the active suggestion.EnterorTab: accept the selected suggestion and replace the active token.Esc: if suggestions are open, close them without inserting; if suggestions are closed, keep focus in the dialog editor (do not close the dialog).
The dialog also shows this shortcut help inline under the editor so users can discover the local autocomplete controls without leaving the workflow.
Priority and focus impact:
Escis always consumed by the dialog to avoid accidental window close while editing rider text.- Navigation/accept keys are consumed only when the dropdown is open.
- Outside that state, the text box keeps its standard free-text editing behavior.
- Global shortcut routing is unchanged because the dialog key handling stays local.
- Suggestion ordering uses local relevance ranking (exact/prefix/fuzzy + recent usage + syntax context), but key routing/priority rules above remain unchanged.