mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-25 09:50:59 +00:00
133 lines
8.3 KiB
Markdown
133 lines
8.3 KiB
Markdown
# Keyboard Shortcuts
|
|
|
|
## Why it exists
|
|
|
|
Key events arrive in several windows (the main frame's char hook, the 3D canvases, the
|
|
gizmo manager and the object list), and the same keys are shown again in menu labels,
|
|
toolbar tooltips, gizmo names and the shortcuts dialog. The registry is the one table all
|
|
of them read. Each binding is defined once; dispatchers look key events up there, labels
|
|
are derived from it, and a change the user makes updates all of them.
|
|
|
|
## Data model
|
|
|
|
`KeyChord` (`src/slic3r/GUI/KeyChord.hpp`) is one key press: the key code as
|
|
`wxEVT_KEY_DOWN` reports it, plus the `wxMOD_*` modifiers held with it. It has two
|
|
text forms. The canonical one (`Ctrl+Shift+S`) is platform-neutral and doubles as the wx
|
|
accelerator string and the config format. The display one uses translated modifier
|
|
names and the command and option glyphs on macOS. `KeyChord::from_event()` turns any wx
|
|
key event into the same key code and modifiers, so a chord recorded in the dialog is
|
|
equal to the chord a dispatcher builds from the key press.
|
|
|
|
`Shortcut` is the enum of every user-facing binding. `shortcut_table` in
|
|
`src/slic3r/GUI/Shortcuts.cpp` gives each one a config key, a description, a context
|
|
mask, a default chord, a `repeatable` flag and a `modifier_variants` flag, in the order
|
|
the dialog lists them; a `static_assert` keeps the table and the enum in step.
|
|
|
|
`ShortcutRegistry` overlays the user's overrides on the defaults and keeps a
|
|
chord-to-shortcut index for lookups. It reads and writes the `shortcuts` section of
|
|
`AppConfig`. Only overrides are stored, so a default can change between releases
|
|
without touching anyone's config; `none` records a shortcut the user unbound.
|
|
|
|
## Contexts
|
|
|
|
A key press is looked up in the context of the window that received it.
|
|
|
|
| Context | Dispatcher | Examples |
|
|
|--------------|-----------------------------------------------------------|-------------------------------|
|
|
| `Global` | `MainFrame`'s `wxEVT_CHAR_HOOK`, before any child sees it | New project, camera views |
|
|
| `Plater` | `GLCanvas3D` of the 3D and assembly views | Arrange, gizmo activation |
|
|
| `Preview` | `GLCanvas3D` of the G-code preview | One-layer mode, jump to layer |
|
|
| `ObjectList` | the object list | Copy, delete, auto drop |
|
|
| `Painting` | `GLGizmosManager` while a painting gizmo is open | Circle, sphere, fill tools |
|
|
|
|
A shortcut can belong to several contexts, which is how copy and paste are a single
|
|
binding for the canvas and the object list. Two shortcuts can share a chord when their
|
|
contexts do not overlap; `C` is the cut gizmo in the 3D view, the G-code window in the
|
|
preview and the circle tool while painting. A Global chord is dispatched before every
|
|
other context, so the dialog treats it as conflicting with all of them.
|
|
|
|
A Global shortcut has to include Ctrl or Alt or use a key that types nothing, since a
|
|
bare printable key in the frame hook would swallow that character in every text field.
|
|
The dialog refuses such chords and `ShortcutRegistry::load()` drops them from the config.
|
|
Space counts as typing. The speed dial's default is the one bare Space, and
|
|
`MainFrame` leaves it to a focused control that uses Space itself (text fields, buttons,
|
|
combo boxes), so it opens the dial from the canvases and the tab strip only.
|
|
|
|
## Which event a chord matches
|
|
|
|
Letters, digits and special keys match on `wxEVT_KEY_DOWN`. Its key codes do not depend
|
|
on the keyboard layout: the key labelled `Q` on an AZERTY keyboard and the key in the
|
|
same position under a Cyrillic layout both report `Q`. Numpad keys fold onto their main
|
|
keyboard equivalents, so `Ctrl+1` and `Ctrl+Numpad 1` are one binding.
|
|
|
|
Punctuation matches on `wxEVT_CHAR`, because only the char event knows which character
|
|
a key produced under the active layout. `+` is Shift and `=` on a US keyboard and a key
|
|
of its own on a German one, and the binding means the character in both cases. The
|
|
canvas looks a key up on key-down first and, when nothing matched, once more on the char
|
|
event, for punctuation chords only. The dialog records chords the same way: a printable
|
|
non-alphanumeric key pressed with nothing but Shift is taken from the char event that
|
|
follows.
|
|
|
|
wxGTK does not report key auto-repeat, so the canvases share one record of the keys
|
|
seen going down and swallow the repeats of every shortcut not marked `repeatable`. Zoom
|
|
and undo repeat, for example; a toggle such as Tab does not. The record is shared because
|
|
a shortcut can move the focus to another canvas while its key is still held; a key
|
|
released while no canvas had the focus is dropped on the next press.
|
|
|
|
A few shortcuts have `modifier_variants`: Shift or Ctrl added to their binding selects a
|
|
step of the same action (1 mm and camera-space moves of the selection, five-step slider
|
|
moves). Only a binding without Shift or Ctrl of its own has steps, so no two bindings
|
|
share one. `ShortcutRegistry::match()` looks the exact chord up first and only then, when
|
|
nothing is bound to it, looks for such a shortcut whose binding is the chord minus those
|
|
modifiers, reporting which were added; a binding on Ctrl+Shift+key therefore wins over
|
|
the combined step. The Shift and Ctrl steps themselves are reserved. `step_owner()` names
|
|
the shortcut they belong to, the capture dialog refuses to assign them, and
|
|
`conflicts()` reports exact chords only. A binding made before its key became a stepping
|
|
key keeps its chord and shadows that one step. A move or rotation of the selection
|
|
started from the keyboard runs until the key that started it is released, or the
|
|
canvas loses focus, so a held key is one undo step.
|
|
|
|
## Labels
|
|
|
|
Menu labels, toolbar tooltips, gizmo names, the context menu and the shortcuts dialog
|
|
read the registry, so a rebinding shows up in all of them. Each tracked menu item keeps
|
|
its base label; `MainFrame::update_shortcut_labels()` appends the current binding
|
|
again after an edit, which also installs the new wx accelerator.
|
|
|
|
A chord that is unsafe as a menu accelerator, meaning a bare printable key, is appended
|
|
to the label as plain text so the menu cannot take it away from text fields. The macOS
|
|
edit menu shows its clipboard and undo entries that way, because a system-menu key
|
|
equivalent for Cmd+C would run instead of the text field's own copy.
|
|
|
|
On macOS the object list receives no key events at all, so its bindings are installed as
|
|
a `wxAcceleratorTable`, regenerated from the registry after each edit.
|
|
|
|
## Editing
|
|
|
|
The shortcuts dialog has a page per context, each opening with a line that says when its
|
|
keys apply. A page lists the shortcuts under the headings of `section_table`, with the
|
|
fixed keys that cannot change (mouse buttons, the step modifiers, Esc, the digit keys
|
|
that pick a filament) sorted into the same sections. The mouse drag rows describe the
|
|
camera actions chosen in Preferences; their button opens Preferences > Control with that
|
|
option scrolled into view and focused, instead of editing a key.
|
|
Editing a row opens a capture dialog that records the next chord, names the shortcuts it
|
|
would take the chord from, and on confirmation unbinds those and binds this one.
|
|
Resetting a row asks the same question when its default is now held by another
|
|
shortcut, so a reset cannot leave two shortcuts on one chord. Each change is written to
|
|
the config at once and pushed to the menus, tooltips and accelerator tables through
|
|
`GUI_App::on_shortcuts_changed()`. The dialog opens from the Help menu and Preferences >
|
|
Control on the Global page, and from the `?` key on the page of the view that received it.
|
|
|
|
## Adding a shortcut
|
|
|
|
1. Add the enum value to `Shortcut` and its row to `shortcut_table`, in the position
|
|
the dialog should list it; the row's section heading is the `section_table` entry
|
|
above it, so a new section needs an entry there too. Pick a default that does not
|
|
collide inside its contexts; the `[Shortcuts]` tests check every default against the
|
|
others.
|
|
2. Handle it in the dispatcher of its context: `MainFrame::handle_global_shortcut`,
|
|
`GLCanvas3D::handle_shortcut`, `ObjectList::dispatch_shortcut`, or a gizmo's
|
|
`on_tool_shortcut`. A gizmo that opens on a key sets `m_shortcut` in its constructor.
|
|
3. Where the UI shows the key, ask the registry (`display()` for tooltips,
|
|
`accelerator()` for menu labels); no label holds a literal key name.
|