IMPROVED/REWORKED: zomm/pan by mouse-wheel: now there are modes that support zoomin AND panning by trakpad and mouse-wheel simultaneously!

This commit is contained in:
jkriege2 2023-06-16 13:41:47 +02:00
parent 5240cf8681
commit 9762b44b7d
7 changed files with 92 additions and 47 deletions

View File

@ -35,6 +35,7 @@ Changes, compared to \ref page_whatsnew_V4_0_0 "v4.0.0" include:
<li>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...)</li> <li>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...)</li>
<li>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</li> <li>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</li>
<li>IMPROVED/REWORKED: coordinate axis code was refactored</li> <li>IMPROVED/REWORKED: coordinate axis code was refactored</li>
<li>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 <code>jkqtpmwaZoomByWheelAndTrackpadPan</code> was introduced into <code>JKQTPMouseWheelActions</code> und is set as default mode: Here JKQTPlotter tries to distinguish the QWheelEvent s sent by an actual mouse wheel and a trackpad.</li>
<li>NEW: JKQTPFilledCurveXGraph and JKQTPFilledCurveYGraph can now plot wiggle plots with different fill styles above and below the baseline (feature request <a href="https://github.com/jkriege2/JKQtPlotter/issues/68">#68 Wiggle Plots</a> from <a href="https://github.com/xichaoqiang">user:xichaoqiang</a> </li> <li>NEW: JKQTPFilledCurveXGraph and JKQTPFilledCurveYGraph can now plot wiggle plots with different fill styles above and below the baseline (feature request <a href="https://github.com/jkriege2/JKQtPlotter/issues/68">#68 Wiggle Plots</a> from <a href="https://github.com/xichaoqiang">user:xichaoqiang</a> </li>
<li>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!</li> <li>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!</li>
<li>NEW: new "seaborn" style for plots, see \ref jkqtpplotter_styling </li> <li>NEW: new "seaborn" style for plots, see \ref jkqtpplotter_styling </li>

View File

@ -211,6 +211,7 @@ TestUserInteraction::TestUserInteraction(QWidget *parent) :
layForm->addRow("mouse action: mouse wheel, no modifiers", cmbMouseWheelAction); layForm->addRow("mouse action: mouse wheel, no modifiers", cmbMouseWheelAction);
cmbMouseWheelAction->addItem("jkqtpmwaZoomByWheel"); cmbMouseWheelAction->addItem("jkqtpmwaZoomByWheel");
cmbMouseWheelAction->addItem("jkqtpmwaPanByWheel"); cmbMouseWheelAction->addItem("jkqtpmwaPanByWheel");
cmbMouseWheelAction->addItem("jkqtpmwaZoomByWheelAndTrackpadPan");
cmbMouseWheelAction->addItem("NoAction"); cmbMouseWheelAction->addItem("NoAction");
cmbMouseWheelAction->setCurrentIndex(0); cmbMouseWheelAction->setCurrentIndex(0);
connect(cmbMouseWheelAction, SIGNAL(currentIndexChanged(int)), this, SLOT(setMouseWheelNoModAction(int))); connect(cmbMouseWheelAction, SIGNAL(currentIndexChanged(int)), this, SLOT(setMouseWheelNoModAction(int)));

View File

@ -1010,67 +1010,106 @@ void JKQTPlotter::wheelEvent ( QWheelEvent * event ) {
const double wheel_x=event->position().x(); const double wheel_x=event->position().x();
const double wheel_y=event->position().y(); const double wheel_y=event->position().y();
#else #else
const int wheel_x=event->pos().x(); const double wheel_x=event->pos().x();
const int wheel_y=event->pos().y(); const double wheel_y=event->pos().y();
#endif #endif
const QPoint angleDelta=event->angleDelta();
//qDebug()<<"wheelEvent("<<event->modifiers()<<"): plotterStyle.registeredMouseWheelActions="<<plotterStyle.registeredMouseWheelActions; QRectF zoomRect= QRectF(QPointF(plotter->x2p(getXAxis()->getMin()),plotter->y2p(getYAxis()->getMax())), QPointF(plotter->x2p(getXAxis()->getMax()),plotter->y2p(getYAxis()->getMin())));
//qDebug()<<"wheelEvent: "<<event->type()<<", "<<event->modifiers()<<", "<<angleDelta<<", "<<event->pixelDelta()<<", "<<event->inverted()<<", "<<event->source()<<", "<<event->buttons();
bool foundIT=false; bool foundIT=false;
auto itAction=findMatchingMouseWheelAction(event->modifiers(), &foundIT); auto itAction=findMatchingMouseWheelAction(event->modifiers(), &foundIT);
//qDebug()<<"wheelEvent("<<event->modifiers()<<"): plotterStyle.registeredMouseWheelActions="<<plotterStyle.registeredMouseWheelActions;
//qDebug()<<"wheelEvent("<<event->modifiers()<<"): itAction="<<itAction.key()<<","<<itAction.value()<<" !=end:"<<(itAction!=plotterStyle.registeredMouseWheelActions.end())<<" ==end:"<<(itAction==plotterStyle.registeredMouseWheelActions.end()); enum {
acZoom,
acPan,
acNone
} acTodo=acNone;
QPointF d(0,0); // for acPan, the shift of the ROI
double factor=1; // for zooming, the zoom-factor
if (foundIT) { if (foundIT) {
if (itAction.value()==JKQTPMouseWheelActions::jkqtpmwaZoomByWheel) { if (itAction.value()==JKQTPMouseWheelActions::jkqtpmwaZoomByWheel) {
//if (act==JKQTPMouseWheelActions::jkqtpmwaZoomByWheel) { acTodo=acZoom;
//qDebug()<<"wheelEvent("<<event->modifiers()<<"):ZoomByWheel"; if (abs(angleDelta.y())>30 && angleDelta.x()==0) {
const double factor=pow(2.0, 1.0*static_cast<double>(event->angleDelta().y())/120.0)*2.0; factor=pow(2.0, 1.0*static_cast<double>(angleDelta.y())/120.0);
double xmin=plotter->p2x(static_cast<double>(wheel_x)/magnification-static_cast<double>(plotter->getPlotWidth())/factor); } else if (abs(angleDelta.x())>30 && angleDelta.y()==0) {
double xmax=plotter->p2x(static_cast<double>(wheel_x)/magnification+static_cast<double>(plotter->getPlotWidth())/factor); factor=pow(2.0, 1.0*static_cast<double>(angleDelta.x())/120.0);
double ymin=plotter->p2y(static_cast<double>(wheel_y)/magnification-static_cast<double>(getPlotYOffset())+static_cast<double>(plotter->getPlotHeight())/factor);
double ymax=plotter->p2y(static_cast<double>(wheel_y)/magnification-static_cast<double>(getPlotYOffset())-static_cast<double>(plotter->getPlotHeight())/factor);
if ( (wheel_x/magnification<plotter->getInternalPlotBorderLeft()) || (wheel_x/magnification>plotter->getPlotWidth()+plotter->getInternalPlotBorderLeft()) ) {
xmin=getXMin();
xmax=getXMax();
} else if (((wheel_y-getPlotYOffset())/magnification<plotter->getInternalPlotBorderTop()) || ((wheel_y-getPlotYOffset())/magnification>plotter->getPlotHeight()+plotter->getInternalPlotBorderTop()) ) {
ymin=getYMin();
ymax=getYMax();
} }
plotter->setXY(xmin, xmax, ymin, ymax, true); } else if (itAction.value()==JKQTPMouseWheelActions::jkqtpmwaZoomByWheelAndTrackpadPan) {
} else if (itAction.value()==JKQTPMouseWheelActions::jkqtpmwaPanByWheel) { if (abs(angleDelta.x())<30 && abs(angleDelta.y())<30) {
//} else if (act==JKQTPMouseWheelActions::jkqtpmwaPanByWheel) { // this heuristics recognizes pan-gestures on a track-pad.
//qDebug()<<"wheelEvent("<<event->modifiers()<<"):PanByWheel"; // These are converted by Qt to wheelEvents with small angleDelta()-Values
QRectF zoomRect= QRectF(QPointF(plotter->x2p(getXAxis()->getMin()),plotter->y2p(getYAxis()->getMax())), QPointF(plotter->x2p(getXAxis()->getMax()),plotter->y2p(getYAxis()->getMin()))); // typical angleDelta values are 120 (1/10 degree) or above, here we use 30
QPointF d=QPointF(event->angleDelta().x()/120.0*zoomRect.width()/10.0, // to accomodate for finer resolved mouse-wheels
event->angleDelta().y()/120.0*zoomRect.height()/10.0); //
if (d.x()<-100) d.setX(-100); // unfortunately there is no other way to distinguish these cases, as Qt does not transport
if (d.x()>100) d.setX(100); // the source of the QWheelEvent!
if (d.y()<-100) d.setY(-100); acTodo=acPan;
if (d.y()>100) d.setY(100); d=angleDelta;
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/magnification<plotter->getInternalPlotBorderLeft()) || (wheel_x/magnification>plotter->getPlotWidth()+plotter->getInternalPlotBorderLeft()) ) {
zoomRect.translate(0, d.y());
} else if (((wheel_y-getPlotYOffset())/magnification<plotter->getInternalPlotBorderTop()) || ((wheel_y-getPlotYOffset())/magnification>plotter->getPlotHeight()+plotter->getInternalPlotBorderTop()) ) {
zoomRect.translate(d.x(), 0);
} else { } 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<double>(angleDelta.y())/120.0);
} else if (abs(angleDelta.x())>30 && angleDelta.y()==0) {
factor=pow(2.0, 1.0*static_cast<double>(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<double>(-100, d.x(), 100));
d.setY(jkqtp_bounded<double>(-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: "<<acTodo<<" factor="<<factor<<" d="<<d;
if (acTodo==acZoom && factor!=1.0) {
double xmin=plotter->p2x(static_cast<double>(wheel_x)/magnification-static_cast<double>(plotter->getPlotWidth()*0.5)/factor);
double xmax=plotter->p2x(static_cast<double>(wheel_x)/magnification+static_cast<double>(plotter->getPlotWidth()*0.5)/factor);
double ymin=plotter->p2y(static_cast<double>(wheel_y)/magnification-static_cast<double>(getPlotYOffset())+static_cast<double>(plotter->getPlotHeight()*0.5)/factor);
double ymax=plotter->p2y(static_cast<double>(wheel_y)/magnification-static_cast<double>(getPlotYOffset())-static_cast<double>(plotter->getPlotHeight()*0.5)/factor);
if ( (wheel_x/magnification<plotter->getInternalPlotBorderLeft()) || (wheel_x/magnification>plotter->getPlotWidth()+plotter->getInternalPlotBorderLeft()) ) {
xmin=getXMin();
xmax=getXMax();
} else if (((wheel_y-getPlotYOffset())/magnification<plotter->getInternalPlotBorderTop()) || ((wheel_y-getPlotYOffset())/magnification>plotter->getPlotHeight()+plotter->getInternalPlotBorderTop()) ) {
ymin=getYMin();
ymax=getYMax();
}
//qDebug()<<" zoom: factor="<<factor;
plotter->setXY(xmin, xmax, ymin, ymax, true);
} else if (acTodo==acPan && (d.x()!=0 || d.y()!=0)) {
//qDebug()<<" pan: d="<<d;
if ( (wheel_x/magnification<plotter->getInternalPlotBorderLeft()) || (wheel_x/magnification>plotter->getPlotWidth()+plotter->getInternalPlotBorderLeft()) ) {
zoomRect.translate(0, d.y());
} else if (((wheel_y-getPlotYOffset())/magnification<plotter->getInternalPlotBorderTop()) || ((wheel_y-getPlotYOffset())/magnification>plotter->getPlotHeight()+plotter->getInternalPlotBorderTop()) ) {
zoomRect.translate(d.x(), 0);
} else {
zoomRect.translate(d.x(), d.y());
}
setXY(plotter->p2x(zoomRect.left()), plotter->p2x(zoomRect.right()), plotter->p2y(zoomRect.bottom()), plotter->p2y(zoomRect.top()), true);
}
event->accept(); event->accept();
emit plotMouseWheelOperated(plotter->p2x(wheel_x/magnification), plotter->p2y((wheel_y-getPlotYOffset())/magnification), event->modifiers(), event->angleDelta().x(), event->angleDelta().y()); emit plotMouseWheelOperated(plotter->p2x(wheel_x/magnification), plotter->p2y((wheel_y-getPlotYOffset())/magnification), event->modifiers(), angleDelta.x(), angleDelta.y());
updateCursor(); updateCursor();
currentMouseDragAction.clear(); currentMouseDragAction.clear();
} }
bool JKQTPlotter::event(QEvent* event) {
//qDebug()<<"event: "<<event->type()<<", "<<event->isAccepted();
return QWidget::event(event);
}
int JKQTPlotter::getPlotYOffset() { int JKQTPlotter::getPlotYOffset() {
int plotYOffset=0; int plotYOffset=0;
if (plotterStyle.displayMousePosition) { if (plotterStyle.displayMousePosition) {

View File

@ -1704,7 +1704,8 @@ class JKQTPLOTTER_LIB_EXPORT JKQTPlotter: public QWidget {
/** \brief action that activates the pan view tool (override!) */ /** \brief action that activates the pan view tool (override!) */
QAction* actMouseLeftAsPanView; 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 /** \brief while the window is resized, the plot is only redrawn after a restartable delay, implemented by this function and resizeTimer
* \internal * \internal
* \see resizeTimer * \see resizeTimer

View File

@ -1,7 +1,6 @@
#include "jkqtplotterstyle.h" #include "jkqtplotterstyle.h"
#include <QApplication> #include <QApplication>
#include <QDebug> #include <QDebug>
#include "jkqtcommon/jkqttools.h"
#include "jkqtplotter/jkqtptools.h" #include "jkqtplotter/jkqtptools.h"
JKQTPlotterStyle::JKQTPlotterStyle(): JKQTPlotterStyle::JKQTPlotterStyle():
@ -32,7 +31,8 @@ JKQTPlotterStyle::JKQTPlotterStyle():
registeredMouseDragActionModes[qMakePair<Qt::MouseButton, Qt::KeyboardModifiers>(Qt::LeftButton, Qt::NoModifier)]=JKQTPMouseDragActions::jkqtpmdaZoomByRectangle; registeredMouseDragActionModes[qMakePair<Qt::MouseButton, Qt::KeyboardModifiers>(Qt::LeftButton, Qt::NoModifier)]=JKQTPMouseDragActions::jkqtpmdaZoomByRectangle;
registeredMouseDragActionModes[qMakePair<Qt::MouseButton, Qt::KeyboardModifiers>(Qt::LeftButton, Qt::ControlModifier)]=JKQTPMouseDragActions::jkqtpmdaPanPlotOnMove; registeredMouseDragActionModes[qMakePair<Qt::MouseButton, Qt::KeyboardModifiers>(Qt::LeftButton, Qt::ControlModifier)]=JKQTPMouseDragActions::jkqtpmdaPanPlotOnMove;
registeredMouseDoubleClickActions[qMakePair<Qt::MouseButton, Qt::KeyboardModifiers>(Qt::LeftButton, Qt::NoModifier)]=JKQTPMouseDoubleClickActions::jkqtpdcaClickMovesViewport; registeredMouseDoubleClickActions[qMakePair<Qt::MouseButton, Qt::KeyboardModifiers>(Qt::LeftButton, Qt::NoModifier)]=JKQTPMouseDoubleClickActions::jkqtpdcaClickMovesViewport;
registeredMouseWheelActions[Qt::NoModifier]=JKQTPMouseWheelActions::jkqtpmwaZoomByWheel; registeredMouseWheelActions[Qt::NoModifier]=JKQTPMouseWheelActions::jkqtpmwaZoomByWheelAndTrackpadPan;
registeredMouseWheelActions[Qt::ControlModifier]=JKQTPMouseWheelActions::jkqtpmwaZoomByWheel;
//qDebug()<<"JKQTPlotterStyle(): registeredMouseWheelActions="<<registeredMouseWheelActions; //qDebug()<<"JKQTPlotterStyle(): registeredMouseWheelActions="<<registeredMouseWheelActions;
} }
@ -124,13 +124,13 @@ void JKQTPlotterStyle::loadSettings(const QSettings &settings, const QString &gr
id=readID(k, group+"actions/mouse_wheel"); id=readID(k, group+"actions/mouse_wheel");
if (id>=0) { if (id>=0) {
auto modifiers=jkqtp_String2KeyboardModifiers(settings.value(group+"actions/mouse_wheel"+QString::number(id)+"/modifiers", Qt::NoModifier).toString()); 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; registeredMouseWheelActions[modifiers]=action;
} }
id=readID(k, group+"actions/mouse_move"); id=readID(k, group+"actions/mouse_move");
if (id>=0) { if (id>=0) {
auto modifiers=jkqtp_String2KeyboardModifiers(settings.value(group+"actions/mouse_move"+QString::number(id)+"/modifiers", Qt::NoModifier).toString()); 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; registeredMouseMoveActions[modifiers]=action;
} }
} }

View File

@ -331,6 +331,7 @@ JKQTPMouseWheelActions String2JKQTPMouseWheelActions(const QString &act)
QString s=act.trimmed().toLower(); QString s=act.trimmed().toLower();
if (s=="jkqtpmwazoombywheel"||s=="zoombywheel" ||s=="zoom") return jkqtpmwaZoomByWheel; if (s=="jkqtpmwazoombywheel"||s=="zoombywheel" ||s=="zoom") return jkqtpmwaZoomByWheel;
if (s=="jkqtpmwapanbywheel"||s=="panbywheel"||s=="pan") return jkqtpmwaPanByWheel; if (s=="jkqtpmwapanbywheel"||s=="panbywheel"||s=="pan") return jkqtpmwaPanByWheel;
if (s=="jkqtpmwazoombywheelandtrackpadpan"||s=="zoombywheelortrackpan"||s=="zoomortrackpan") return jkqtpmwaZoomByWheelAndTrackpadPan;
return jkqtpmwaZoomByWheel; return jkqtpmwaZoomByWheel;
} }
@ -339,6 +340,7 @@ QString JKQTPMouseWheelActions2String(JKQTPMouseWheelActions act)
{ {
if (act==jkqtpmwaZoomByWheel) return "zoom"; if (act==jkqtpmwaZoomByWheel) return "zoom";
if (act==jkqtpmwaPanByWheel) return "pan"; if (act==jkqtpmwaPanByWheel) return "pan";
if (act==jkqtpmwaZoomByWheelAndTrackpadPan) return "zoomortrackpan";
return "unknown"; return "unknown";
} }

View File

@ -162,6 +162,7 @@ JKQTPLOTTER_LIB_EXPORT JKQTPMouseDoubleClickActions String2JKQTPMouseDoubleClick
enum JKQTPMouseWheelActions { enum JKQTPMouseWheelActions {
jkqtpmwaZoomByWheel=0, /*!< \brief use the mouse-wheel for zooming */ jkqtpmwaZoomByWheel=0, /*!< \brief use the mouse-wheel for zooming */
jkqtpmwaPanByWheel, /*!< \brief use the mouse-wheel for panning the plot */ 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 <a href="http://doc.qt.io/qt-5/qstring.html">QString</a> /** \brief convert a JKQTPMouseWheelActions to a <a href="http://doc.qt.io/qt-5/qstring.html">QString</a>