diff --git a/gui/include/Visualization/VisualizationGraphWidget.h b/gui/include/Visualization/VisualizationGraphWidget.h index cefdddf..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); diff --git a/gui/include/Visualization/VisualizationSelectionZoneItem.h b/gui/include/Visualization/VisualizationSelectionZoneItem.h index 25220c8..9e1c3d2 100644 --- a/gui/include/Visualization/VisualizationSelectionZoneItem.h +++ b/gui/include/Visualization/VisualizationSelectionZoneItem.h @@ -6,6 +6,7 @@ #include class VisualizationSelectionZoneItem : public QCPItemRect { + public: VisualizationSelectionZoneItem(QCustomPlot *plot); virtual ~VisualizationSelectionZoneItem(); 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/meson.build b/gui/meson.build index c350f40..d923004 100644 --- a/gui/meson.build +++ b/gui/meson.build @@ -83,7 +83,8 @@ gui_sources = [ 'src/Visualization/ColorScaleEditor.cpp', 'src/Visualization/SqpColorScale.cpp', 'src/Visualization/QCPColorMapIterator.cpp', - 'src/Visualization/VisualizationSelectionZoneItem.cpp' + 'src/Visualization/VisualizationSelectionZoneItem.cpp', + 'src/Visualization/VisualizationSelectionZoneManager.cpp' ] gui_inc = include_directories(['include']) diff --git a/gui/src/Visualization/VisualizationGraphWidget.cpp b/gui/src/Visualization/VisualizationGraphWidget.cpp index 206dcdc..7e4a8de 100644 --- a/gui/src/Visualization/VisualizationGraphWidget.cpp +++ b/gui/src/Visualization/VisualizationGraphWidget.cpp @@ -5,6 +5,8 @@ #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" @@ -40,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; @@ -109,30 +114,30 @@ struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { } } - void startDrawingZone(const QPoint &pos, QCustomPlot &plot) + void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph) { - endDrawingZone(plot); + endDrawingZone(graph); - auto axisPos = posToAxisPos(pos, plot); + auto axisPos = posToAxisPos(pos, graph->plot()); - m_DrawingZone = new VisualizationSelectionZoneItem{&plot}; + m_DrawingZone = new VisualizationSelectionZoneItem{&graph->plot()}; m_DrawingZone->setRange(axisPos.x(), axisPos.x()); m_DrawingZone->setEditionEnabled(false); } - void endDrawingZone(QCustomPlot &plot) + 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); - m_SelectionZones.append(m_DrawingZone); + addSelectionZone(m_DrawingZone); } else { - plot.removeItem(m_DrawingZone); // the item is deleted by QCustomPlot + graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot } - plot.replot(QCustomPlot::rpQueuedReplot); + graph->plot().replot(QCustomPlot::rpQueuedReplot); m_DrawingZone = nullptr; } } @@ -144,6 +149,8 @@ struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { } } + void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; } + VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos, const QCustomPlot &plot) const { @@ -188,7 +195,7 @@ VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget // Set qcpplot properties : // - 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 @@ -207,9 +214,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); @@ -243,6 +251,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; @@ -348,9 +366,10 @@ QVector VisualizationGraphWidget::selectionZoneRanges() const 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->m_SelectionZones << zone; + impl->addSelectionZone(zone); } plot().replot(QCustomPlot::rpQueuedReplot); @@ -588,9 +607,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}; @@ -728,19 +747,19 @@ void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept { bool isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER); + auto isSelectionZoneMode + = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones; if (!isDragDropClick) { if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) { // Starts a zoom box impl->startDrawingRect(event->pos(), plot()); } - else if (sqpApp->plotsInteractionMode() - == SqpApplication::PlotsInteractionMode::SelectionZones - && impl->m_DrawingZone == nullptr) { + 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(), plot()); + impl->startDrawingZone(event->pos(), this); } } } @@ -753,10 +772,31 @@ void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept == SqpApplication::PlotsInteractionMode::None && !isDragDropClick); - // Allows zone edition only in selection zone mode without ALT pressed (ALT is for drag&drop) - impl->setSelectionZonesEditionEnabled( - sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones - && !isDragDropClick); + // Allows zone edition only in selection zone mode without drag&drop + impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick); + + // Selection + 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) { + parentVisualizationWidget()->selectionZoneManager().select( + {selectionZoneItemUnderCursor}); + } + else { + parentVisualizationWidget()->selectionZoneManager().setSelected( + selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected() + || event->button() == Qt::RightButton); + } + } + else if (!isMultiSelectionClick && event->button() == Qt::LeftButton) { + parentVisualizationWidget()->selectionZoneManager().clearSelection(); + } + else { + // No selection change + } + } VisualizationDragWidget::mousePressEvent(event); } @@ -786,7 +826,7 @@ void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept } } - impl->endDrawingZone(plot()); + impl->endDrawingZone(this); impl->m_IsCalibration = false; } diff --git a/gui/src/Visualization/VisualizationSelectionZoneItem.cpp b/gui/src/Visualization/VisualizationSelectionZoneItem.cpp index 096dde1..08b5829 100644 --- a/gui/src/Visualization/VisualizationSelectionZoneItem.cpp +++ b/gui/src/Visualization/VisualizationSelectionZoneItem.cpp @@ -1,5 +1,7 @@ #include "Visualization/VisualizationSelectionZoneItem.h" +const QString &DEFAULT_COLOR = QStringLiteral("#E79D41"); + struct VisualizationSelectionZoneItem::VisualizationSelectionZoneItemPrivate { QCustomPlot *m_Plot; @@ -54,6 +56,7 @@ VisualizationSelectionZoneItem::VisualizationSelectionZoneItem(QCustomPlot *plot 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); @@ -62,6 +65,7 @@ VisualizationSelectionZoneItem::VisualizationSelectionZoneItem(QCustomPlot *plot 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); @@ -70,16 +74,9 @@ VisualizationSelectionZoneItem::VisualizationSelectionZoneItem(QCustomPlot *plot impl->m_LeftLine->point1->setTypeY(QCPItemPosition::ptAbsolute); impl->m_LeftLine->point2->setTypeX(QCPItemPosition::ptAbsolute); impl->m_LeftLine->point2->setTypeY(QCPItemPosition::ptAbsolute); - - impl->m_RightLine->setSelectable(false); 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("#E79D41")); + setColor(QColor(DEFAULT_COLOR)); } VisualizationSelectionZoneItem::~VisualizationSelectionZoneItem() @@ -225,13 +222,14 @@ void VisualizationSelectionZoneItem::setHovered(bool value) void VisualizationSelectionZoneItem::mousePressEvent(QMouseEvent *event, const QVariant &details) { - if (isEditionEnabled()) { + 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; } else { + impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition; event->ignore(); } } @@ -252,8 +250,8 @@ void VisualizationSelectionZoneItem::mouseMoveEvent(QMouseEvent *event, const QP case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight: setEnd(impl->m_MovedOrinalT2 + diff); break; - // default: - // unknown edition mode + default: + break; } } else { 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/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) {