diff --git a/gui/include/Visualization/VisualizationGraphWidget.h b/gui/include/Visualization/VisualizationGraphWidget.h index 54a04f1..da6369f 100644 --- a/gui/include/Visualization/VisualizationGraphWidget.h +++ b/gui/include/Visualization/VisualizationGraphWidget.h @@ -86,6 +86,9 @@ public: /// Undo the last zoom done with a zoom box void undoZoom(); + void zoom(double factor, int center, Qt::Orientation orientation); + void move(double factor, Qt::Orientation orientation); + // IVisualizationWidget interface void accept(IVisualizationWidgetVisitor *visitor) override; bool canDrop(const Variable &variable) const override; @@ -123,6 +126,12 @@ protected: void closeEvent(QCloseEvent *event) override; void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; + void wheelEvent(QWheelEvent * event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mousePressEvent(QMouseEvent *event) override; + void keyReleaseEvent(QKeyEvent * event) override; + void keyPressEvent(QKeyEvent * event) override; QCustomPlot &plot() const noexcept; @@ -131,13 +140,13 @@ private: class VisualizationGraphWidgetPrivate; spimpl::unique_impl_ptr impl; - + QPoint _dragLastPos; private slots: /// Slot called when right clicking on the graph (displays a menu) void onGraphMenuRequested(const QPoint &pos) noexcept; /// Rescale the X axe to range parameter - void onRangeChanged(const QCPRange &t1, const QCPRange &t2); + void onRangeChanged(const QCPRange &newRange, const QCPRange &oldRange); /// Slot called when a mouse double click was made void onMouseDoubleClick(QMouseEvent *event) noexcept; diff --git a/gui/src/Visualization/VisualizationGraphWidget.cpp b/gui/src/Visualization/VisualizationGraphWidget.cpp index 0755f90..9bbc6a3 100644 --- a/gui/src/Visualization/VisualizationGraphWidget.cpp +++ b/gui/src/Visualization/VisualizationGraphWidget.cpp @@ -231,16 +231,22 @@ VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget impl->m_VerticalCursor = std::make_unique(&plot()); impl->m_VerticalCursor->setOrientation(Qt::Vertical); - connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress); - connect(ui->widget, &QCustomPlot::mouseRelease, this, - &VisualizationGraphWidget::onMouseRelease); - connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove); - connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel); - connect(ui->widget, &QCustomPlot::mouseDoubleClick, this, - &VisualizationGraphWidget::onMouseDoubleClick); - connect(ui->widget->xAxis, static_cast( - &QCPAxis::rangeChanged), - this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection); + // ↓ swhitch to this ASAP, VisualizationGraphWidget should intercept all UI events + this->setFocusPolicy(Qt::WheelFocus); + ui->widget->setAttribute(Qt::WA_TransparentForMouseEvents); + // connect(ui->widget, &QCustomPlot::mousePress, this, + // &VisualizationGraphWidget::onMousePress); connect(ui->widget, &QCustomPlot::mouseRelease, + // this, + // &VisualizationGraphWidget::onMouseRelease); + // connect(ui->widget, &QCustomPlot::mouseMove, this, + // &VisualizationGraphWidget::onMouseMove); connect(ui->widget, &QCustomPlot::mouseWheel, + // this, &VisualizationGraphWidget::onMouseWheel); connect(ui->widget, + // &QCustomPlot::mouseDoubleClick, this, + // &VisualizationGraphWidget::onMouseDoubleClick); + // connect(ui->widget->xAxis, static_cast( + // &QCPAxis::rangeChanged), + // this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection); // Activates menu when right clicking on the graph ui->widget->setContextMenuPolicy(Qt::CustomContextMenu); @@ -248,11 +254,12 @@ VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget &VisualizationGraphWidget::onGraphMenuRequested); //@TODO implement this :) -// connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(), -// &VariableController::onRequestDataLoading); + // connect(this, &VisualizationGraphWidget::requestDataLoading, + // &sqpApp->variableController(), + // &VariableController::onRequestDataLoading); -// connect(&sqpApp->variableController(), &VariableController2::updateVarDisplaying, this, -// &VisualizationGraphWidget::onUpdateVarDisplaying); + // connect(&sqpApp->variableController(), &VariableController2::updateVarDisplaying, this, + // &VisualizationGraphWidget::onUpdateVarDisplaying); // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable. plot().setPlottingHint(QCP::phFastPolylines, true); @@ -308,8 +315,8 @@ void VisualizationGraphWidget::addVariable(std::shared_ptr variable, D } //@TODO this is bad! when variable is moved to another graph it still fires // even if this has been deleted - connect(variable.get(),&Variable::updated,this, &VisualizationGraphWidget::variableUpdated); - this->onUpdateVarDisplaying(variable,range);//My bullshit + connect(variable.get(), &Variable::updated, this, &VisualizationGraphWidget::variableUpdated); + this->onUpdateVarDisplaying(variable, range); // My bullshit emit variableAdded(variable); } @@ -334,7 +341,7 @@ void VisualizationGraphWidget::removeVariable(std::shared_ptr variable } // Updates graph - ui->widget->replot(); + ui->widget->replot(QCustomPlot::rpQueuedReplot); } std::vector > VisualizationGraphWidget::variables() const @@ -342,7 +349,7 @@ std::vector > VisualizationGraphWidget::variables() co auto variables = std::vector >{}; for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap); it != std::cend(impl->m_VariableToPlotMultiMap); ++it) { - variables.push_back (it->first); + variables.push_back(it->first); } return variables; @@ -371,7 +378,7 @@ void VisualizationGraphWidget::setGraphRange(const DateTimeRange &range, bool ca } ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd); - ui->widget->replot(); + ui->widget->replot(QCustomPlot::rpQueuedReplot); if (calibration) { impl->m_IsCalibration = false; @@ -405,8 +412,8 @@ void VisualizationGraphWidget::addSelectionZones(const QVector &r plot().replot(QCustomPlot::rpQueuedReplot); } -VisualizationSelectionZoneItem *VisualizationGraphWidget::addSelectionZone(const QString &name, - const DateTimeRange &range) +VisualizationSelectionZoneItem * +VisualizationGraphWidget::addSelectionZone(const QString &name, const DateTimeRange &range) { // note: ownership is transfered to QCustomPlot auto zone = new VisualizationSelectionZoneItem(&plot()); @@ -445,6 +452,46 @@ void VisualizationGraphWidget::undoZoom() plot().replot(QCustomPlot::rpQueuedReplot); } +void VisualizationGraphWidget::zoom(double factor, int center, Qt::Orientation orientation) + +{ + auto oldRange = ui->widget->xAxis->range(); + QCPAxis *axis = ui->widget->axisRect()->rangeZoomAxis(orientation); + axis->scaleRange(factor, axis->pixelToCoord(center)); + if (orientation == Qt::Horizontal) + onRangeChanged(axis->range(), oldRange); + ui->widget->replot(QCustomPlot::rpQueuedReplot); +} + +void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation) +{ + auto oldRange = ui->widget->xAxis->range(); + QCPAxis *axis = ui->widget->axisRect()->rangeDragAxis(orientation); + if (ui->widget->xAxis->scaleType() == QCPAxis::stLinear) { + double rg = (axis->range().upper - axis->range().lower) * (factor / 10); + axis->setRange(axis->range().lower + (rg), axis->range().upper + (rg)); + } + else if (ui->widget->xAxis->scaleType() == QCPAxis::stLogarithmic) { + int start = 0, stop = 0; + double diff = 0.; + if (factor > 0.0) { + stop = this->width() * factor / 10; + start = 2 * this->width() * factor / 10; + } + if (factor < 0.0) { + factor *= -1.0; + start = this->width() * factor / 10; + stop = 2 * this->width() * factor / 10; + } + diff = axis->pixelToCoord(start) / axis->pixelToCoord(stop); + axis->setRange(ui->widget->axisRect()->rangeDragAxis(orientation)->range().lower * diff, + ui->widget->axisRect()->rangeDragAxis(orientation)->range().upper * diff); + } + if (orientation == Qt::Horizontal) + onRangeChanged(axis->range(), oldRange); + ui->widget->replot(QCustomPlot::rpQueuedReplot); +} + void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor) { if (visitor) { @@ -636,6 +683,147 @@ void VisualizationGraphWidget::leaveEvent(QEvent *event) } } +void VisualizationGraphWidget::wheelEvent(QWheelEvent *event) +{ + double factor; + double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually + if (event->modifiers() == Qt::ControlModifier) { + if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical)) + { + setCursor(Qt::SizeVerCursor); + factor = pow(ui->widget->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps); + zoom(factor, event->pos().y(), Qt::Vertical); + } + } + else if (event->modifiers() == Qt::ShiftModifier) { + if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical)) + { + setCursor(Qt::SizeHorCursor); + factor = pow(ui->widget->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps); + zoom(factor, event->pos().x(), Qt::Horizontal); + } + } + else { + move(wheelSteps, Qt::Horizontal); + } + QWidget::wheelEvent(event); +} + +inline QCPRange _pixDistanceToRange(double pos1, double pos2, QCPAxis *axis) +{ + if (axis->scaleType() == QCPAxis::stLinear) + { + auto diff = axis->pixelToCoord(pos1) - axis->pixelToCoord(pos2); + return QCPRange{axis->range().lower + diff, axis->range().upper + diff}; + } + else + { + auto diff = axis->pixelToCoord(pos1) / axis->pixelToCoord(pos2); + return QCPRange{axis->range().lower * diff, axis->range().upper * diff}; + } +} + +void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent *event) +{ + if (event->buttons() & Qt::LeftButton) { + auto currentPos = event->pos(); + auto xAxis = ui->widget->axisRect()->rangeDragAxis(Qt::Horizontal); + auto yAxis = ui->widget->axisRect()->rangeDragAxis(Qt::Vertical); + auto oldXRange = xAxis->range(); + xAxis->setRange(_pixDistanceToRange(_dragLastPos.x(), currentPos.x(), xAxis)); + yAxis->setRange(_pixDistanceToRange(_dragLastPos.y(), currentPos.y(), yAxis)); + onRangeChanged(oldXRange, xAxis->range()); + ui->widget->replot(QCustomPlot::rpQueuedReplot); + _dragLastPos = currentPos; + } + QWidget::mouseMoveEvent(event); +} + +void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent *event) +{ + setCursor(Qt::ArrowCursor); + QWidget::mouseReleaseEvent(event); +} + +void VisualizationGraphWidget::mousePressEvent(QMouseEvent *event) +{ + if (event->button() == Qt::LeftButton) { + if (event->modifiers() == Qt::ControlModifier) { + } + else { + setCursor(Qt::ClosedHandCursor); + this->_dragLastPos= event->pos(); + } + } + QWidget::mousePressEvent(event); +} + +void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Control: + event->accept(); + break; + case Qt::Key_Shift: + event->accept(); + break; + default: + QWidget::keyReleaseEvent(event); + break; + } + setCursor(Qt::ArrowCursor); +} + +void VisualizationGraphWidget::keyPressEvent(QKeyEvent *event) +{ + switch (event->key()) { + case Qt::Key_Control: + setCursor(Qt::CrossCursor); + break; + case Qt::Key_Shift: + break; + case Qt::Key_M: + ui->widget->rescaleAxes(); + ui->widget->replot(QCustomPlot::rpQueuedReplot); + break; + case Qt::Key_Left: + if (event->modifiers() != Qt::ControlModifier) { + move(-0.1, Qt::Horizontal); + } + else { + zoom(2, this->width() / 2, Qt::Horizontal); + } + break; + case Qt::Key_Right: + if (event->modifiers() != Qt::ControlModifier) { + move(0.1, Qt::Horizontal); + } + else { + zoom(0.5, this->width() / 2, Qt::Horizontal); + } + break; + case Qt::Key_Up: + if (event->modifiers() != Qt::ControlModifier) { + move(0.1, Qt::Vertical); + } + else { + zoom(0.5, this->height() / 2, Qt::Vertical); + } + break; + case Qt::Key_Down: + if (event->modifiers() != Qt::ControlModifier) { + move(-0.1, Qt::Vertical); + } + else { + zoom(2, this->height() / 2, Qt::Vertical); + } + break; + default: + QWidget::keyPressEvent(event); + break; + } +} + QCustomPlot &VisualizationGraphWidget::plot() const noexcept { return *ui->widget; @@ -651,7 +839,7 @@ void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) { // 'Remove variable' action graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()), - [ this, var = it->first ]() { removeVariable(var); }); + [this, var = it->first]() { removeVariable(var); }); } if (!impl->m_ZoomStack.isEmpty()) { @@ -733,10 +921,10 @@ void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept } } -void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2) +void VisualizationGraphWidget::onRangeChanged(const QCPRange &newRange, const QCPRange &oldRange) { - auto graphRange = DateTimeRange{t1.lower, t1.upper}; - auto oldGraphRange = DateTimeRange{t2.lower, t2.upper}; + auto graphRange = DateTimeRange{newRange.lower, newRange.upper}; + auto oldGraphRange = DateTimeRange{oldRange.lower, oldRange.upper}; if (impl->m_Flags.testFlag(GraphFlag::EnableAcquisition)) { for (auto it = impl->m_VariableToPlotMultiMap.begin(), @@ -746,24 +934,24 @@ void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange } } - if (impl->m_Flags.testFlag(GraphFlag::EnableSynchronization) && !impl->m_IsCalibration) - { - emit synchronize(graphRange, oldGraphRange); - } - - auto pos = mapFromGlobal(QCursor::pos()); - auto axisPos = impl->posToAxisPos(pos, plot()); - if (auto parentZone = parentZoneWidget()) { - if (impl->pointIsInAxisRect(axisPos, plot())) { - parentZone->notifyMouseMoveInGraph(pos, axisPos, this); - } - else { - parentZone->notifyMouseLeaveGraph(this); - } - } + // if (impl->m_Flags.testFlag(GraphFlag::EnableSynchronization) && !impl->m_IsCalibration) + // { + // emit synchronize(graphRange, oldGraphRange); + // } + + // auto pos = mapFromGlobal(QCursor::pos()); + // auto axisPos = impl->posToAxisPos(pos, plot()); + // if (auto parentZone = parentZoneWidget()) { + // if (impl->pointIsInAxisRect(axisPos, plot())) { + // parentZone->notifyMouseMoveInGraph(pos, axisPos, this); + // } + // else { + // parentZone->notifyMouseLeaveGraph(this); + // } + // } // Quits calibration - impl->m_IsCalibration = false; + // impl->m_IsCalibration = false; } void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept @@ -860,7 +1048,7 @@ void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept plot().setNotAntialiasedElements(QCP::aeAll); } - //plot().replot(QCustomPlot::rpQueuedReplot); + // plot().replot(QCustomPlot::rpQueuedReplot); } } } @@ -887,9 +1075,10 @@ void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept } // Allows mouse panning only in default mode - plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode() - == SqpApplication::PlotsInteractionMode::None - && !isDragDropClick); + // plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode() + // == + // SqpApplication::PlotsInteractionMode::None + // && !isDragDropClick); // Allows zone edition only in selection zone mode without drag&drop impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick); @@ -1035,11 +1224,9 @@ void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr v void VisualizationGraphWidget::variableUpdated(QUuid id) { - for(auto& [var,plotables]:impl->m_VariableToPlotMultiMap) - { - if(var->ID()==id) - { - impl->updateData(plotables,var,this->graphRange()); + for (auto &[var, plotables] : impl->m_VariableToPlotMultiMap) { + if (var->ID() == id) { + impl->updateData(plotables, var, this->graphRange()); } } } diff --git a/gui/tests/GUITestUtils/GUITestUtils.h b/gui/tests/GUITestUtils/GUITestUtils.h index 5a1bd13..bfd086e 100644 --- a/gui/tests/GUITestUtils/GUITestUtils.h +++ b/gui/tests/GUITestUtils/GUITestUtils.h @@ -20,7 +20,7 @@ QPoint center(T* widget) HAS_METHOD(viewport) template -using is_QWidgetOrDerived = std::is_base_of; +static inline constexpr bool is_QWidgetOrDerived = std::is_base_of::value; template using viewport_type = decltype(std::declval().viewport()); @@ -31,9 +31,16 @@ void mouseMove(T* widget, QPoint pos, Qt::MouseButton mouseModifier) { QCursor::setPos(widget->mapToGlobal(pos)); QMouseEvent event(QEvent::MouseMove, pos, Qt::NoButton, mouseModifier, Qt::NoModifier); - if constexpr(has_viewport && is_QWidgetOrDerived>::value ) + if constexpr(has_viewport) { - qApp->sendEvent(widget->viewport(), &event); + if constexpr(is_QWidgetOrDerived>) + { + qApp->sendEvent(widget->viewport(), &event); + } + else + { + qApp->sendEvent(widget, &event); + } } else { @@ -46,9 +53,16 @@ void mouseMove(T* widget, QPoint pos, Qt::MouseButton mouseModifier) template void setMouseTracking(T* widget) { - if constexpr(has_viewport && is_QWidgetOrDerived>::value) + if constexpr(has_viewport) { - widget->viewport()->setMouseTracking(true); + if constexpr(is_QWidgetOrDerived>) + { + widget->viewport()->setMouseTracking(true); + } + else + { + widget->setMouseTracking(true); + } } else { diff --git a/gui/tests/simple_graph/main.cpp b/gui/tests/simple_graph/main.cpp index 9677410..8082628 100644 --- a/gui/tests/simple_graph/main.cpp +++ b/gui/tests/simple_graph/main.cpp @@ -30,7 +30,7 @@ ALIAS_TEMPLATE_FUNCTION(isReady, static_cast(qApp)->variableCo QCoreApplication::processEvents();\ w.addVariable(var, range);\ GET_CHILD_WIDGET_FOR_GUI_TESTS(w, plot, QCustomPlot, "widget");\ - auto cent = center(plot);\ + auto cent = center(&w);\ class A_SimpleGraph : public QObject { @@ -44,23 +44,20 @@ private slots: A_SIMPLE_GRAPH_FIXTURE for (auto i = 0; i < 10; i++) { - QTest::mousePress(plot, Qt::LeftButton, Qt::NoModifier, cent, 5); - mouseMove(plot, {cent.x() + 200, cent.y()}, Qt::LeftButton); - QTest::mouseRelease(plot, Qt::LeftButton); + QTest::mousePress(&w, Qt::LeftButton, Qt::NoModifier, cent, 500); + mouseMove(&w, {cent.x() + 200, cent.y()}, Qt::LeftButton); + QTest::mouseRelease(&w, Qt::LeftButton); while (!isReady(var)) QCoreApplication::processEvents(); - /* - * Just for visual inspection while running tests - */ - plot->rescaleAxes(); - plot->replot(); - QCoreApplication::processEvents(); } while (!isReady(var)) QCoreApplication::processEvents(); auto r = var->range(); + /* + * Scrolling to the left implies going back in time + * Scroll only implies keeping the same delta T -> shit only transformation + */ QVERIFY(r.m_TEnd < range.m_TEnd); - // this fails :( QVERIFY(SciQLop::numeric::almost_equal(r.delta(),range.delta(),1)); } };