From 9762b44b7d7b241c353812db8804952a621b5f4d Mon Sep 17 00:00:00 2001 From: jkriege2 Date: Fri, 16 Jun 2023 13:41:47 +0200 Subject: [PATCH] IMPROVED/REWORKED: zomm/pan by mouse-wheel: now there are modes that support zoomin AND panning by trakpad and mouse-wheel simultaneously! --- doc/dox/whatsnew.dox | 1 + .../test_user_interaction.cpp | 3 +- lib/jkqtplotter/jkqtplotter.cpp | 121 ++++++++++++------ lib/jkqtplotter/jkqtplotter.h | 3 +- lib/jkqtplotter/jkqtplotterstyle.cpp | 8 +- lib/jkqtplotter/jkqtptools.cpp | 2 + lib/jkqtplotter/jkqtptools.h | 1 + 7 files changed, 92 insertions(+), 47 deletions(-) diff --git a/doc/dox/whatsnew.dox b/doc/dox/whatsnew.dox index d631fc3d7c..5657fe49ad 100644 --- a/doc/dox/whatsnew.dox +++ b/doc/dox/whatsnew.dox @@ -35,6 +35,7 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
  • IMPROVED/REWORKED: reworked JKQTPErrorPlotstyle and error indicator plotting so error-inidcators can be specified as ORed combination of flags from JKQTPErrorPlotstyleElements, added additional error indicator styles (half-bars, arrows...)
  • IMPROVED/REWORKED: reworked JKQTPCADrawMode and coordinate axis drawing so the draw mide can be specified as ORed combination of flags from JKQTPCADrawModeElements, added flags to draw arrows at the end of the axis line
  • IMPROVED/REWORKED: coordinate axis code was refactored
  • +
  • IMPROVED/REWORKED: zomm/pan by mouse-wheel: now there are modes that support zoomin AND panning by trakpad and mouse-wheel simultaneously! This can only be implemented using heuristics, due to the way that Qt handles track-pad events, but the current solution should at least improve the behaviour seen before. Mainly jkqtpmwaZoomByWheelAndTrackpadPan was introduced into JKQTPMouseWheelActions und is set as default mode: Here JKQTPlotter tries to distinguish the QWheelEvent s sent by an actual mouse wheel and a trackpad.
  • NEW: JKQTPFilledCurveXGraph and JKQTPFilledCurveYGraph can now plot wiggle plots with different fill styles above and below the baseline (feature request #68 Wiggle Plots from user:xichaoqiang
  • NEW/BREAKING CHANGE: data tooltip can now also be shown when "just" moving the mouse (so far this was only possible when dragging the mouse with a button pressed). This also removes JKQtPlotter::getActMouseLeftAsToolTip() and adds JKQtPlotter::getActMouseMoveToolTip() instead! Also the default toolbars and context menus changed!
  • NEW: new "seaborn" style for plots, see \ref jkqtpplotter_styling
  • diff --git a/examples/user_interaction/test_user_interaction.cpp b/examples/user_interaction/test_user_interaction.cpp index 85b2042b44..8fd66887c9 100644 --- a/examples/user_interaction/test_user_interaction.cpp +++ b/examples/user_interaction/test_user_interaction.cpp @@ -5,7 +5,7 @@ * \see \ref JKQTPLOTTER_USERINTERACTION */ -#include "jkqtpexampleapplication.h" +#include "jkqtpexampleapplication.h" #include "test_user_interaction.h" #include #include @@ -211,6 +211,7 @@ TestUserInteraction::TestUserInteraction(QWidget *parent) : layForm->addRow("mouse action: mouse wheel, no modifiers", cmbMouseWheelAction); cmbMouseWheelAction->addItem("jkqtpmwaZoomByWheel"); cmbMouseWheelAction->addItem("jkqtpmwaPanByWheel"); + cmbMouseWheelAction->addItem("jkqtpmwaZoomByWheelAndTrackpadPan"); cmbMouseWheelAction->addItem("NoAction"); cmbMouseWheelAction->setCurrentIndex(0); connect(cmbMouseWheelAction, SIGNAL(currentIndexChanged(int)), this, SLOT(setMouseWheelNoModAction(int))); diff --git a/lib/jkqtplotter/jkqtplotter.cpp b/lib/jkqtplotter/jkqtplotter.cpp index 40898da8e3..d3d6253723 100644 --- a/lib/jkqtplotter/jkqtplotter.cpp +++ b/lib/jkqtplotter/jkqtplotter.cpp @@ -1010,67 +1010,106 @@ void JKQTPlotter::wheelEvent ( QWheelEvent * event ) { const double wheel_x=event->position().x(); const double wheel_y=event->position().y(); #else - const int wheel_x=event->pos().x(); - const int wheel_y=event->pos().y(); + const double wheel_x=event->pos().x(); + const double wheel_y=event->pos().y(); #endif - - //qDebug()<<"wheelEvent("<modifiers()<<"): plotterStyle.registeredMouseWheelActions="<angleDelta(); + QRectF zoomRect= QRectF(QPointF(plotter->x2p(getXAxis()->getMin()),plotter->y2p(getYAxis()->getMax())), QPointF(plotter->x2p(getXAxis()->getMax()),plotter->y2p(getYAxis()->getMin()))); + //qDebug()<<"wheelEvent: "<type()<<", "<modifiers()<<", "<pixelDelta()<<", "<inverted()<<", "<source()<<", "<buttons(); bool foundIT=false; auto itAction=findMatchingMouseWheelAction(event->modifiers(), &foundIT); - //qDebug()<<"wheelEvent("<modifiers()<<"): plotterStyle.registeredMouseWheelActions="<modifiers()<<"): itAction="<modifiers()<<"):ZoomByWheel"; - const double factor=pow(2.0, 1.0*static_cast(event->angleDelta().y())/120.0)*2.0; - double xmin=plotter->p2x(static_cast(wheel_x)/magnification-static_cast(plotter->getPlotWidth())/factor); - double xmax=plotter->p2x(static_cast(wheel_x)/magnification+static_cast(plotter->getPlotWidth())/factor); - double ymin=plotter->p2y(static_cast(wheel_y)/magnification-static_cast(getPlotYOffset())+static_cast(plotter->getPlotHeight())/factor); - double ymax=plotter->p2y(static_cast(wheel_y)/magnification-static_cast(getPlotYOffset())-static_cast(plotter->getPlotHeight())/factor); - if ( (wheel_x/magnificationgetInternalPlotBorderLeft()) || (wheel_x/magnification>plotter->getPlotWidth()+plotter->getInternalPlotBorderLeft()) ) { - xmin=getXMin(); - xmax=getXMax(); - } else if (((wheel_y-getPlotYOffset())/magnificationgetInternalPlotBorderTop()) || ((wheel_y-getPlotYOffset())/magnification>plotter->getPlotHeight()+plotter->getInternalPlotBorderTop()) ) { - ymin=getYMin(); - ymax=getYMax(); + acTodo=acZoom; + if (abs(angleDelta.y())>30 && angleDelta.x()==0) { + factor=pow(2.0, 1.0*static_cast(angleDelta.y())/120.0); + } else if (abs(angleDelta.x())>30 && angleDelta.y()==0) { + factor=pow(2.0, 1.0*static_cast(angleDelta.x())/120.0); } - plotter->setXY(xmin, xmax, ymin, ymax, true); - } else if (itAction.value()==JKQTPMouseWheelActions::jkqtpmwaPanByWheel) { - //} else if (act==JKQTPMouseWheelActions::jkqtpmwaPanByWheel) { - //qDebug()<<"wheelEvent("<modifiers()<<"):PanByWheel"; - QRectF zoomRect= QRectF(QPointF(plotter->x2p(getXAxis()->getMin()),plotter->y2p(getYAxis()->getMax())), QPointF(plotter->x2p(getXAxis()->getMax()),plotter->y2p(getYAxis()->getMin()))); - QPointF d=QPointF(event->angleDelta().x()/120.0*zoomRect.width()/10.0, - event->angleDelta().y()/120.0*zoomRect.height()/10.0); - if (d.x()<-100) d.setX(-100); - if (d.x()>100) d.setX(100); - if (d.y()<-100) d.setY(-100); - if (d.y()>100) d.setY(100); - if (d.x()>=0 && d.x()<10) d.setX(10); - if (d.x()<0 && d.x()>-10) d.setX(-10); - if (d.y()>=0 && d.y()<10) d.setY(10); - if (d.y()<0 && d.y()>-10) d.setY(-10); - if ( (wheel_x/magnificationgetInternalPlotBorderLeft()) || (wheel_x/magnification>plotter->getPlotWidth()+plotter->getInternalPlotBorderLeft()) ) { - zoomRect.translate(0, d.y()); - } else if (((wheel_y-getPlotYOffset())/magnificationgetInternalPlotBorderTop()) || ((wheel_y-getPlotYOffset())/magnification>plotter->getPlotHeight()+plotter->getInternalPlotBorderTop()) ) { - zoomRect.translate(d.x(), 0); + } else if (itAction.value()==JKQTPMouseWheelActions::jkqtpmwaZoomByWheelAndTrackpadPan) { + if (abs(angleDelta.x())<30 && abs(angleDelta.y())<30) { + // this heuristics recognizes pan-gestures on a track-pad. + // These are converted by Qt to wheelEvents with small angleDelta()-Values + // typical angleDelta values are 120 (1/10 degree) or above, here we use 30 + // to accomodate for finer resolved mouse-wheels + // + // unfortunately there is no other way to distinguish these cases, as Qt does not transport + // the source of the QWheelEvent! + acTodo=acPan; + d=angleDelta; } else { - zoomRect.translate(d.x(), d.y()); + acTodo=acZoom; + if (abs(angleDelta.y())>30 && angleDelta.x()==0) { + factor=pow(2.0, 1.0*static_cast(angleDelta.y())/120.0); + } else if (abs(angleDelta.x())>30 && angleDelta.y()==0) { + factor=pow(2.0, 1.0*static_cast(angleDelta.x())/120.0); + } } - setXY(plotter->p2x(zoomRect.left()), plotter->p2x(zoomRect.right()), plotter->p2y(zoomRect.bottom()), plotter->p2y(zoomRect.top()), true); + } else if (itAction.value()==JKQTPMouseWheelActions::jkqtpmwaPanByWheel) { + acTodo=acPan; + d=QPointF(angleDelta.x()/120.0*zoomRect.width()/10.0, + angleDelta.y()/120.0*zoomRect.height()/10.0); + // maximum shoft is 100 Pixels in either direction + d.setX(jkqtp_bounded(-100, d.x(), 100)); + d.setY(jkqtp_bounded(-100, d.y(), 100)); + // minimmum shift is 10 pixels, unles |shift|<1 + if (d.x()>=1 && d.x()<10) d.setX(10); + if (d.x()<=-1 && d.x()>-10) d.setX(-10); + if (d.y()>=1 && d.y()<10) d.setY(10); + if (d.y()<=-1 && d.y()>-10) d.setY(-10); } } + //qDebug()<<" action: "<type()<<", "<isAccepted(); + return QWidget::event(event); +} + int JKQTPlotter::getPlotYOffset() { int plotYOffset=0; if (plotterStyle.displayMousePosition) { diff --git a/lib/jkqtplotter/jkqtplotter.h b/lib/jkqtplotter/jkqtplotter.h index 81b111f1c9..d9aa37a20b 100644 --- a/lib/jkqtplotter/jkqtplotter.h +++ b/lib/jkqtplotter/jkqtplotter.h @@ -1704,7 +1704,8 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPlotter: public QWidget { /** \brief action that activates the pan view tool (override!) */ QAction* actMouseLeftAsPanView; - protected slots: + virtual bool event(QEvent *event) override; +protected slots: /** \brief while the window is resized, the plot is only redrawn after a restartable delay, implemented by this function and resizeTimer * \internal * \see resizeTimer diff --git a/lib/jkqtplotter/jkqtplotterstyle.cpp b/lib/jkqtplotter/jkqtplotterstyle.cpp index 2be3043149..a46d350feb 100644 --- a/lib/jkqtplotter/jkqtplotterstyle.cpp +++ b/lib/jkqtplotter/jkqtplotterstyle.cpp @@ -1,7 +1,6 @@ #include "jkqtplotterstyle.h" #include #include -#include "jkqtcommon/jkqttools.h" #include "jkqtplotter/jkqtptools.h" JKQTPlotterStyle::JKQTPlotterStyle(): @@ -32,7 +31,8 @@ JKQTPlotterStyle::JKQTPlotterStyle(): registeredMouseDragActionModes[qMakePair(Qt::LeftButton, Qt::NoModifier)]=JKQTPMouseDragActions::jkqtpmdaZoomByRectangle; registeredMouseDragActionModes[qMakePair(Qt::LeftButton, Qt::ControlModifier)]=JKQTPMouseDragActions::jkqtpmdaPanPlotOnMove; registeredMouseDoubleClickActions[qMakePair(Qt::LeftButton, Qt::NoModifier)]=JKQTPMouseDoubleClickActions::jkqtpdcaClickMovesViewport; - registeredMouseWheelActions[Qt::NoModifier]=JKQTPMouseWheelActions::jkqtpmwaZoomByWheel; + registeredMouseWheelActions[Qt::NoModifier]=JKQTPMouseWheelActions::jkqtpmwaZoomByWheelAndTrackpadPan; + registeredMouseWheelActions[Qt::ControlModifier]=JKQTPMouseWheelActions::jkqtpmwaZoomByWheel; //qDebug()<<"JKQTPlotterStyle(): registeredMouseWheelActions="<=0) { auto modifiers=jkqtp_String2KeyboardModifiers(settings.value(group+"actions/mouse_wheel"+QString::number(id)+"/modifiers", Qt::NoModifier).toString()); - auto action=String2JKQTPMouseWheelActions(settings.value(group+"actions/mouse_wheel"+QString::number(id)+"/action", jkqtpmwaZoomByWheel).toString()); + auto action=String2JKQTPMouseWheelActions(settings.value(group+"actions/mouse_wheel"+QString::number(id)+"/action", jkqtpmwaZoomByWheelAndTrackpadPan).toString()); registeredMouseWheelActions[modifiers]=action; } id=readID(k, group+"actions/mouse_move"); if (id>=0) { auto modifiers=jkqtp_String2KeyboardModifiers(settings.value(group+"actions/mouse_move"+QString::number(id)+"/modifiers", Qt::NoModifier).toString()); - auto action=String2JKQTPMouseMoveActions(settings.value(group+"actions/mouse_move"+QString::number(id)+"/action", jkqtpmwaZoomByWheel).toString()); + auto action=String2JKQTPMouseMoveActions(settings.value(group+"actions/mouse_move"+QString::number(id)+"/action", jkqtpmmaToolTipForClosestDataPoint).toString()); registeredMouseMoveActions[modifiers]=action; } } diff --git a/lib/jkqtplotter/jkqtptools.cpp b/lib/jkqtplotter/jkqtptools.cpp index 16ed1d9f9b..76a98d35cd 100644 --- a/lib/jkqtplotter/jkqtptools.cpp +++ b/lib/jkqtplotter/jkqtptools.cpp @@ -331,6 +331,7 @@ JKQTPMouseWheelActions String2JKQTPMouseWheelActions(const QString &act) QString s=act.trimmed().toLower(); if (s=="jkqtpmwazoombywheel"||s=="zoombywheel" ||s=="zoom") return jkqtpmwaZoomByWheel; if (s=="jkqtpmwapanbywheel"||s=="panbywheel"||s=="pan") return jkqtpmwaPanByWheel; + if (s=="jkqtpmwazoombywheelandtrackpadpan"||s=="zoombywheelortrackpan"||s=="zoomortrackpan") return jkqtpmwaZoomByWheelAndTrackpadPan; return jkqtpmwaZoomByWheel; } @@ -339,6 +340,7 @@ QString JKQTPMouseWheelActions2String(JKQTPMouseWheelActions act) { if (act==jkqtpmwaZoomByWheel) return "zoom"; if (act==jkqtpmwaPanByWheel) return "pan"; + if (act==jkqtpmwaZoomByWheelAndTrackpadPan) return "zoomortrackpan"; return "unknown"; } diff --git a/lib/jkqtplotter/jkqtptools.h b/lib/jkqtplotter/jkqtptools.h index 4677360fd3..d0dd6061bb 100644 --- a/lib/jkqtplotter/jkqtptools.h +++ b/lib/jkqtplotter/jkqtptools.h @@ -162,6 +162,7 @@ JKQTPLOTTER_LIB_EXPORT JKQTPMouseDoubleClickActions String2JKQTPMouseDoubleClick enum JKQTPMouseWheelActions { jkqtpmwaZoomByWheel=0, /*!< \brief use the mouse-wheel for zooming */ jkqtpmwaPanByWheel, /*!< \brief use the mouse-wheel for panning the plot */ + jkqtpmwaZoomByWheelAndTrackpadPan, /*!< \brief use the mouse-wheel for zooming. In addition, this tries to recognize track-pad pan gestures and applies them. \note This is needed, because Qt converts track-pad zoom AND pan gestures to wheelEvents, but does not provide the source. Therefore a heuristics is required to interpret both! */ }; /** \brief convert a JKQTPMouseWheelActions to a QString