Commit Graph

4 Commits

Author SHA1 Message Date
ispyisail bf7f49f334 Merge remote-tracking branch 'upstream/master' into review-635-fix
# Conflicts:
#	CMakeLists.txt
#	cmake/developer_options.cmake
#	cmake/qet_compilation_vars.cmake
#	sources/qetapp.cpp
2026-09-22 23:10:34 +12:00
Andre Rummler 8d08c3fd56 Replacing depreciated qAsConst with std::as_const 2026-08-12 12:29:56 +02:00
ispyisail 83b9f32bd0 Add device-button-to-action bindings, for any 3D mouse backend
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.
2026-08-02 22:11:30 +12:00
ispyisail 5275fb44fe Add configurable shortcuts: ShortcutManager registry + Shortcuts config page (#574)
Implements the first pillar of #574: a "Shortcuts" preferences page letting
users rebind, search and reset every keyboard shortcut in the app.

What it does
- New ShortcutManager singleton: every one of the ~95 setShortcut()/
  setShortcuts() call sites across qet.cpp, qetmainwindow.cpp,
  elementspanelwidget.cpp, autonumberingdockwidget.cpp, richtexteditor.cpp,
  qetdiagrameditor.cpp, qettemplateeditor.cpp and qetelementeditor.cpp now
  calls registerAction(target, id, category, default_sequence) instead,
  which applies the user's saved override (or the default) and remembers
  the target for later editing.
- New ShortcutsConfigPage, added to the existing "Configurer QElectroTech"
  dialog: a filterable table of every registered shortcut, grouped by
  category, each with a QKeySequenceEdit and a per-row reset button, plus a
  "reset all" button. Bindings are only persisted (via
  ShortcutManager::setSequence()) when the dialog is accepted.
- Conflict detection: rows whose currently-edited sequence collides with
  another row are highlighted with a tooltip naming the conflicting action.
- Overrides are stored under a "shortcuts/" QSettings group, one key per
  id, keyed to match the id (not persisted at all when equal to the
  hardcoded default), so a future QET version can safely raise a default
  for anyone who never customized it.

Design notes
- Targets are handled generically via QObject rather than QAction, since one
  call site (autonumberingdockwidget's "Configurer" button) is a
  QPushButton, not a QAction. Both declare an identical "shortcut"
  QKeySequence Q_PROPERTY, so registerAction() reads/writes it through the
  property system instead of needing a separate code path.
- Several live targets can share one id at once -- QET allows multiple
  windows of the same kind (diagram editor, element editor...) open
  simultaneously, each constructing its own QAction with the same id.
  setSequence() updates every live target for that id in one call, so a
  rebind takes effect in all open windows immediately, without restart.
- A shortcut's description is captured from its target's text() the first
  time that id is registered, then cached -- so the config page stays
  correct even after the owning window is closed. One consequence: a
  shortcut belonging to an on-demand window (element editor, title block
  editor, rich text editor) only appears in the list once that window has
  been opened at least once in the current session, since nothing has
  registered its id yet otherwise.

Testing
Full CMake build (qmake CONFIG+=no_kf5, Qt 5.15) compiles clean with zero
errors and zero new warnings. Verified end-to-end in a real running session
(Xvfb + xdotool):
- The Shortcuts page appears in Configure QElectroTech with the right icon,
  lists every always-registered shortcut with correct category/action name/
  current binding.
- The filter box correctly narrows the list, and correctly returns nothing
  for an action whose owning window hasn't been constructed yet this
  session (confirming the on-demand-registration behavior above is working
  as designed, not silently broken).
- Conflict detection correctly flagged a real pre-existing same-key overlap
  between "Supprimer" (delete selection, Del) and "Supprimer ce folio"
  (delete diagram from panel, Del) -- both highlighted with explanatory
  tooltips.
- Rebound "Manuel en ligne" to Ctrl+Shift+M, clicked OK: persisted under
  [shortcuts] in QElectroTech.conf, and the Aide menu's entry showed the new
  binding immediately, no restart needed.
- Reopened the dialog: the rebind was still shown. Clicked its per-row
  reset button, then OK: the settings key was removed entirely (not stored
  as "F1"), correctly falling back to the hardcoded default.

Retrofitting the Tab/Shift+Tab, select-all (#585) and Ctrl+G jump-to-element
(#586) shortcuts through this registry is left for a follow-up once those
PRs land, to avoid re-merging still-open branches into this one.

Developed with assistance from Claude (Anthropic).
2026-08-01 01:35:51 +12:00