diff --git a/app/src/MainWindow.cpp b/app/src/MainWindow.cpp index aa9c8ee..0831198 100644 --- a/app/src/MainWindow.cpp +++ b/app/src/MainWindow.cpp @@ -189,7 +189,7 @@ MainWindow::MainWindow(QWidget *parent) auto timeWidget = new TimeWidget{}; mainToolBar->addWidget(timeWidget); - auto actionPointerMode = new QAction{QIcon(":/icones/pointer.png"), "Pointer", this}; + auto actionPointerMode = new QAction{QIcon(":/icones/pointer.png"), "Move", this}; actionPointerMode->setCheckable(true); actionPointerMode->setChecked(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::None); diff --git a/core/include/Common/MimeTypesDef.h b/core/include/Common/MimeTypesDef.h index 3766fac..27535fa 100644 --- a/core/include/Common/MimeTypesDef.h +++ b/core/include/Common/MimeTypesDef.h @@ -14,6 +14,7 @@ extern SCIQLOP_CORE_EXPORT const QString MIME_TYPE_ZONE; extern SCIQLOP_CORE_EXPORT const QString MIME_TYPE_VARIABLE_LIST; extern SCIQLOP_CORE_EXPORT const QString MIME_TYPE_PRODUCT_LIST; extern SCIQLOP_CORE_EXPORT const QString MIME_TYPE_TIME_RANGE; +extern SCIQLOP_CORE_EXPORT const QString MIME_TYPE_SELECTION_ZONE; #endif // SCIQLOP_MIMETYPESDEF_H diff --git a/core/src/Common/MimeTypesDef.cpp b/core/src/Common/MimeTypesDef.cpp index 0393e1f..c057895 100644 --- a/core/src/Common/MimeTypesDef.cpp +++ b/core/src/Common/MimeTypesDef.cpp @@ -5,3 +5,4 @@ const QString MIME_TYPE_ZONE = QStringLiteral("sciqlop/zone"); const QString MIME_TYPE_VARIABLE_LIST = QStringLiteral("sciqlop/var-list"); const QString MIME_TYPE_PRODUCT_LIST = QStringLiteral("sciqlop/product-list"); const QString MIME_TYPE_TIME_RANGE = QStringLiteral("sciqlop/time-range"); +const QString MIME_TYPE_SELECTION_ZONE = QStringLiteral("sciqlop/selection-zone"); diff --git a/gui/include/Visualization/VisualizationDragWidget.h b/gui/include/Visualization/VisualizationDragWidget.h index 15924d3..bdc71ab 100644 --- a/gui/include/Visualization/VisualizationDragWidget.h +++ b/gui/include/Visualization/VisualizationDragWidget.h @@ -11,9 +11,13 @@ class VisualizationDragWidget : public QWidget { public: VisualizationDragWidget(QWidget *parent = nullptr); - virtual QMimeData *mimeData() const = 0; + virtual QMimeData *mimeData(const QPoint &position) const = 0; virtual bool isDragAllowed() const = 0; - virtual void highlightForMerge(bool highlighted) { Q_UNUSED(highlighted); }; + virtual void highlightForMerge(bool highlighted) { Q_UNUSED(highlighted); } + + /// Custom pixmap to display during a drag operation. + /// If the provided pixmap is null, a pixmap of the entire widget is used. + virtual QPixmap customDragPixmap(const QPoint &dragPosition); protected: virtual void mousePressEvent(QMouseEvent *event) override; diff --git a/gui/include/Visualization/VisualizationGraphWidget.h b/gui/include/Visualization/VisualizationGraphWidget.h index be9f91b..56ba511 100644 --- a/gui/include/Visualization/VisualizationGraphWidget.h +++ b/gui/include/Visualization/VisualizationGraphWidget.h @@ -17,6 +17,7 @@ class QCPRange; class QCustomPlot; class SqpRange; class Variable; +class VisualizationWidget; class VisualizationZoneWidget; namespace Ui { @@ -33,8 +34,12 @@ public: explicit VisualizationGraphWidget(const QString &name = {}, QWidget *parent = 0); virtual ~VisualizationGraphWidget(); + /// Returns the VisualizationZoneWidget which contains the graph or nullptr VisualizationZoneWidget *parentZoneWidget() const noexcept; + /// Returns the main VisualizationWidget which contains the graph or nullptr + VisualizationWidget *parentVisualizationWidget() const; + /// If acquisition isn't enable, requestDataLoading signal cannot be emit void enableAcquisition(bool enable); @@ -51,6 +56,15 @@ public: SqpRange graphRange() const noexcept; void setGraphRange(const SqpRange &range); + /// Returns the ranges of all the selection zones on the graph + QVector selectionZoneRanges() const; + + /// Adds new selection zones in the graph + void addSelectionZones(const QVector &ranges); + + /// Undo the last zoom done with a zoom box + void undoZoom(); + // IVisualizationWidget interface void accept(IVisualizationWidgetVisitor *visitor) override; bool canDrop(const Variable &variable) const override; @@ -58,7 +72,8 @@ public: QString name() const override; // VisualisationDragWidget - QMimeData *mimeData() const override; + QMimeData *mimeData(const QPoint &position) const override; + QPixmap customDragPixmap(const QPoint &dragPosition) override; bool isDragAllowed() const override; void highlightForMerge(bool highlighted) override; @@ -89,7 +104,7 @@ protected: void enterEvent(QEvent *event) override; void leaveEvent(QEvent *event) override; - QCustomPlot &plot() noexcept; + QCustomPlot &plot() const noexcept; private: Ui::VisualizationGraphWidget *ui; diff --git a/gui/include/Visualization/VisualizationSelectionZoneItem.h b/gui/include/Visualization/VisualizationSelectionZoneItem.h new file mode 100644 index 0000000..2bc3c93 --- /dev/null +++ b/gui/include/Visualization/VisualizationSelectionZoneItem.h @@ -0,0 +1,47 @@ +#ifndef SCIQLOP_VISUALIZATIONSELECTIONZONEITEM_H +#define SCIQLOP_VISUALIZATIONSELECTIONZONEITEM_H + +#include +#include +#include + +class VisualizationSelectionZoneItem : public QCPItemRect { + +public: + VisualizationSelectionZoneItem(QCustomPlot *plot); + virtual ~VisualizationSelectionZoneItem(); + + void setName(const QString &name); + QString name() const; + + SqpRange range() const; + void setRange(double tstart, double tend); + void setStart(double tstart); + void setEnd(double tend); + + void setColor(const QColor &color); + + void setEditionEnabled(bool value); + bool isEditionEnabled() const; + + Qt::CursorShape curshorShapeForPosition(const QPoint &position) const; + void setHovered(bool value); + + void setAssociatedEditedZones(const QVector &associatedZones); + +protected: + void mousePressEvent(QMouseEvent *event, const QVariant &details) override; + void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override; + void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override; + + void resizeLeft(double pixelDiff); + void resizeRight(double pixelDiff); + void move(double pixelDiff); + + +private: + class VisualizationSelectionZoneItemPrivate; + spimpl::unique_impl_ptr impl; +}; + +#endif // SCIQLOP_VISUALIZATIONSELECTIONZONEITEM_H diff --git a/gui/include/Visualization/VisualizationSelectionZoneManager.h b/gui/include/Visualization/VisualizationSelectionZoneManager.h new file mode 100644 index 0000000..d114186 --- /dev/null +++ b/gui/include/Visualization/VisualizationSelectionZoneManager.h @@ -0,0 +1,26 @@ +#ifndef SCIQLOP_VISUALIZATIONSELECTIONZONEMANAGER_H +#define SCIQLOP_VISUALIZATIONSELECTIONZONEMANAGER_H + +#include + +#include + +class VisualizationSelectionZoneItem; + +class VisualizationSelectionZoneManager { +public: + VisualizationSelectionZoneManager(); + + void select(const QVector &items); + void setSelected(VisualizationSelectionZoneItem *item, bool value); + + void clearSelection(); + + QVector selectedItems() const; + +private: + class VisualizationSelectionZoneManagerPrivate; + spimpl::unique_impl_ptr impl; +}; + +#endif // SCIQLOP_VISUALIZATIONSELECTIONZONEMANAGER_H diff --git a/gui/include/Visualization/VisualizationWidget.h b/gui/include/Visualization/VisualizationWidget.h index 3310990..fedb137 100644 --- a/gui/include/Visualization/VisualizationWidget.h +++ b/gui/include/Visualization/VisualizationWidget.h @@ -7,11 +7,14 @@ #include #include +#include + Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationWidget) class QMenu; class Variable; class VisualizationTabWidget; +class VisualizationSelectionZoneManager; namespace Ui { class VisualizationWidget; @@ -24,6 +27,9 @@ public: explicit VisualizationWidget(QWidget *parent = 0); virtual ~VisualizationWidget(); + /// Returns the class which manage the selection of selection zone across the visualization + VisualizationSelectionZoneManager &selectionZoneManager() const; + // IVisualizationWidget interface void accept(IVisualizationWidgetVisitor *visitor) override; bool canDrop(const Variable &variable) const override; @@ -49,6 +55,9 @@ protected: private: Ui::VisualizationWidget *ui; + + class VisualizationWidgetPrivate; + spimpl::unique_impl_ptr impl; }; #endif // VISUALIZATIONWIDGET_H diff --git a/gui/include/Visualization/VisualizationZoneWidget.h b/gui/include/Visualization/VisualizationZoneWidget.h index 211c0e5..c13f26f 100644 --- a/gui/include/Visualization/VisualizationZoneWidget.h +++ b/gui/include/Visualization/VisualizationZoneWidget.h @@ -60,6 +60,9 @@ public: VisualizationGraphWidget *createGraph(const QList > variables, int index); + /// Returns the first graph in the zone or nullptr if there is no graph inside + VisualizationGraphWidget *firstGraph() const; + // IVisualizationWidget interface void accept(IVisualizationWidgetVisitor *visitor) override; bool canDrop(const Variable &variable) const override; @@ -67,7 +70,7 @@ public: QString name() const override; // VisualisationDragWidget - QMimeData *mimeData() const override; + QMimeData *mimeData(const QPoint &position) const override; bool isDragAllowed() const override; void notifyMouseMoveInGraph(const QPointF &graphPosition, const QPointF &plotPosition, diff --git a/gui/meson.build b/gui/meson.build index 8d3e873..d923004 100644 --- a/gui/meson.build +++ b/gui/meson.build @@ -82,7 +82,9 @@ gui_sources = [ 'src/Visualization/VisualizationCursorItem.cpp', 'src/Visualization/ColorScaleEditor.cpp', 'src/Visualization/SqpColorScale.cpp', - 'src/Visualization/QCPColorMapIterator.cpp' + 'src/Visualization/QCPColorMapIterator.cpp', + 'src/Visualization/VisualizationSelectionZoneItem.cpp', + 'src/Visualization/VisualizationSelectionZoneManager.cpp' ] gui_inc = include_directories(['include']) diff --git a/gui/src/Visualization/VisualizationDragDropContainer.cpp b/gui/src/Visualization/VisualizationDragDropContainer.cpp index 7fc18f1..83da700 100644 --- a/gui/src/Visualization/VisualizationDragDropContainer.cpp +++ b/gui/src/Visualization/VisualizationDragDropContainer.cpp @@ -212,11 +212,15 @@ void VisualizationDragDropContainer::startDrag(VisualizationDragWidget *dragWidg // Note: The management of the drag object is done by Qt auto drag = new QDrag{dragWidget}; - auto mimeData = dragWidget->mimeData(); + auto mimeData = dragWidget->mimeData(dragPosition); drag->setMimeData(mimeData); - auto pixmap = QPixmap(dragWidget->size()); - dragWidget->render(&pixmap); + auto pixmap = dragWidget->customDragPixmap(dragPosition); + if (pixmap.isNull()) { + pixmap = QPixmap{dragWidget->size()}; + dragWidget->render(&pixmap); + } + drag->setPixmap(pixmap.scaled(DRAGGED_MINIATURE_WIDTH, DRAGGED_MINIATURE_WIDTH, Qt::KeepAspectRatio, Qt::SmoothTransformation)); @@ -225,17 +229,20 @@ void VisualizationDragDropContainer::startDrag(VisualizationDragWidget *dragWidg mimeData->setUrls({helper.imageTemporaryUrl(image)}); if (impl->m_Layout->indexOf(dragWidget) >= 0) { - helper.setCurrentDragWidget(dragWidget); - if (impl->cursorIsInContainer(this)) { - auto dragWidgetIndex = impl->m_Layout->indexOf(dragWidget); - helper.insertPlaceHolder(impl->m_Layout, dragWidgetIndex, impl->m_PlaceHolderType, - impl->m_PlaceHolderText); - dragWidget->setVisible(false); - } - else { - // The drag starts directly outside the drop zone - // do not add the placeHolder + if (impl->acceptMimeData(mimeData) && impl->allowInsertForMimeData(mimeData)) { + helper.setCurrentDragWidget(dragWidget); + + if (impl->cursorIsInContainer(this)) { + auto dragWidgetIndex = impl->m_Layout->indexOf(dragWidget); + helper.insertPlaceHolder(impl->m_Layout, dragWidgetIndex, impl->m_PlaceHolderType, + impl->m_PlaceHolderText); + dragWidget->setVisible(false); + } + else { + // The drag starts directly outside the drop zone + // do not add the placeHolder + } } drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::MoveAction); diff --git a/gui/src/Visualization/VisualizationDragWidget.cpp b/gui/src/Visualization/VisualizationDragWidget.cpp index 0214e5e..39d36a8 100644 --- a/gui/src/Visualization/VisualizationDragWidget.cpp +++ b/gui/src/Visualization/VisualizationDragWidget.cpp @@ -19,6 +19,12 @@ VisualizationDragWidget::VisualizationDragWidget(QWidget *parent) { } +QPixmap VisualizationDragWidget::customDragPixmap(const QPoint &dragPosition) +{ + Q_UNUSED(dragPosition); + return QPixmap(); +} + void VisualizationDragWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { diff --git a/gui/src/Visualization/VisualizationGraphRenderingDelegate.cpp b/gui/src/Visualization/VisualizationGraphRenderingDelegate.cpp index bb70482..30747c3 100644 --- a/gui/src/Visualization/VisualizationGraphRenderingDelegate.cpp +++ b/gui/src/Visualization/VisualizationGraphRenderingDelegate.cpp @@ -48,6 +48,7 @@ void initPointTracerStyle(QCPItemTracer &tracer) noexcept tracer.setSize(3); tracer.setPen(QPen(Qt::black)); tracer.setBrush(Qt::black); + tracer.setSelectable(false); } QPixmap pixmap(const QString &iconPath) noexcept @@ -94,6 +95,7 @@ void initTitleTextStyle(QCPItemText &text) noexcept text.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft); text.position->setType(QCPItemPosition::ptAxisRectRatio); text.position->setCoords(0.5, 0); + text.setSelectable(false); } /** @@ -162,9 +164,9 @@ struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegateP initClosePixmapStyle(*m_ClosePixmap); // Connects pixmap selection to graph widget closing - QObject::connect(m_ClosePixmap, &QCPItemPixmap::selectionChanged, - [&graphWidget](bool selected) { - if (selected) { + QObject::connect(&m_Plot, &QCustomPlot::itemClick, + [&graphWidget, this](auto item, auto mouseEvent) { + if (item == m_ClosePixmap) { graphWidget.close(); } }); @@ -179,15 +181,14 @@ struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegateP initXAxisPixmapStyle(*m_XAxisPixmap); // Connects pixmap selection to graph x-axis showing/hiding - QObject::connect(m_XAxisPixmap, &QCPItemPixmap::selectionChanged, [this]() { - if (m_XAxisPixmap->selected()) { + QObject::connect(&m_Plot, &QCustomPlot::itemClick, [this](auto item, auto mouseEvent) { + if (m_XAxisPixmap == item) { // Changes the selection state and refreshes the x-axis m_ShowXAxis = !m_ShowXAxis; - updateXAxisState(); + this->updateXAxisState(); m_Plot.layer(AXES_LAYER)->replot(); // Deselects the x-axis pixmap and updates icon - m_XAxisPixmap->setSelected(false); m_XAxisPixmap->setPixmap( pixmap(m_ShowXAxis ? HIDE_AXIS_ICON_PATH : SHOW_AXIS_ICON_PATH)); m_Plot.layer(OVERLAY_LAYER)->replot(); diff --git a/gui/src/Visualization/VisualizationGraphWidget.cpp b/gui/src/Visualization/VisualizationGraphWidget.cpp index bc9cf8d..4e0c6d9 100644 --- a/gui/src/Visualization/VisualizationGraphWidget.cpp +++ b/gui/src/Visualization/VisualizationGraphWidget.cpp @@ -4,6 +4,9 @@ #include "Visualization/VisualizationDefs.h" #include "Visualization/VisualizationGraphHelper.h" #include "Visualization/VisualizationGraphRenderingDelegate.h" +#include "Visualization/VisualizationSelectionZoneItem.h" +#include "Visualization/VisualizationSelectionZoneManager.h" +#include "Visualization/VisualizationWidget.h" #include "Visualization/VisualizationZoneWidget.h" #include "ui_VisualizationGraphWidget.h" @@ -24,6 +27,9 @@ Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget") namespace { +/// Key pressed to enable drag&drop in all modes +const auto DRAG_DROP_MODIFIER = Qt::AltModifier; + /// Key pressed to enable zoom on horizontal axis const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier; @@ -36,6 +42,9 @@ const auto PAN_SPEED = 5; /// Key pressed to enable a calibration pan const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier; +/// Key pressed to enable multi selection of selection zones +const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier; + /// Minimum size for the zoom box, in percentage of the axis range const auto ZOOM_BOX_MIN_SIZE = 0.8; @@ -71,18 +80,17 @@ struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { /// Delegate used to attach rendering features to the plot std::unique_ptr m_RenderingDelegate; - QCPItemRect *m_DrawingRect = nullptr; + QCPItemRect *m_DrawingZoomRect = nullptr; + QStack > m_ZoomStack; + std::unique_ptr m_HorizontalCursor = nullptr; std::unique_ptr m_VerticalCursor = nullptr; - void configureDrawingRect() - { - if (m_DrawingRect) { - QPen p; - p.setWidth(2); - m_DrawingRect->setPen(p); - } - } + VisualizationSelectionZoneItem *m_DrawingZone = nullptr; + VisualizationSelectionZoneItem *m_HoveredZone = nullptr; + QVector m_SelectionZones; + + bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even void startDrawingRect(const QPoint &pos, QCustomPlot &plot) { @@ -90,22 +98,77 @@ struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { auto axisPos = posToAxisPos(pos, plot); - m_DrawingRect = new QCPItemRect{&plot}; - configureDrawingRect(); + m_DrawingZoomRect = new QCPItemRect{&plot}; + QPen p; + p.setWidth(2); + m_DrawingZoomRect->setPen(p); - m_DrawingRect->topLeft->setCoords(axisPos); - m_DrawingRect->bottomRight->setCoords(axisPos); + m_DrawingZoomRect->topLeft->setCoords(axisPos); + m_DrawingZoomRect->bottomRight->setCoords(axisPos); } void removeDrawingRect(QCustomPlot &plot) { - if (m_DrawingRect) { - plot.removeItem(m_DrawingRect); // the item is deleted by QCustomPlot - m_DrawingRect = nullptr; + if (m_DrawingZoomRect) { + plot.removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot + m_DrawingZoomRect = nullptr; plot.replot(QCustomPlot::rpQueuedReplot); } } + void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph) + { + endDrawingZone(graph); + + auto axisPos = posToAxisPos(pos, graph->plot()); + + m_DrawingZone = new VisualizationSelectionZoneItem{&graph->plot()}; + m_DrawingZone->setRange(axisPos.x(), axisPos.x()); + m_DrawingZone->setEditionEnabled(false); + } + + void endDrawingZone(VisualizationGraphWidget *graph) + { + if (m_DrawingZone) { + auto drawingZoneRange = m_DrawingZone->range(); + if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) { + m_DrawingZone->setEditionEnabled(true); + addSelectionZone(m_DrawingZone); + } + else { + graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot + } + + graph->plot().replot(QCustomPlot::rpQueuedReplot); + m_DrawingZone = nullptr; + } + } + + void setSelectionZonesEditionEnabled(bool value) + { + for (auto s : m_SelectionZones) { + s->setEditionEnabled(value); + } + } + + void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; } + + VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos, + const QCustomPlot &plot) const + { + VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr; + auto minDistanceToZone = -1; + for (auto zone : m_SelectionZones) { + auto distanceToZone = zone->selectTest(pos, false); + if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone) + && distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) { + selectionZoneItemUnderCursor = zone; + } + } + + return selectionZoneItemUnderCursor; + } + QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const { auto axisX = plot.axisRect()->axis(QCPAxis::atBottom); @@ -117,7 +180,6 @@ struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { { auto axisX = plot.axisRect()->axis(QCPAxis::atBottom); auto axisY = plot.axisRect()->axis(QCPAxis::atLeft); - return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y()); } }; @@ -133,9 +195,10 @@ VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget setAttribute(Qt::WA_DeleteOnClose); // Set qcpplot properties : - // - Drag (on x-axis) and zoom are enabled + // - zoom is enabled // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation - ui->widget->setInteractions(QCP::iRangeZoom | QCP::iSelectItems); + ui->widget->setInteractions(QCP::iRangeZoom); + ui->widget->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical); // The delegate must be initialized after the ui as it uses the plot impl->m_RenderingDelegate = std::make_unique(*this); @@ -153,9 +216,10 @@ VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget 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); + 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); @@ -189,6 +253,16 @@ VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noex return qobject_cast(parent); } +VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const +{ + auto parent = parentWidget(); + while (parent != nullptr && !qobject_cast(parent)) { + parent = parent->parentWidget(); + } + + return qobject_cast(parent); +} + void VisualizationGraphWidget::enableAcquisition(bool enable) { impl->m_DoAcquisition = enable; @@ -281,6 +355,40 @@ void VisualizationGraphWidget::setGraphRange(const SqpRange &range) qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END"); } +QVector VisualizationGraphWidget::selectionZoneRanges() const +{ + QVector ranges; + for (auto zone : impl->m_SelectionZones) { + ranges << zone->range(); + } + + return ranges; +} + +void VisualizationGraphWidget::addSelectionZones(const QVector &ranges) +{ + for (const auto &range : ranges) { + // note: ownership is transfered to QCustomPlot + auto zone = new VisualizationSelectionZoneItem(&plot()); + zone->setRange(range.m_TStart, range.m_TEnd); + impl->addSelectionZone(zone); + } + + plot().replot(QCustomPlot::rpQueuedReplot); +} + +void VisualizationGraphWidget::undoZoom() +{ + auto zoom = impl->m_ZoomStack.pop(); + auto axisX = plot().axisRect()->axis(QCPAxis::atBottom); + auto axisY = plot().axisRect()->axis(QCPAxis::atLeft); + + axisX->setRange(zoom.first); + axisY->setRange(zoom.second); + + plot().replot(QCustomPlot::rpQueuedReplot); +} + void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor) { if (visitor) { @@ -324,17 +432,50 @@ QString VisualizationGraphWidget::name() const return impl->m_Name; } -QMimeData *VisualizationGraphWidget::mimeData() const +QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const { auto mimeData = new QMimeData; - mimeData->setData(MIME_TYPE_GRAPH, QByteArray{}); - auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange()); - mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData); + auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position, plot()); + if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones + && selectionZoneItemUnderCursor) { + mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange( + selectionZoneItemUnderCursor->range())); + mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange( + selectionZoneItemUnderCursor->range())); + } + else { + mimeData->setData(MIME_TYPE_GRAPH, QByteArray{}); + + auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange()); + mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData); + } return mimeData; } +QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition) +{ + auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition, plot()); + if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones + && selectionZoneItemUnderCursor) { + + auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition(); + auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition(); + + auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()), + qAbs(zoneBottomRight.y() - zoneTopLeft.y())} + .toSize(); + + auto pixmap = QPixmap(zoneSize); + render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}}); + + return pixmap; + } + + return QPixmap(); +} + bool VisualizationGraphWidget::isDragAllowed() const { return true; @@ -428,9 +569,14 @@ void VisualizationGraphWidget::leaveEvent(QEvent *event) else { qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget"; } + + if (impl->m_HoveredZone) { + impl->m_HoveredZone->setHovered(false); + impl->m_HoveredZone = nullptr; + } } -QCustomPlot &VisualizationGraphWidget::plot() noexcept +QCustomPlot &VisualizationGraphWidget::plot() const noexcept { return *ui->widget; } @@ -448,6 +594,14 @@ void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept [ this, var = it->first ]() { removeVariable(var); }); } + if (!impl->m_ZoomStack.isEmpty()) { + if (!graphMenu.isEmpty()) { + graphMenu.addSeparator(); + } + + graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); }); + } + if (!graphMenu.isEmpty()) { graphMenu.exec(QCursor::pos()); } @@ -455,9 +609,9 @@ void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2) { - qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged") - << QThread::currentThread()->objectName() << "DoAcqui" - << impl->m_DoAcquisition; + qCDebug(LOG_VisualizationGraphWidget()) + << tr("TORM: VisualizationGraphWidget::onRangeChanged") + << QThread::currentThread()->objectName() << "DoAcqui" << impl->m_DoAcquisition; auto graphRange = SqpRange{t1.lower, t1.upper}; auto oldGraphRange = SqpRange{t2.lower, t2.upper}; @@ -508,10 +662,15 @@ void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept auto axisPos = impl->posToAxisPos(event->pos(), plot()); - if (impl->m_DrawingRect) { - impl->m_DrawingRect->bottomRight->setCoords(axisPos); + // Zoom box and zone drawing + if (impl->m_DrawingZoomRect) { + impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos); + } + else if (impl->m_DrawingZone) { + impl->m_DrawingZone->setEnd(axisPos.x()); } + // Cursor if (auto parentZone = parentZoneWidget()) { if (impl->pointIsInAxisRect(axisPos, plot())) { parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this); @@ -524,6 +683,36 @@ void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget"; } + // Search for the selection zone under the mouse + auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot()); + if (selectionZoneItemUnderCursor && !impl->m_DrawingZone + && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) { + + // Sets the appropriate cursor shape + auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos()); + setCursor(cursorShape); + + // Manages the hovered zone + if (selectionZoneItemUnderCursor != impl->m_HoveredZone) { + if (impl->m_HoveredZone) { + impl->m_HoveredZone->setHovered(false); + } + selectionZoneItemUnderCursor->setHovered(true); + impl->m_HoveredZone = selectionZoneItemUnderCursor; + plot().replot(QCustomPlot::rpQueuedReplot); + } + } + else { + // There is no zone under the mouse or the interaction mode is not "selection zones" + if (impl->m_HoveredZone) { + impl->m_HoveredZone->setHovered(false); + impl->m_HoveredZone = nullptr; + } + + setCursor(Qt::ArrowCursor); + } + + impl->m_HasMovedMouse = true; VisualizationDragWidget::mouseMoveEvent(event); } @@ -560,30 +749,72 @@ void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept { - if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) { - impl->startDrawingRect(event->pos(), plot()); + auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER); + auto isSelectionZoneMode + = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones; + auto isLeftClick = event->buttons().testFlag(Qt::LeftButton); + + if (!isDragDropClick && isLeftClick) { + if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) { + // Starts a zoom box + impl->startDrawingRect(event->pos(), plot()); + } + else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) { + // Starts a new selection zone + auto zoneAtPos = impl->selectionZoneAt(event->pos(), plot()); + if (!zoneAtPos) { + impl->startDrawingZone(event->pos(), this); + } + } + } + + // Allows mouse panning only in default mode + 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); + + // Selection / Deselection + if (isSelectionZoneMode) { + auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER); + auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot()); + if (selectionZoneItemUnderCursor && isLeftClick) { + selectionZoneItemUnderCursor->setAssociatedEditedZones( + parentVisualizationWidget()->selectionZoneManager().selectedItems()); + } + else if (!isMultiSelectionClick && isLeftClick) { + parentVisualizationWidget()->selectionZoneManager().clearSelection(); + } + else { + // No selection change + } } + + impl->m_HasMovedMouse = false; VisualizationDragWidget::mousePressEvent(event); } void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept { - if (impl->m_DrawingRect) { + if (impl->m_DrawingZoomRect) { auto axisX = plot().axisRect()->axis(QCPAxis::atBottom); auto axisY = plot().axisRect()->axis(QCPAxis::atLeft); - auto newAxisXRange = QCPRange{impl->m_DrawingRect->topLeft->coords().x(), - impl->m_DrawingRect->bottomRight->coords().x()}; + auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(), + impl->m_DrawingZoomRect->bottomRight->coords().x()}; - auto newAxisYRange = QCPRange{impl->m_DrawingRect->topLeft->coords().y(), - impl->m_DrawingRect->bottomRight->coords().y()}; + auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(), + impl->m_DrawingZoomRect->bottomRight->coords().y()}; impl->removeDrawingRect(plot()); if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0) && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) { + impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range())); axisX->setRange(newAxisXRange); axisY->setRange(newAxisYRange); @@ -591,7 +822,31 @@ void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept } } + impl->endDrawingZone(this); + impl->m_IsCalibration = false; + + // Selection / Deselection + auto isSelectionZoneMode + = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones; + if (isSelectionZoneMode) { + auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER); + auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot()); + if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton) { + if (!isMultiSelectionClick && !impl->m_HasMovedMouse) { + parentVisualizationWidget()->selectionZoneManager().select( + {selectionZoneItemUnderCursor}); + } + else if (!impl->m_HasMovedMouse) { + parentVisualizationWidget()->selectionZoneManager().setSelected( + selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected() + || event->button() == Qt::RightButton); + } + } + else { + // No selection change + } + } } void VisualizationGraphWidget::onDataCacheVariableUpdated() diff --git a/gui/src/Visualization/VisualizationSelectionZoneItem.cpp b/gui/src/Visualization/VisualizationSelectionZoneItem.cpp new file mode 100644 index 0000000..c29e8fc --- /dev/null +++ b/gui/src/Visualization/VisualizationSelectionZoneItem.cpp @@ -0,0 +1,343 @@ +#include "Visualization/VisualizationSelectionZoneItem.h" + +const QString &DEFAULT_COLOR = QStringLiteral("#E79D41"); + +struct VisualizationSelectionZoneItem::VisualizationSelectionZoneItemPrivate { + + QCustomPlot *m_Plot; + double m_T1 = 0; + double m_T2 = 0; + QColor m_Color; + + bool m_IsEditionEnabled = true; + double m_MovedOrinalT1 = 0; + double m_MovedOrinalT2 = 0; + + QCPItemStraightLine *m_LeftLine; + QCPItemStraightLine *m_RightLine; + QCPItemText *m_NameLabelItem = nullptr; + + enum class EditionMode { NoEdition, ResizeLeft, ResizeRight, Move }; + EditionMode m_CurrentEditionMode; + + QVector m_AssociatedEditedZones; + + VisualizationSelectionZoneItemPrivate(QCustomPlot *plot) + : m_Plot(plot), m_Color(Qt::blue), m_CurrentEditionMode(EditionMode::NoEdition) + { + } + + void updatePosition(VisualizationSelectionZoneItem *item) + { + item->topLeft->setCoords(m_T1, 0); + item->bottomRight->setCoords(m_T2, 1); + } + + EditionMode getEditionMode(const QPoint &pos, const VisualizationSelectionZoneItem *zoneItem) + { + auto distanceLeft = m_LeftLine->selectTest(pos, false); + auto distanceRight = m_RightLine->selectTest(pos, false); + auto distance = zoneItem->selectTest(pos, false); + + if (distanceRight <= distance) { + return VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight; + } + else if (distanceLeft <= distance) { + return VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft; + } + + return VisualizationSelectionZoneItemPrivate::EditionMode::Move; + } + + double pixelSizeToAxisXSize(double pixels) + { + auto axis = m_Plot->axisRect()->axis(QCPAxis::atBottom); + return axis->pixelToCoord(pixels) - axis->pixelToCoord(0); + } +}; + +VisualizationSelectionZoneItem::VisualizationSelectionZoneItem(QCustomPlot *plot) + : QCPItemRect(plot), + impl{spimpl::make_unique_impl(plot)} +{ + topLeft->setTypeX(QCPItemPosition::ptPlotCoords); + topLeft->setTypeY(QCPItemPosition::ptAxisRectRatio); + bottomRight->setTypeX(QCPItemPosition::ptPlotCoords); + bottomRight->setTypeY(QCPItemPosition::ptAxisRectRatio); + setSelectable(false); + + impl->m_RightLine = new QCPItemStraightLine(plot); + impl->m_RightLine->point1->setParentAnchor(topRight); + impl->m_RightLine->point2->setParentAnchor(bottomRight); + impl->m_RightLine->point1->setTypeX(QCPItemPosition::ptAbsolute); + impl->m_RightLine->point1->setTypeY(QCPItemPosition::ptAbsolute); + impl->m_RightLine->point2->setTypeX(QCPItemPosition::ptAbsolute); + impl->m_RightLine->point2->setTypeY(QCPItemPosition::ptAbsolute); + impl->m_RightLine->setSelectable(false); + + impl->m_LeftLine = new QCPItemStraightLine(plot); + impl->m_LeftLine->point1->setParentAnchor(topLeft); + impl->m_LeftLine->point2->setParentAnchor(bottomLeft); + impl->m_LeftLine->point1->setTypeX(QCPItemPosition::ptAbsolute); + impl->m_LeftLine->point1->setTypeY(QCPItemPosition::ptAbsolute); + impl->m_LeftLine->point2->setTypeX(QCPItemPosition::ptAbsolute); + impl->m_LeftLine->point2->setTypeY(QCPItemPosition::ptAbsolute); + impl->m_LeftLine->setSelectable(false); + + connect(this, &VisualizationSelectionZoneItem::selectionChanged, impl->m_RightLine, + &QCPItemStraightLine::setSelected); + connect(this, &VisualizationSelectionZoneItem::selectionChanged, impl->m_LeftLine, + &QCPItemStraightLine::setSelected); + + setColor(QColor(DEFAULT_COLOR)); +} + +VisualizationSelectionZoneItem::~VisualizationSelectionZoneItem() +{ + impl->m_Plot->removeItem(impl->m_RightLine); + impl->m_Plot->removeItem(impl->m_LeftLine); +} + +void VisualizationSelectionZoneItem::setName(const QString &name) +{ + if (name.isEmpty() && impl->m_NameLabelItem) { + impl->m_Plot->removeItem(impl->m_NameLabelItem); + impl->m_NameLabelItem = nullptr; + } + else if (!impl->m_NameLabelItem) { + impl->m_NameLabelItem = new QCPItemText(impl->m_Plot); + impl->m_NameLabelItem->setText(name); + impl->m_NameLabelItem->setPositionAlignment(Qt::AlignHCenter | Qt::AlignTop); + impl->m_NameLabelItem->setColor(impl->m_Color); + impl->m_NameLabelItem->position->setParentAnchor(top); + } +} + +QString VisualizationSelectionZoneItem::name() const +{ + if (!impl->m_NameLabelItem) { + return QString(); + } + + return impl->m_NameLabelItem->text(); +} + +SqpRange VisualizationSelectionZoneItem::range() const +{ + SqpRange range; + range.m_TStart = impl->m_T1 <= impl->m_T2 ? impl->m_T1 : impl->m_T2; + range.m_TEnd = impl->m_T1 > impl->m_T2 ? impl->m_T1 : impl->m_T2; + return range; +} + +void VisualizationSelectionZoneItem::setRange(double tstart, double tend) +{ + impl->m_T1 = tstart; + impl->m_T2 = tend; + impl->updatePosition(this); +} + +void VisualizationSelectionZoneItem::setStart(double tstart) +{ + impl->m_T1 = tstart; + impl->updatePosition(this); +} + +void VisualizationSelectionZoneItem::setEnd(double tend) +{ + impl->m_T2 = tend; + impl->updatePosition(this); +} + +void VisualizationSelectionZoneItem::setColor(const QColor &color) +{ + impl->m_Color = color; + + auto brushColor = color; + brushColor.setAlpha(80); + setBrush(QBrush(brushColor)); + setPen(QPen(Qt::NoPen)); + + auto selectedBrushColor = brushColor; + selectedBrushColor.setAlpha(150); + setSelectedBrush(QBrush(selectedBrushColor)); + setSelectedPen(QPen(Qt::NoPen)); + + auto linePen = QPen(color); + linePen.setStyle(Qt::SolidLine); + linePen.setWidth(4); + + auto selectedLinePen = linePen; + selectedLinePen.setColor(color.darker(120)); + selectedLinePen.setWidth(4); + + impl->m_LeftLine->setPen(linePen); + impl->m_RightLine->setPen(linePen); + + impl->m_LeftLine->setSelectedPen(selectedLinePen); + impl->m_RightLine->setSelectedPen(selectedLinePen); +} + +void VisualizationSelectionZoneItem::setEditionEnabled(bool value) +{ + impl->m_IsEditionEnabled = value; + setSelectable(value); + if (!value) { + setSelected(false); + impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition; + } +} + +bool VisualizationSelectionZoneItem::isEditionEnabled() const +{ + return impl->m_IsEditionEnabled; +} + +Qt::CursorShape +VisualizationSelectionZoneItem::curshorShapeForPosition(const QPoint &position) const +{ + auto mode = impl->m_CurrentEditionMode + == VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition + ? impl->getEditionMode(position, this) + : impl->m_CurrentEditionMode; + switch (mode) { + case VisualizationSelectionZoneItemPrivate::EditionMode::Move: + return Qt::SizeAllCursor; + case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft: + case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight: // fallthrough + return Qt::SizeHorCursor; + default: + return Qt::ArrowCursor; + } +} + +void VisualizationSelectionZoneItem::setHovered(bool value) +{ + if (value) { + auto linePen = impl->m_LeftLine->pen(); + linePen.setStyle(Qt::DotLine); + linePen.setWidth(3); + + auto selectedLinePen = impl->m_LeftLine->selectedPen(); + ; + selectedLinePen.setStyle(Qt::DotLine); + selectedLinePen.setWidth(3); + + impl->m_LeftLine->setPen(linePen); + impl->m_RightLine->setPen(linePen); + + impl->m_LeftLine->setSelectedPen(selectedLinePen); + impl->m_RightLine->setSelectedPen(selectedLinePen); + } + else { + setColor(impl->m_Color); + } +} + +void VisualizationSelectionZoneItem::setAssociatedEditedZones( + const QVector &associatedZones) +{ + impl->m_AssociatedEditedZones = associatedZones; + impl->m_AssociatedEditedZones.removeAll(this); +} + +void VisualizationSelectionZoneItem::mousePressEvent(QMouseEvent *event, const QVariant &details) +{ + if (isEditionEnabled() && event->button() == Qt::LeftButton) { + impl->m_CurrentEditionMode = impl->getEditionMode(event->pos(), this); + + impl->m_MovedOrinalT1 = impl->m_T1; + impl->m_MovedOrinalT2 = impl->m_T2; + for (auto associatedZone : impl->m_AssociatedEditedZones) { + associatedZone->impl->m_MovedOrinalT1 = associatedZone->impl->m_T1; + associatedZone->impl->m_MovedOrinalT2 = associatedZone->impl->m_T2; + } + } + else { + impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition; + event->ignore(); + } +} + +void VisualizationSelectionZoneItem::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) +{ + if (isEditionEnabled()) { + auto axis = impl->m_Plot->axisRect()->axis(QCPAxis::atBottom); + auto pixelDiff = event->pos().x() - startPos.x(); + auto diff = impl->pixelSizeToAxisXSize(pixelDiff); + + switch (impl->m_CurrentEditionMode) { + case VisualizationSelectionZoneItemPrivate::EditionMode::Move: + setRange(impl->m_MovedOrinalT1 + diff, impl->m_MovedOrinalT2 + diff); + for (auto associatedZone : impl->m_AssociatedEditedZones) { + associatedZone->move(pixelDiff); + } + break; + case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft: + setStart(impl->m_MovedOrinalT1 + diff); + for (auto associatedZone : impl->m_AssociatedEditedZones) { + impl->m_MovedOrinalT1 < impl->m_MovedOrinalT2 + ? associatedZone->resizeLeft(pixelDiff) + : associatedZone->resizeRight(pixelDiff); + } + break; + case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight: + setEnd(impl->m_MovedOrinalT2 + diff); + for (auto associatedZone : impl->m_AssociatedEditedZones) { + impl->m_MovedOrinalT1 < impl->m_MovedOrinalT2 + ? associatedZone->resizeRight(pixelDiff) + : associatedZone->resizeLeft(pixelDiff); + } + break; + default: + break; + } + + for (auto associatedZone : impl->m_AssociatedEditedZones) { + associatedZone->parentPlot()->replot(); + } + } + else { + event->ignore(); + } +} + +void VisualizationSelectionZoneItem::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) +{ + if (isEditionEnabled()) { + impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition; + } + else { + event->ignore(); + } + + impl->m_AssociatedEditedZones.clear(); +} + +void VisualizationSelectionZoneItem::resizeLeft(double pixelDiff) +{ + auto diff = impl->pixelSizeToAxisXSize(pixelDiff); + if (impl->m_MovedOrinalT1 <= impl->m_MovedOrinalT2) { + setStart(impl->m_MovedOrinalT1 + diff); + } + else { + setEnd(impl->m_MovedOrinalT2 + diff); + } +} + +void VisualizationSelectionZoneItem::resizeRight(double pixelDiff) +{ + auto diff = impl->pixelSizeToAxisXSize(pixelDiff); + if (impl->m_MovedOrinalT1 > impl->m_MovedOrinalT2) { + setStart(impl->m_MovedOrinalT1 + diff); + } + else { + setEnd(impl->m_MovedOrinalT2 + diff); + } +} + +void VisualizationSelectionZoneItem::move(double pixelDiff) +{ + auto diff = impl->pixelSizeToAxisXSize(pixelDiff); + setRange(impl->m_MovedOrinalT1 + diff, impl->m_MovedOrinalT2 + diff); +} diff --git a/gui/src/Visualization/VisualizationSelectionZoneManager.cpp b/gui/src/Visualization/VisualizationSelectionZoneManager.cpp new file mode 100644 index 0000000..f90009b --- /dev/null +++ b/gui/src/Visualization/VisualizationSelectionZoneManager.cpp @@ -0,0 +1,51 @@ +#include "Visualization/VisualizationSelectionZoneManager.h" +#include "Visualization/VisualizationSelectionZoneItem.h" + +struct VisualizationSelectionZoneManager::VisualizationSelectionZoneManagerPrivate { + QVector m_SelectedItems; +}; + +VisualizationSelectionZoneManager::VisualizationSelectionZoneManager() + : impl{spimpl::make_unique_impl()} +{ +} + +void VisualizationSelectionZoneManager::select( + const QVector &items) +{ + clearSelection(); + for (auto item : items) { + setSelected(item, true); + } +} + +void VisualizationSelectionZoneManager::setSelected(VisualizationSelectionZoneItem *item, + bool value) +{ + if (value != item->selected()) { + item->setSelected(value); + item->parentPlot()->replot(); + } + + if (!value && impl->m_SelectedItems.contains(item)) { + impl->m_SelectedItems.removeAll(item); + } + else if (value) { + impl->m_SelectedItems << item; + } +} + +void VisualizationSelectionZoneManager::clearSelection() +{ + for (auto item : impl->m_SelectedItems) { + item->setSelected(false); + item->parentPlot()->replot(); + } + + impl->m_SelectedItems.clear(); +} + +QVector VisualizationSelectionZoneManager::selectedItems() const +{ + return impl->m_SelectedItems; +} diff --git a/gui/src/Visualization/VisualizationTabWidget.cpp b/gui/src/Visualization/VisualizationTabWidget.cpp index 0269164..ff28657 100644 --- a/gui/src/Visualization/VisualizationTabWidget.cpp +++ b/gui/src/Visualization/VisualizationTabWidget.cpp @@ -255,7 +255,16 @@ void VisualizationTabWidget::VisualizationTabWidgetPrivate::dropGraph( helper.delayedCloseWidget(graphWidget); } - tabWidget->createZone(variables, index); + auto zoneWidget = tabWidget->createZone(variables, index); + auto firstGraph = zoneWidget->firstGraph(); + if (firstGraph) { + firstGraph->addSelectionZones(graphWidget->selectionZoneRanges()); + } + else { + qCWarning(LOG_VisualizationZoneWidget()) + << tr("VisualizationTabWidget::dropGraph, no graph added in the widget."); + Q_ASSERT(false); + } } else { // The graph is empty, create an empty zone and move the graph inside diff --git a/gui/src/Visualization/VisualizationWidget.cpp b/gui/src/Visualization/VisualizationWidget.cpp index 39f3370..91d1ce7 100644 --- a/gui/src/Visualization/VisualizationWidget.cpp +++ b/gui/src/Visualization/VisualizationWidget.cpp @@ -1,6 +1,7 @@ #include "Visualization/VisualizationWidget.h" #include "Visualization/IVisualizationWidgetVisitor.h" #include "Visualization/VisualizationGraphWidget.h" +#include "Visualization/VisualizationSelectionZoneManager.h" #include "Visualization/VisualizationTabWidget.h" #include "Visualization/VisualizationZoneWidget.h" #include "Visualization/operations/FindVariableOperation.h" @@ -16,10 +17,23 @@ #include +#include + Q_LOGGING_CATEGORY(LOG_VisualizationWidget, "VisualizationWidget") +struct VisualizationWidget::VisualizationWidgetPrivate { + std::unique_ptr m_ZoneSelectionManager = nullptr; + + VisualizationWidgetPrivate() + : m_ZoneSelectionManager(std::make_unique()) + { + } +}; + VisualizationWidget::VisualizationWidget(QWidget *parent) - : QWidget{parent}, ui{new Ui::VisualizationWidget} + : QWidget{parent}, + ui{new Ui::VisualizationWidget}, + impl{spimpl::make_unique_impl()} { ui->setupUi(this); @@ -82,6 +96,11 @@ VisualizationWidget::~VisualizationWidget() delete ui; } +VisualizationSelectionZoneManager &VisualizationWidget::selectionZoneManager() const +{ + return *impl->m_ZoneSelectionManager.get(); +} + void VisualizationWidget::accept(IVisualizationWidgetVisitor *visitor) { if (visitor) { diff --git a/gui/src/Visualization/VisualizationZoneWidget.cpp b/gui/src/Visualization/VisualizationZoneWidget.cpp index e5cfd59..6c93f50 100644 --- a/gui/src/Visualization/VisualizationZoneWidget.cpp +++ b/gui/src/Visualization/VisualizationZoneWidget.cpp @@ -72,21 +72,6 @@ struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate { QUuid m_SynchronisationGroupId; std::unique_ptr m_Synchronizer; - // Returns the first graph in the zone or nullptr if there is no graph inside - VisualizationGraphWidget *firstGraph(const VisualizationZoneWidget *zoneWidget) const - { - VisualizationGraphWidget *firstGraph = nullptr; - auto layout = zoneWidget->ui->dragDropContainer->layout(); - if (layout->count() > 0) { - if (auto visualizationGraphWidget - = qobject_cast(layout->itemAt(0)->widget())) { - firstGraph = visualizationGraphWidget; - } - } - - return firstGraph; - } - void dropGraph(int index, VisualizationZoneWidget *zoneWidget); void dropVariables(const QList > &variables, int index, VisualizationZoneWidget *zoneWidget); @@ -110,6 +95,8 @@ VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *p VisualizationDragDropContainer::DropBehavior::Merged); ui->dragDropContainer->setMimeType(MIME_TYPE_ZONE, VisualizationDragDropContainer::DropBehavior::Forbidden); + ui->dragDropContainer->setMimeType(MIME_TYPE_SELECTION_ZONE, + VisualizationDragDropContainer::DropBehavior::Forbidden); ui->dragDropContainer->setAcceptMimeDataFunction([this](auto mimeData) { return sqpApp->dragDropHelper().checkMimeDataForVisualization(mimeData, ui->dragDropContainer); @@ -284,7 +271,7 @@ VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptrfirstGraph(this)) { + if (auto firstGraph = this->firstGraph()) { // Case of a new graph in a existant zone range = firstGraph->graphRange(); } @@ -316,6 +303,20 @@ VisualizationZoneWidget::createGraph(const QList > var return graphWidget; } +VisualizationGraphWidget *VisualizationZoneWidget::firstGraph() const +{ + VisualizationGraphWidget *firstGraph = nullptr; + auto layout = ui->dragDropContainer->layout(); + if (layout->count() > 0) { + if (auto visualizationGraphWidget + = qobject_cast(layout->itemAt(0)->widget())) { + firstGraph = visualizationGraphWidget; + } + } + + return firstGraph; +} + void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor) { if (visitor) { @@ -352,12 +353,14 @@ QString VisualizationZoneWidget::name() const return ui->zoneNameLabel->text(); } -QMimeData *VisualizationZoneWidget::mimeData() const +QMimeData *VisualizationZoneWidget::mimeData(const QPoint &position) const { + Q_UNUSED(position); + auto mimeData = new QMimeData; mimeData->setData(MIME_TYPE_ZONE, QByteArray{}); - if (auto firstGraph = impl->firstGraph(this)) { + if (auto firstGraph = this->firstGraph()) { auto timeRangeData = TimeController::mimeDataForTimeRange(firstGraph->graphRange()); mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData); } @@ -543,7 +546,8 @@ void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropGraph( } // Creates the new graph in the zone - zoneWidget->createGraph(variables, index); + auto newGraphWidget = zoneWidget->createGraph(variables, index); + newGraphWidget->addSelectionZones(graphWidget->selectionZoneRanges()); } else { // The drop occurred in the same zone or the graph is empty