diff --git a/gui/include/Visualization/VisualizationMultiZoneSelectionDialog.h b/gui/include/Visualization/VisualizationMultiZoneSelectionDialog.h new file mode 100644 index 0000000..f3ebcfb --- /dev/null +++ b/gui/include/Visualization/VisualizationMultiZoneSelectionDialog.h @@ -0,0 +1,30 @@ +#ifndef SCIQLOP_VISUALIZATIONMULTIZONESELECTIONDIALOG_H +#define SCIQLOP_VISUALIZATIONMULTIZONESELECTIONDIALOG_H + +#include +#include + +namespace Ui { +class VisualizationMultiZoneSelectionDialog; +} + +class VisualizationSelectionZoneItem; + +class VisualizationMultiZoneSelectionDialog : public QDialog { + Q_OBJECT + +public: + explicit VisualizationMultiZoneSelectionDialog(QWidget *parent = 0); + ~VisualizationMultiZoneSelectionDialog(); + + void setZones(const QVector &zones); + QMap selectedZones() const; + +private: + Ui::VisualizationMultiZoneSelectionDialog *ui; + + class VisualizationMultiZoneSelectionDialogPrivate; + spimpl::unique_impl_ptr impl; +}; + +#endif // SCIQLOP_VISUALIZATIONMULTIZONESELECTIONDIALOG_H diff --git a/gui/include/Visualization/VisualizationSelectionZoneItem.h b/gui/include/Visualization/VisualizationSelectionZoneItem.h index 1eb81c5..d7b6e58 100644 --- a/gui/include/Visualization/VisualizationSelectionZoneItem.h +++ b/gui/include/Visualization/VisualizationSelectionZoneItem.h @@ -28,6 +28,10 @@ public: void setEditionEnabled(bool value); bool isEditionEnabled() const; + /// Moves the item at the top of its QCPLayer. It will then receive the mouse events if multiple + /// items are stacked on top of each others. + void moveToTop(); + Qt::CursorShape curshorShapeForPosition(const QPoint &position) const; void setHovered(bool value); diff --git a/gui/meson.build b/gui/meson.build index 747a2fd..4f918c6 100644 --- a/gui/meson.build +++ b/gui/meson.build @@ -18,7 +18,8 @@ gui_moc_headers = [ 'include/Visualization/VisualizationDragDropContainer.h', 'include/Visualization/VisualizationDragWidget.h', 'include/Visualization/ColorScaleEditor.h', - 'include/Visualization/SelectionZoneAction.h' + 'include/Visualization/SelectionZoneAction.h', + 'include/Visualization/VisualizationMultiZoneSelectionDialog.h' ] gui_ui_files = [ @@ -34,7 +35,8 @@ gui_ui_files = [ 'ui/Visualization/VisualizationTabWidget.ui', 'ui/Visualization/VisualizationWidget.ui', 'ui/Visualization/VisualizationZoneWidget.ui', - 'ui/Visualization/ColorScaleEditor.ui' + 'ui/Visualization/ColorScaleEditor.ui', + 'ui/Visualization/VisualizationMultiZoneSelectionDialog.ui' ] gui_qresources = ['resources/sqpguiresources.qrc'] @@ -88,7 +90,8 @@ gui_sources = [ 'src/Visualization/VisualizationSelectionZoneManager.cpp', 'src/Visualization/SelectionZoneAction.cpp', 'src/Visualization/ActionsGuiController.cpp', - 'src/Visualization/VisualizationActionManager.cpp' + 'src/Visualization/VisualizationActionManager.cpp', + 'src/Visualization/VisualizationMultiZoneSelectionDialog.cpp' ] gui_inc = include_directories(['include']) diff --git a/gui/src/Visualization/VisualizationGraphWidget.cpp b/gui/src/Visualization/VisualizationGraphWidget.cpp index 9c6e733..52484ae 100644 --- a/gui/src/Visualization/VisualizationGraphWidget.cpp +++ b/gui/src/Visualization/VisualizationGraphWidget.cpp @@ -4,6 +4,7 @@ #include "Visualization/VisualizationDefs.h" #include "Visualization/VisualizationGraphHelper.h" #include "Visualization/VisualizationGraphRenderingDelegate.h" +#include "Visualization/VisualizationMultiZoneSelectionDialog.h" #include "Visualization/VisualizationSelectionZoneItem.h" #include "Visualization/VisualizationSelectionZoneManager.h" #include "Visualization/VisualizationWidget.h" @@ -170,6 +171,29 @@ struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate { return selectionZoneItemUnderCursor; } + QVector selectionZonesAt(const QPoint &pos, + const QCustomPlot &plot) const + { + QVector zones; + for (auto zone : m_SelectionZones) { + auto distanceToZone = zone->selectTest(pos, false); + if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) { + zones << zone; + } + } + + return zones; + } + + void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot) + { + if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) { + zone->moveToTop(); + m_SelectionZones.removeAll(zone); + m_SelectionZones.append(zone); + } + } + QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const { auto axisX = plot.axisRect()->axis(QCPAxis::atBottom); @@ -902,15 +926,49 @@ void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept 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}); + if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton + && !impl->m_HasMovedMouse) { + + auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot()); + if (zonesUnderCursor.count() > 1) { + // There are multiple zones under the mouse. + // Performs the selection with a selection dialog. + VisualizationMultiZoneSelectionDialog dialog{this}; + dialog.setZones(zonesUnderCursor); + dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20))); + dialog.activateWindow(); + dialog.raise(); + if (dialog.exec() == QDialog::Accepted) { + auto selection = dialog.selectedZones(); + + if (!isMultiSelectionClick) { + parentVisualizationWidget()->selectionZoneManager().clearSelection(); + } + + for (auto it = selection.cbegin(); it != selection.cend(); ++it) { + auto zone = it.key(); + auto isSelected = it.value(); + parentVisualizationWidget()->selectionZoneManager().setSelected(zone, + isSelected); + + if (isSelected) { + // Puts the zone on top of the stack so it can be moved or resized + impl->moveSelectionZoneOnTop(zone, plot()); + } + } + } } - else if (!impl->m_HasMovedMouse) { - parentVisualizationWidget()->selectionZoneManager().setSelected( - selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected() - || event->button() == Qt::RightButton); + else { + if (!isMultiSelectionClick) { + parentVisualizationWidget()->selectionZoneManager().select( + {selectionZoneItemUnderCursor}); + impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot()); + } + else { + parentVisualizationWidget()->selectionZoneManager().setSelected( + selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected() + || event->button() == Qt::RightButton); + } } } else { diff --git a/gui/src/Visualization/VisualizationMultiZoneSelectionDialog.cpp b/gui/src/Visualization/VisualizationMultiZoneSelectionDialog.cpp new file mode 100644 index 0000000..e625b52 --- /dev/null +++ b/gui/src/Visualization/VisualizationMultiZoneSelectionDialog.cpp @@ -0,0 +1,69 @@ +#include "Visualization/VisualizationMultiZoneSelectionDialog.h" +#include "ui_VisualizationMultiZoneSelectionDialog.h" + +#include "Common/DateUtils.h" +#include "Visualization/VisualizationSelectionZoneItem.h" + +const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss"); + +struct VisualizationMultiZoneSelectionDialog::VisualizationMultiZoneSelectionDialogPrivate { + QVector m_Zones; +}; + +VisualizationMultiZoneSelectionDialog::VisualizationMultiZoneSelectionDialog(QWidget *parent) + : QDialog(parent, Qt::Tool), + ui(new Ui::VisualizationMultiZoneSelectionDialog), + impl{spimpl::make_unique_impl()} +{ + ui->setupUi(this); + + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, + &VisualizationMultiZoneSelectionDialog::accept); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, + &VisualizationMultiZoneSelectionDialog::reject); +} + +VisualizationMultiZoneSelectionDialog::~VisualizationMultiZoneSelectionDialog() +{ + delete ui; +} + +void VisualizationMultiZoneSelectionDialog::setZones( + const QVector &zones) +{ + impl->m_Zones = zones; + + // Sorts the zones to display them in temporal order + std::sort(impl->m_Zones.begin(), impl->m_Zones.end(), [](auto zone1, auto zone2) { + return zone1->range().m_TStart < zone2->range().m_TStart; + }); + + // Adds the zones in the listwidget + for (auto zone : impl->m_Zones) { + auto name = zone->name(); + if (!name.isEmpty()) { + name += tr(": "); + } + + auto range = zone->range(); + name += DateUtils::dateTime(range.m_TStart).toString(DATETIME_FORMAT); + name += " - "; + name += DateUtils::dateTime(range.m_TEnd).toString(DATETIME_FORMAT); + + auto item = new QListWidgetItem(name, ui->listWidget); + item->setSelected(zone->selected()); + } +} + +QMap +VisualizationMultiZoneSelectionDialog::selectedZones() const +{ + QMap selectedZones; + + for (auto i = 0; i < ui->listWidget->count(); ++i) { + auto item = ui->listWidget->item(i); + selectedZones[impl->m_Zones[i]] = item->isSelected(); + } + + return selectedZones; +} diff --git a/gui/src/Visualization/VisualizationSelectionZoneItem.cpp b/gui/src/Visualization/VisualizationSelectionZoneItem.cpp index e20c58e..45fdf5c 100644 --- a/gui/src/Visualization/VisualizationSelectionZoneItem.cpp +++ b/gui/src/Visualization/VisualizationSelectionZoneItem.cpp @@ -253,6 +253,11 @@ bool VisualizationSelectionZoneItem::isEditionEnabled() const return impl->m_IsEditionEnabled; } +void VisualizationSelectionZoneItem::moveToTop() +{ + moveToLayer(layer(), false); +} + Qt::CursorShape VisualizationSelectionZoneItem::curshorShapeForPosition(const QPoint &position) const { @@ -327,6 +332,8 @@ bool VisualizationSelectionZoneItem::alignZonesTemporallyOnRight( void VisualizationSelectionZoneItem::mousePressEvent(QMouseEvent *event, const QVariant &details) { + Q_UNUSED(details); + if (isEditionEnabled() && event->button() == Qt::LeftButton) { impl->m_CurrentEditionMode = impl->getEditionMode(event->pos(), this); @@ -394,6 +401,8 @@ void VisualizationSelectionZoneItem::mouseMoveEvent(QMouseEvent *event, const QP void VisualizationSelectionZoneItem::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) { + Q_UNUSED(startPos); + if (isEditionEnabled()) { impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition; } diff --git a/gui/src/Visualization/VisualizationSelectionZoneManager.cpp b/gui/src/Visualization/VisualizationSelectionZoneManager.cpp index 0d7d8bd..ee679ce 100644 --- a/gui/src/Visualization/VisualizationSelectionZoneManager.cpp +++ b/gui/src/Visualization/VisualizationSelectionZoneManager.cpp @@ -39,7 +39,7 @@ void VisualizationSelectionZoneManager::clearSelection() { for (auto item : impl->m_SelectedItems) { item->setSelected(false); - item->parentPlot()->replot(); + item->parentPlot()->replot(QCustomPlot::rpQueuedReplot); } impl->m_SelectedItems.clear(); diff --git a/gui/ui/Visualization/VisualizationMultiZoneSelectionDialog.ui b/gui/ui/Visualization/VisualizationMultiZoneSelectionDialog.ui new file mode 100644 index 0000000..abc96fd --- /dev/null +++ b/gui/ui/Visualization/VisualizationMultiZoneSelectionDialog.ui @@ -0,0 +1,47 @@ + + + VisualizationMultiZoneSelectionDialog + + + + 0 + 0 + 270 + 175 + + + + Select... + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QAbstractItemView::ExtendedSelection + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + +