Commit Graph

3 Commits

Author SHA1 Message Date
ispyisail 17ebbffbca Add 3D mouse speed, direction, dead zone and twist-to-zoom settings
The 3D mouse's pan and zoom speeds were fixed guesses, and each sample
was applied as it came, so the speed on screen depended on how often
the driver sends samples -- different for every platform and device.

Motion now goes through SpaceMouseMotion::map(), which scales each
sample by the time since the previous one, and applies the user's
settings from a new "Mouvement" section of Configuration > Souris 3D:
pan and zoom speed, a dead zone, inverting each axis, and zooming by
push/pull (as before) or by twisting the cap. The defaults keep the
previous behaviour. Zoom is now exponential in the deflection, so the
factor stays positive however hard the cap is pulled (1 + z/1000 went
negative past z = -1000) and an equal push and pull cancel out. Sub-
pixel pan is carried over between samples instead of being rounded
away. The backend now reports all six axes.

tst_spacemousemotion covers the mapping without a device and is built
whether or not QET_ENABLE_SPACEMOUSE is on. The new behaviour was also
checked end to end with tools/spnav-shim (qelectrotech-docker): twist
with a dead zone of 10 ignores push/pull and small drift, and a twist of
60 gives the same frame as a push of 50 with the defaults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 09:56:04 +12: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 e35cab33f0 Extract a SpaceMouseBackend seam ahead of a future Windows/macOS backend
The user asked for phase 2 (Windows/macOS via 3Dconnexion's proprietary
3DxWare SDK) on top of #635. This sandbox has no 3DxWare SDK, no Windows
toolchain, and no macOS toolchain -- nothing to compile, link, or run a
single line of platform code against, unlike the Linux/libspnav path,
which was built and actually tested here for real. Writing 3DxWare
integration code that has never even built would be a materially weaker,
unverifiable thing sitting in this PR, so it is not in this commit.

What is: the structural seam that makes adding it later a contained,
reviewable change instead of a rewrite of code that already works.

## Before

SpaceMouseListener did three unrelated things in one class: own the
libspnav connection, read spnav events, and apply motion to the active
DiagramView. A Windows/macOS backend would have had to either duplicate
all of the DiagramView-facing logic (the pan/zoom calls, the Z-to-zoom-
factor mapping, the "which view is active" lookup -- all already verified)
or bolt onto the same class with a maze of #ifdefs. Either way, touching
that file again would put the already-tested Linux path back in scope for
review.

## After

- SpaceMouseBackend: a tiny interface (isAvailable(), a motion(dx,dy,dz)
  signal). A backend's only job is owning one platform's connection to the
  driver/daemon and translating its native event into this one signal.
- SpnavBackend: the libspnav code from the previous commit, moved behind
  that interface with no behaviour change -- still spnav_open() in the
  constructor, still a QSocketNotifier on spnav_fd(), still silent when no
  daemon/device is present.
- SpaceMouseListener: now backend-agnostic. Owns whichever backend the
  platform provides, applies its motion to the active DiagramView exactly
  as before. The DiagramView-facing code (pan/zoom calls,
  zoomFactorForZAxis) did not need to change at all -- only its input
  changed from a spnav_event_motion struct to three plain ints.

A future 3DxWare backend implements SpaceMouseBackend, is selected in
SpaceMouseListener's constructor behind its own
QET_SPACEMOUSE_BACKEND_3DXWARE guard (see the comment marking exactly
where), and never has to touch SpnavBackend or SpaceMouseListener's
DiagramView-facing half.

## CMake: one user option, one define per backend

QET_ENABLE_SPACEMOUSE is unchanged as the single option a user sets.
Internally, find_spacemouse.cmake now decides *which* backend (if any)
that resolves to: on Linux with libspnav found, QET_SPACEMOUSE_BACKEND_SPNAV
plus the umbrella QET_SPACEMOUSE_SUPPORT. Turning the option on anywhere
else today downgrades cleanly with a warning naming discussion #599,
instead of trying (and failing) to find libspnav on a platform that
doesn't ship it. Adding 3DxWare later means adding one more branch here,
not restructuring this file.

## Verified this is a pure refactor, not just "still compiles"

Reconfigured and rebuilt both ways from scratch:
 - option off: unchanged from before -- no new source files compiled, zero
   new object code.
 - option on: both new files compile with zero warnings, binary still
   links against libspnav.so.0 (confirmed via ldd), and run to completion
   in this environment (which has no spacenavd) with zero crashes and zero
   spnav-related output -- identical to before the refactor.
 - zoomFactorForZAxis re-linked and re-run in isolation: identical output
   to the pre-refactor commit (z=0 -> exactly 1.0, z=+-350 -> 1.35/0.65),
   confirming the math moved unchanged rather than being reimplemented.
2026-08-02 21:22:42 +12:00