Discussion #599's own scope explicitly deferred this ("Related, not
proposed here... a natural follow-up once basic pan/zoom motion works").
Basic pan/zoom now works (previous commits on this branch), so this adds
it -- generically, for whichever SpaceMouseBackend is in use, not tied to
libspnav specifically, matching the seam the previous commit built.
## Reuses ShortcutManager instead of inventing a second action registry
ShortcutManager is already an app-wide registry of every named, rebindable
action -- undo, redo, rotate selection, cut/copy/paste, autonum configure,
and dozens more -- each carried by a live QAction or QAbstractButton. A
device button binding to one of *those* ids, rather than to a bespoke
QET-3D-mouse-only action list, means the discussion's own examples
(rotate/mirror/undo) are available for free, and any action added to the
app in the future is automatically bindable too.
Added ShortcutManager::trigger(id): find the first still-alive target for
an id and call QAction::trigger() or QAbstractButton::click(), whichever
it is. Deliberately not disambiguated by which window is currently active,
unlike SpaceMouseListener's own pan/zoom dispatch -- a target's owning
top-level window isn't reliably discoverable from a bare QAction. Correct
in the overwhelming common case of one open editor window; documented in
the header as a known simplification, not silently assumed correct.
## The binding itself: SpaceMouseButtonMap
A thin QSettings-backed button-number -> action-id map, unbound by default
for every button on every device -- nothing happens on any button press
until the user opens Configuration > Souris 3D and binds something,
matching this whole feature's "silent until asked for" default.
## Backend side: SpaceMouseBackend::buttonPressed(int)
Added to the platform interface alongside the existing motion() signal.
SpnavBackend now handles SPNAV_EVENT_BUTTON (previously explicitly
ignored) and emits on press only -- release is not reported, since nothing
downstream has a use for it. A future non-spnav backend implements the
same signal and gets button support for free through
SpaceMouseListener::applyButton(), without that logic being duplicated or
re-verified per backend -- the same reasoning the previous commit's seam
was built around.
## Configuration UI: SpaceMouseConfigPage
Modelled directly on the existing ShortcutsConfigPage -- same QTableWidget
shape, same "persist on applyConf(), not live" contract -- one row per
binding: button number (spin box, unbounded, since button count and
numbering genuinely vary from 2 to 30+ across real devices and this could
not be checked against hardware) and action (combo box populated from
ShortcutManager::instance().allShortcuts(), the exact same live registry
the Shortcuts page itself lists). Only added to the Configuration dialog
when QET_SPACEMOUSE_SUPPORT is compiled in.
## Verified, including the one thing that doesn't need hardware to prove
Rebuilt from scratch both ways: option off adds zero new object code
(confirmed via a forced rebuild of the one unconditionally-changed file,
shortcutmanager.cpp, which alone picked up new warning-free code); option
on compiles all four new/changed files warning-free and links clean.
The backend's button *detection* (SPNAV_EVENT_BUTTON -> buttonPressed
signal) still cannot be verified without a real device or daemon -- same
limitation as the motion path from the previous commits, stated plainly
rather than glossed over.
What *is* fully verified, because none of it needs hardware:
- SpaceMouseButtonMap: unbound by default, set/read-back, clearing via an
empty id, enumeration -- all confirmed via a standalone harness linked
against the real compiled objects.
- ShortcutManager::trigger(): registered a real QAction, confirmed
trigger() fires it exactly once and returns true; confirmed it returns
false (not a crash) for an unknown id.
- SpnavBackend: constructs safely with no daemon present (isAvailable()
false, as it must be), and both its motion and buttonPressed signals
are correctly wired per Qt's own metaobject data (QSignalSpy).
- The configuration page end-to-end, via a real Xvfb session: opened
Configuration > Souris 3D, confirmed the action combo box lists the
live, real ShortcutManager registry (undo, rotate, cut/copy/paste,
dozens more -- not a mock), added rows, edited the button number,
removed rows, selected "Éditeur de schémas — Pivoter" (Rotate -- the
discussion's own example) for button 3, clicked OK, and confirmed via
the actual settings file that it persisted exactly as
"buttons\3=diagrameditor.rotate_selection". Reopened the dialog and
confirmed it read back correctly. This is a full, real round trip
through the UI, not a claim.