Make the 3D mouse pan and zoom the element editor too

The 3D mouse only acted on the diagram editor: SpaceMouseListener
ignored every other window, so the element editor did not move at
all (reported by scorpio810 in PR #635 with a SpacePilot Pro).

applyMotion() now also handles a QETElementEditor, driving its
ElementView through the same scrollbar pan and a new
ElementView::zoom(factor), which keeps the wheel zoom's clamping.
The element editor's scene rect only covers what is on screen, so it
is grown before each pan sample, as its middle-button pan does;
without that the scrollbars have no range and the pan does nothing.

Verified under Xvfb with an LD_PRELOAD stand-in for libspnav feeding
recorded motion samples: element editor zooms 1.63x for ten z=50
samples and pans; diagram editor screenshots are byte-identical to
master's for the same input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ispyisail
2026-09-25 09:32:10 +12:00
parent 73075a5154
commit 04e2279a7f
4 changed files with 73 additions and 32 deletions
+12
View File
@@ -117,6 +117,18 @@ void ElementView::scaleClamped(qreal factor)
scale(factor, factor);
}
/**
@brief ElementView::zoom
Zoom by an arbitrary factor, for a continuous input such as a 3D mouse.
Same clamping as the wheel zoom.
@param zoom_factor : > 1 zooms in, < 1 zooms out
*/
void ElementView::zoom(qreal zoom_factor)
{
adjustSceneRect();
scaleClamped(zoom_factor);
}
/**
Agrandit le schema (+33% = inverse des -25 % de zoomMoins())
*/
+1
View File
@@ -41,6 +41,7 @@ class ElementView : public QGraphicsView {
ElementScene *scene() const;
void setScene(ElementScene *);
QRectF viewedSceneRect() const;
void zoom(qreal zoom_factor);
protected:
void mousePressEvent(QMouseEvent *) override;
+54 -26
View File
@@ -24,6 +24,8 @@
#endif
#include "../diagramview.h"
#include "../editor/elementview.h"
#include "../editor/ui/qetelementeditor.h"
#include "../projectview.h"
#include "../qetdiagrameditor.h"
#include "../shortcutmanager.h"
@@ -47,6 +49,16 @@ namespace {
//one-line fix once someone with a device tries it.
constexpr qreal ZOOM_DIVISOR = 1000.0;
constexpr qreal PAN_SCALE = 1.0;
//Pan a view by one motion sample, through its scrollbars -- the same
//way both editors' own middle-button drag pans them.
void panView(QGraphicsView *view, int dx, int dy)
{
view->horizontalScrollBar()->setValue(
view->horizontalScrollBar()->value() - qRound(dx * PAN_SCALE));
view->verticalScrollBar()->setValue(
view->verticalScrollBar()->value() - qRound(dy * PAN_SCALE));
}
}
/**
@@ -96,11 +108,12 @@ qreal SpaceMouseListener::zoomFactorForZAxis(int z)
/**
@brief SpaceMouseListener::applyMotion
Apply one motion sample to whichever DiagramView is currently active.
Apply one motion sample to the view of the active window: the current
folio of a diagram editor, or the drawing of an element editor.
X/Y translation pans it, Z translation zooms it -- the same two
primitives (scrollbars, DiagramView::zoom()) DiagramView::wheelEvent()
already drives from a physical wheel, so there is no new navigation
logic here, only a new input source feeding the existing one.
primitives (scrollbars, zoom()) each view's wheelEvent() already drives
from a physical wheel, so there is no new navigation logic here, only a
new input source feeding the existing one.
Which of a device's three translation axes is "left/right" vs
"forward/back" vs "up/down", and their sign, is a hardware convention
@@ -112,31 +125,46 @@ qreal SpaceMouseListener::zoomFactorForZAxis(int z)
*/
void SpaceMouseListener::applyMotion(int dx, int dy, int dz)
{
auto *editor = qobject_cast<QETDiagramEditor *>(qApp->activeWindow());
if (!editor) {
return;
}
QWidget *window = qApp->activeWindow();
ProjectView *project_view = editor->currentProjectView();
if (!project_view) {
return;
}
DiagramView *view = project_view->currentDiagram();
if (!view) {
return;
}
if (dx || dy)
if (auto *editor = qobject_cast<QETDiagramEditor *>(window))
{
view->horizontalScrollBar()->setValue(
view->horizontalScrollBar()->value() - qRound(dx * PAN_SCALE));
view->verticalScrollBar()->setValue(
view->verticalScrollBar()->value() - qRound(dy * PAN_SCALE));
}
ProjectView *project_view = editor->currentProjectView();
if (!project_view) {
return;
}
if (dz) {
view->zoom(zoomFactorForZAxis(dz));
DiagramView *view = project_view->currentDiagram();
if (!view) {
return;
}
if (dx || dy) {
panView(view, dx, dy);
}
if (dz) {
view->zoom(zoomFactorForZAxis(dz));
}
}
else if (auto *element_editor = qobject_cast<QETElementEditor *>(window))
{
ElementView *view = element_editor->elementView();
if (!view) {
return;
}
if (dx || dy)
{
//The element editor's scene rect only just covers what is
//on screen, so grow it before each sample, as its own
//middle-button pan does on release -- otherwise the
//scrollbars have no range and the pan does nothing.
view->adjustSceneRect();
panView(view, dx, dy);
}
if (dz) {
view->zoom(zoomFactorForZAxis(dz));
}
}
}
+6 -6
View File
@@ -25,16 +25,16 @@ class SpaceMouseBackend;
/**
@brief The SpaceMouseListener class
https://github.com/qelectrotech/qelectrotech-source-mirror/discussions/599 :
bridges a 3Dconnexion SpaceMouse/SpacePilot 6-DOF device to
DiagramView's existing pan/zoom primitives (the same
horizontalScrollBar()/verticalScrollBar()/zoom() calls
DiagramView::wheelEvent() already uses for a physical wheel), and its
bridges a 3Dconnexion SpaceMouse/SpacePilot 6-DOF device to the
existing pan/zoom primitives of DiagramView and ElementView (the same
horizontalScrollBar()/verticalScrollBar()/zoom() calls their
wheelEvent() already uses for a physical wheel), and its
buttons to named QET actions via ShortcutManager -- the same registry
keyboard shortcuts already use, so a device button can trigger anything
in that registry (undo, redo, rotate selection, ...) without QET having
a second, device-specific action list.
Everything here is platform-independent: which DiagramView to apply
Everything here is platform-independent: which view to apply
motion to, the pan/zoom calls, the Z-to-zoom-factor mapping, and button
dispatch via SpaceMouseButtonMap + ShortcutManager. Talking to the
actual device driver is a SpaceMouseBackend's job (see its class
@@ -73,7 +73,7 @@ class SpaceMouseListener : public QObject
private slots:
/// Apply one motion sample -- from whichever backend is in use
/// -- to whichever DiagramView is currently active.
/// -- to the view of the active diagram or element editor.
void applyMotion(int dx, int dy, int dz);
/// Look up which action id, if any, SpaceMouseButtonMap binds