From f326c2e342fba132fe0b4e1331ae6863ac7e44be Mon Sep 17 00:00:00 2001 From: Victor Voronin Date: Sun, 30 Nov 2025 18:45:53 +0100 Subject: [PATCH 1/3] add option to swap pan/zoom actions --- .../include/PlotJuggler/plotwidget_base.h | 4 +++ plotjuggler_base/src/plotwidget_base.cpp | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/plotjuggler_base/include/PlotJuggler/plotwidget_base.h b/plotjuggler_base/include/PlotJuggler/plotwidget_base.h index 99464a180..6e450e1e9 100644 --- a/plotjuggler_base/include/PlotJuggler/plotwidget_base.h +++ b/plotjuggler_base/include/PlotJuggler/plotwidget_base.h @@ -83,6 +83,8 @@ class PlotWidgetBase : public QWidget bool isZoomEnabled() const; + void setSwapZoomPan(bool swapped); + bool isXYPlot() const; QRectF currentBoundingRect() const; @@ -138,6 +140,8 @@ public slots: PlotLegend* legend(); PlotZoomer* zoomer(); PlotMagnifier* magnifier(); + PlotPanner* panner1(); + PlotPanner* panner2(); void updateMaximumZoomArea(); diff --git a/plotjuggler_base/src/plotwidget_base.cpp b/plotjuggler_base/src/plotwidget_base.cpp index d9cd30edd..3a0228b9d 100644 --- a/plotjuggler_base/src/plotwidget_base.cpp +++ b/plotjuggler_base/src/plotwidget_base.cpp @@ -777,6 +777,22 @@ bool PlotWidgetBase::isZoomEnabled() const return p->zoom_enabled; } +void PlotWidgetBase::setSwapZoomPan(bool swapped) +{ + if (swapped) + { + // Swap: Left button for pan, Ctrl+Left for zoom + p->zoomer->setMousePattern(QwtEventPattern::MouseSelect1, Qt::LeftButton, Qt::ControlModifier); + p->panner1->setMouseButton(Qt::LeftButton, Qt::NoModifier); + } + else + { + // Default: Left button for zoom, Ctrl+Left for pan + p->zoomer->setMousePattern(QwtEventPattern::MouseSelect1, Qt::LeftButton, Qt::NoModifier); + p->panner1->setMouseButton(Qt::LeftButton, Qt::ControlModifier); + } +} + void PlotWidgetBase::overrideCurvesStyle(std::optional style) { p->overridden_curve_style = style; @@ -815,6 +831,7 @@ PlotLegend* PlotWidgetBase::legend() { return p->legend; } + PlotZoomer* PlotWidgetBase::zoomer() { return p->zoomer; @@ -825,6 +842,16 @@ PlotMagnifier* PlotWidgetBase::magnifier() return p->magnifier; } +PlotPanner* PlotWidgetBase::panner1() +{ + return p->panner1; +} + +PlotPanner* PlotWidgetBase::panner2() +{ + return p->panner2; +} + void PlotWidgetBase::updateMaximumZoomArea() { QRectF max_rect; From 51de215bf60bb03a0a8562ebb18881c15be0791d Mon Sep 17 00:00:00 2001 From: Victor Voronin Date: Sat, 20 Dec 2025 12:52:21 +0100 Subject: [PATCH 2/3] handle cursor updates in panner/zoomer --- plotjuggler_app/plotwidget.cpp | 16 +------------ plotjuggler_app/plotwidget.h | 1 - plotjuggler_base/src/plotpanner.cpp | 36 +++++++++++++++++++++++++++++ plotjuggler_base/src/plotpanner.h | 5 ++++ plotjuggler_base/src/plotzoomer.cpp | 6 ++++- 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/plotjuggler_app/plotwidget.cpp b/plotjuggler_app/plotwidget.cpp index d90db457b..178693ab5 100644 --- a/plotjuggler_app/plotwidget.cpp +++ b/plotjuggler_app/plotwidget.cpp @@ -1650,14 +1650,6 @@ bool PlotWidget::eventFilter(QObject* obj, QEvent* event) return false; } -void PlotWidget::overrideCursonMove() -{ - QSettings settings; - QString theme = settings.value("Preferences::theme", "light").toString(); - auto pixmap = LoadSvg(":/resources/svg/move_view.svg", theme); - QApplication::setOverrideCursor(QCursor(pixmap.scaled(24, 24))); -} - void PlotWidget::setAxisScale(QwtAxisId axisId, double min, double max) { if (min > max) @@ -1713,17 +1705,12 @@ bool PlotWidget::canvasEventFilter(QEvent* event) emit trackerMoved(pointF); return true; // don't pass to canvas(). } - else if (mouse_event->modifiers() == Qt::ControlModifier) // panner - { - overrideCursonMove(); - } return false; // send to canvas() } else if (mouse_event->buttons() == Qt::MiddleButton && mouse_event->modifiers() == Qt::NoModifier) { - overrideCursonMove(); - return false; + return false; // send to canvas() } else if (mouse_event->button() == Qt::RightButton) { @@ -1764,7 +1751,6 @@ bool PlotWidget::canvasEventFilter(QEvent* event) case QEvent::MouseButtonRelease: { if (_dragging.mode == DragInfo::NONE) { - QApplication::restoreOverrideCursor(); return false; } } diff --git a/plotjuggler_app/plotwidget.h b/plotjuggler_app/plotwidget.h index fdeed4c80..543ab2003 100644 --- a/plotjuggler_app/plotwidget.h +++ b/plotjuggler_app/plotwidget.h @@ -247,7 +247,6 @@ private slots: // void updateMaximumZoomArea(); void rescaleEqualAxisScaling(); - void overrideCursonMove(); void setAxisScale(QwtAxisId axisId, double min, double max); }; diff --git a/plotjuggler_base/src/plotpanner.cpp b/plotjuggler_base/src/plotpanner.cpp index 4651f830f..8c19090f5 100644 --- a/plotjuggler_base/src/plotpanner.cpp +++ b/plotjuggler_base/src/plotpanner.cpp @@ -3,6 +3,10 @@ #include "qwt_scale_div.h" #include "plotpanner.h" +#include +#include +#include +#include "PlotJuggler/svg_util.h" void PlotPanner::moveCanvas(int dx, int dy) { @@ -61,3 +65,35 @@ void PlotPanner::moveCanvas(int dx, int dy) plot->setAutoReplot(doAutoReplot); plot->replot(); } + +void PlotPanner::widgetMousePressEvent(QMouseEvent* event) +{ + // Check if this event matches our panning button/modifiers + Qt::MouseButton button; + Qt::KeyboardModifiers modifiers; + getMouseButton(button, modifiers); + + if (event->button() == button && event->modifiers() == modifiers) + { + // Set the move cursor when panning starts + QSettings settings; + QString theme = settings.value("Preferences::theme", "light").toString(); + auto pixmap = LoadSvg(":/resources/svg/move_view.svg", theme); + QApplication::setOverrideCursor(QCursor(pixmap.scaled(24, 24))); + _cursor_overridden = true; + } + + QwtPlotPanner::widgetMousePressEvent(event); +} + +void PlotPanner::widgetMouseReleaseEvent(QMouseEvent* event) +{ + // Restore cursor before calling base class + if (_cursor_overridden) + { + QApplication::restoreOverrideCursor(); + _cursor_overridden = false; + } + + QwtPlotPanner::widgetMouseReleaseEvent(event); +} diff --git a/plotjuggler_base/src/plotpanner.h b/plotjuggler_base/src/plotpanner.h index 5b97e31e3..30cfacdb1 100644 --- a/plotjuggler_base/src/plotpanner.h +++ b/plotjuggler_base/src/plotpanner.h @@ -18,7 +18,12 @@ public Q_SLOTS: signals: void rescaled(QRectF new_size); +protected: + void widgetMousePressEvent(QMouseEvent* event) override; + void widgetMouseReleaseEvent(QMouseEvent* event) override; + private: + bool _cursor_overridden = false; }; #endif // PLOTPANNER_H diff --git a/plotjuggler_base/src/plotzoomer.cpp b/plotjuggler_base/src/plotzoomer.cpp index add5dba46..752dfcaef 100644 --- a/plotjuggler_base/src/plotzoomer.cpp +++ b/plotjuggler_base/src/plotzoomer.cpp @@ -83,7 +83,11 @@ void PlotZoomer::widgetMouseMoveEvent(QMouseEvent* me) void PlotZoomer::widgetMouseReleaseEvent(QMouseEvent* me) { _mouse_pressed = false; - _zoom_enabled = false; + if (_zoom_enabled) + { + QApplication::restoreOverrideCursor(); + _zoom_enabled = false; + } QwtPlotPicker::widgetMouseReleaseEvent(me); this->setTrackerMode(AlwaysOff); } From ac1f90fd90302b935498e5446069fd8722e5de04 Mon Sep 17 00:00:00 2001 From: Victor Voronin Date: Sun, 21 Dec 2025 22:56:11 +0100 Subject: [PATCH 3/3] add swap pan/zoom to preferences dialog --- plotjuggler_app/mainwindow.cpp | 15 ++++++++++ plotjuggler_app/preferences_dialog.cpp | 38 ++++++++++++++++---------- plotjuggler_app/preferences_dialog.ui | 24 +++++++++++++++- 3 files changed, 61 insertions(+), 16 deletions(-) diff --git a/plotjuggler_app/mainwindow.cpp b/plotjuggler_app/mainwindow.cpp index d71db2479..e2d32f4af 100644 --- a/plotjuggler_app/mainwindow.cpp +++ b/plotjuggler_app/mainwindow.cpp @@ -822,6 +822,10 @@ void MainWindow::onPlotAdded(PlotWidget* plot) plot->setDefaultStyle(ui->buttonDots->isChecked() ? PlotWidgetBase::LINES_AND_DOTS : PlotWidgetBase::LINES); + QSettings settings; + bool swap_pan_zoom = settings.value("Preferences::swap_pan_zoom", false).toBool(); + plot->setSwapZoomPan(swap_pan_zoom); + if (ui->buttonReferencePoint->isChecked() && _reference_tracker_time.has_value()) { plot->onReferenceLineChecked(ui->buttonReferencePoint->isChecked(), @@ -3134,6 +3138,7 @@ void MainWindow::on_actionPreferences_triggered() { QSettings settings; QString prev_style = settings.value("Preferences::theme", "light").toString(); + bool prev_swap_pan_zoom = settings.value("Preferences::swap_pan_zoom", false).toBool(); PreferencesDialog dialog; dialog.exec(); @@ -3144,6 +3149,16 @@ void MainWindow::on_actionPreferences_triggered() { loadStyleSheet(tr(":/resources/stylesheet_%1.qss").arg(theme)); } + + // Apply swap pan/zoom preference to all existing plots + bool swap_pan_zoom = settings.value("Preferences::swap_pan_zoom", false).toBool(); + if (swap_pan_zoom != prev_swap_pan_zoom) + { + auto visitor = [swap_pan_zoom](PlotWidget* plot) { + plot->setSwapZoomPan(swap_pan_zoom); + }; + forEachWidget(visitor); + } } void MainWindow::on_playbackStep_valueChanged(double step) diff --git a/plotjuggler_app/preferences_dialog.cpp b/plotjuggler_app/preferences_dialog.cpp index bb2b0f726..d9f478001 100644 --- a/plotjuggler_app/preferences_dialog.cpp +++ b/plotjuggler_app/preferences_dialog.cpp @@ -17,6 +17,8 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) { ui->setupUi(this); QSettings settings; + + // Apperance QString theme = settings.value("Preferences::theme", "light").toString(); if (theme == "dark") { @@ -27,9 +29,19 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) ui->comboBoxTheme->setCurrentIndex(0); } + bool use_separator = settings.value("Preferences::use_separator", true).toBool(); + ui->checkBoxSeparator->setChecked(use_separator); + + bool use_opengl = settings.value("Preferences::use_opengl", true).toBool(); + ui->checkBoxOpenGL->setChecked(use_opengl); + int precision = settings.value("Preferences::precision", 3).toInt(); ui->comboBoxPrecision->setCurrentIndex(precision); + bool no_splash = settings.value("Preferences::no_splash", false).toBool(); + ui->checkBoxSkipSplash->setChecked(no_splash); + + // Behavior bool use_plot_color_index = settings.value("Preferences::use_plot_color_index", false).toBool(); bool remember_color = settings.value("Preferences::remember_color", true).toBool(); @@ -37,18 +49,6 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) ui->radioLocalColorIndex->setChecked(use_plot_color_index); ui->radioGlobalColorIndex->setChecked(!use_plot_color_index); - ui->pushButtonAdd->setIcon(LoadSvg(":/resources/svg/add_tab.svg", theme)); - ui->pushButtonRemove->setIcon(LoadSvg(":/resources/svg/trash.svg", theme)); - - bool use_separator = settings.value("Preferences::use_separator", true).toBool(); - ui->checkBoxSeparator->setChecked(use_separator); - - bool use_opengl = settings.value("Preferences::use_opengl", true).toBool(); - ui->checkBoxOpenGL->setChecked(use_opengl); - - bool no_splash = settings.value("Preferences::no_splash", false).toBool(); - ui->checkBoxSkipSplash->setChecked(no_splash); - bool autozoom_visibility = settings.value("Preferences::autozoom_visibility", true).toBool(); ui->checkBoxAutoZoomVisibility->setChecked(autozoom_visibility); @@ -59,14 +59,21 @@ PreferencesDialog::PreferencesDialog(QWidget* parent) settings.value("Preferences::autozoom_filter_applied", true).toBool(); ui->checkBoxAutoZoomFilter->setChecked(autozoom_filter_applied); - bool truncation_check = settings.value("Preferences::truncation_check", true).toBool(); - ui->checkBoxTruncation->setChecked(truncation_check); - QSize export_plot = settings.value("Preferences::export_plot_size", default_document_dimentions).toSize(); ui->spinBoxExportX->setValue(export_plot.width()); ui->spinBoxExportY->setValue(export_plot.height()); + bool swap_pan_zoom = settings.value("Preferences::swap_pan_zoom", false).toBool(); + ui->checkBoxSwapPanZoom->setChecked(swap_pan_zoom); + + bool truncation_check = settings.value("Preferences::truncation_check", true).toBool(); + ui->checkBoxTruncation->setChecked(truncation_check); + + // Plugins + ui->pushButtonAdd->setIcon(LoadSvg(":/resources/svg/add_tab.svg", theme)); + ui->pushButtonRemove->setIcon(LoadSvg(":/resources/svg/trash.svg", theme)); + //--------------- auto custom_plugin_folders = settings.value("Preferences::plugin_folders", true).toStringList(); for (const auto& folder : custom_plugin_folders) @@ -113,6 +120,7 @@ void PreferencesDialog::on_buttonBox_accepted() settings.setValue("Preferences::truncation_check", ui->checkBoxTruncation->isChecked()); settings.setValue("Preferences::export_plot_size", QSize{ ui->spinBoxExportX->value(), ui->spinBoxExportY->value() }); + settings.setValue("Preferences::swap_pan_zoom", ui->checkBoxSwapPanZoom->isChecked()); QStringList plugin_folders; for (int row = 0; row < ui->listWidgetCustom->count(); row++) diff --git a/plotjuggler_app/preferences_dialog.ui b/plotjuggler_app/preferences_dialog.ui index b8b1f5853..3f0ddf3cd 100644 --- a/plotjuggler_app/preferences_dialog.ui +++ b/plotjuggler_app/preferences_dialog.ui @@ -7,7 +7,7 @@ 0 0 545 - 545 + 600 @@ -452,6 +452,28 @@ + + + + Mouse Interaction: + + + + + + <html><head/><body><p>Default: Left-click to zoom, Ctrl+Left-click to pan</p><p>Swapped: Left-click to pan, Ctrl+Left-click to zoom</p></body></html> + + + Swap pan/zoom mouse action modifiers + + + false + + + + + +