##// END OF EJS Templates
Implements cursor mode
trabillard -
r1003:569b12637de1
parent child
Show More
@@ -0,0 +1,26
1 #ifndef VISUALIZATIONCURSORITEM_H
2 #define VISUALIZATIONCURSORITEM_H
3
4 #include <Common/spimpl.h>
5 #include <SqpApplication.h>
6
7 class QCustomPlot;
8
9 class VisualizationCursorItem {
10 public:
11 VisualizationCursorItem(QCustomPlot *plot);
12
13 void setVisible(bool value);
14 bool isVisible() const;
15
16 void setPosition(double value);
17 void setAbsolutePosition(double value);
18 void setOrientation(Qt::Orientation orientation);
19 void setLabelText(const QString &text);
20
21 private:
22 class VisualizationCursorItemPrivate;
23 spimpl::unique_impl_ptr<VisualizationCursorItemPrivate> impl;
24 };
25
26 #endif // VISUALIZATIONCURSORITEM_H
@@ -0,0 +1,163
1 #include <Common/DateUtils.h>
2 #include <Visualization/VisualizationCursorItem.h>
3 #include <Visualization/qcustomplot.h>
4
5 /// Width of the cursor in pixel
6 const auto CURSOR_WIDTH = 3;
7
8 /// Color of the cursor in the graph
9 const auto CURSOR_COLOR = QColor{68, 114, 196};
10
11 /// Line style of the cursor in the graph
12 auto CURSOR_PEN_STYLE = Qt::DotLine;
13
14 struct VisualizationCursorItem::VisualizationCursorItemPrivate {
15
16 QCustomPlot *m_Plot = nullptr;
17
18 QCPItemStraightLine *m_LineItem = nullptr;
19 QCPItemText *m_LabelItem = nullptr;
20
21 Qt::Orientation m_Orientation;
22 double m_Position = 0.0;
23 bool m_IsAbsolutePosition = false;
24 QString m_LabelText;
25
26 explicit VisualizationCursorItemPrivate(QCustomPlot *plot)
27 : m_Plot(plot), m_Orientation(Qt::Vertical)
28 {
29 }
30
31 void updateOrientation()
32 {
33 if (m_LineItem) {
34 switch (m_Orientation) {
35 case Qt::Vertical:
36 m_LineItem->point1->setTypeX(m_IsAbsolutePosition
37 ? QCPItemPosition::ptAbsolute
38 : QCPItemPosition::ptPlotCoords);
39 m_LineItem->point1->setTypeY(QCPItemPosition::ptAxisRectRatio);
40 m_LineItem->point2->setTypeX(m_IsAbsolutePosition
41 ? QCPItemPosition::ptAbsolute
42 : QCPItemPosition::ptPlotCoords);
43 m_LineItem->point2->setTypeY(QCPItemPosition::ptAxisRectRatio);
44 m_LabelItem->setPositionAlignment(Qt::AlignLeft | Qt::AlignTop);
45 break;
46 case Qt::Horizontal:
47 m_LineItem->point1->setTypeX(QCPItemPosition::ptAxisRectRatio);
48 m_LineItem->point1->setTypeY(m_IsAbsolutePosition
49 ? QCPItemPosition::ptAbsolute
50 : QCPItemPosition::ptPlotCoords);
51 m_LineItem->point2->setTypeX(QCPItemPosition::ptAxisRectRatio);
52 m_LineItem->point2->setTypeY(m_IsAbsolutePosition
53 ? QCPItemPosition::ptAbsolute
54 : QCPItemPosition::ptPlotCoords);
55 m_LabelItem->setPositionAlignment(Qt::AlignRight | Qt::AlignBottom);
56 }
57 }
58 }
59
60 void updateCursorPosition()
61 {
62 if (m_LineItem) {
63 switch (m_Orientation) {
64 case Qt::Vertical:
65 m_LineItem->point1->setCoords(m_Position, 0);
66 m_LineItem->point2->setCoords(m_Position, 1);
67 m_LabelItem->position->setCoords(5, 5);
68 break;
69 case Qt::Horizontal:
70 m_LineItem->point1->setCoords(1, m_Position);
71 m_LineItem->point2->setCoords(0, m_Position);
72 m_LabelItem->position->setCoords(-5, -5);
73 }
74 }
75 }
76
77 void updateLabelText()
78 {
79 if (m_LabelItem) {
80 m_LabelItem->setText(m_LabelText);
81 }
82 }
83 };
84
85 VisualizationCursorItem::VisualizationCursorItem(QCustomPlot *plot)
86 : impl{spimpl::make_unique_impl<VisualizationCursorItemPrivate>(plot)}
87 {
88 }
89
90 void VisualizationCursorItem::setVisible(bool value)
91 {
92 if (value != isVisible()) {
93
94 if (value) {
95 Q_ASSERT(!impl->m_LineItem && !impl->m_Plot);
96
97 impl->m_LineItem = new QCPItemStraightLine{impl->m_Plot};
98 auto pen = QPen{CURSOR_PEN_STYLE};
99 pen.setColor(CURSOR_COLOR);
100 pen.setWidth(CURSOR_WIDTH);
101 impl->m_LineItem->setPen(pen);
102 impl->m_LineItem->setSelectable(false);
103
104 impl->m_LabelItem = new QCPItemText{impl->m_Plot};
105 impl->m_LabelItem->setColor(CURSOR_COLOR);
106 impl->m_LabelItem->setSelectable(false);
107 impl->m_LabelItem->position->setParentAnchor(impl->m_LineItem->point1);
108 impl->m_LabelItem->position->setTypeX(QCPItemPosition::ptAbsolute);
109 impl->m_LabelItem->position->setTypeY(QCPItemPosition::ptAbsolute);
110
111 auto font = impl->m_LabelItem->font();
112 font.setPointSize(10);
113 font.setBold(true);
114 impl->m_LabelItem->setFont(font);
115
116 impl->updateOrientation();
117 impl->updateLabelText();
118 impl->updateCursorPosition();
119 }
120 else {
121 Q_ASSERT(impl->m_LineItem && impl->m_Plot);
122
123 // Note: the items are destroyed by QCustomPlot in removeItem
124 impl->m_Plot->removeItem(impl->m_LineItem);
125 impl->m_LineItem = nullptr;
126 impl->m_Plot->removeItem(impl->m_LabelItem);
127 impl->m_LabelItem = nullptr;
128 }
129 }
130 }
131
132 bool VisualizationCursorItem::isVisible() const
133 {
134 return impl->m_LineItem != nullptr;
135 }
136
137 void VisualizationCursorItem::setPosition(double value)
138 {
139 impl->m_Position = value;
140 impl->m_IsAbsolutePosition = false;
141 impl->updateLabelText();
142 impl->updateCursorPosition();
143 }
144
145 void VisualizationCursorItem::setAbsolutePosition(double value)
146 {
147 setPosition(value);
148 impl->m_IsAbsolutePosition = true;
149 }
150
151 void VisualizationCursorItem::setOrientation(Qt::Orientation orientation)
152 {
153 impl->m_Orientation = orientation;
154 impl->updateLabelText();
155 impl->updateOrientation();
156 impl->updateCursorPosition();
157 }
158
159 void VisualizationCursorItem::setLabelText(const QString &text)
160 {
161 impl->m_LabelText = text;
162 impl->updateLabelText();
163 }
@@ -4,4 +4,5
4 4 /// Minimum height for graph added in zones (in pixels)
5 5 extern const int GRAPH_MINIMUM_HEIGHT;
6 6
7
7 8 #endif // SCIQLOP_VISUALIZATIONDEF_H
@@ -62,6 +62,18 public:
62 62 bool isDragAllowed() const override;
63 63 void highlightForMerge(bool highlighted) override;
64 64
65 // Cursors
66 /// Adds or moves the vertical cursor at the specified value on the x-axis
67 void addVerticalCursor(double time);
68 /// Adds or moves the vertical cursor at the specified value on the x-axis
69 void addVerticalCursorAtViewportPosition(double position);
70 void removeVerticalCursor();
71 /// Adds or moves the vertical cursor at the specified value on the y-axis
72 void addHorizontalCursor(double value);
73 /// Adds or moves the vertical cursor at the specified value on the y-axis
74 void addHorizontalCursorAtViewportPosition(double position);
75 void removeHorizontalCursor();
76
65 77 signals:
66 78 void synchronize(const SqpRange &range, const SqpRange &oldRange);
67 79 void requestDataLoading(QVector<std::shared_ptr<Variable> > variable, const SqpRange &range,
@@ -70,6 +70,10 public:
70 70 QMimeData *mimeData() const override;
71 71 bool isDragAllowed() const override;
72 72
73 void notifyMouseMoveInGraph(const QPointF &graphPosition, const QPointF &plotPosition,
74 VisualizationGraphWidget *graphWidget);
75 void notifyMouseLeaveGraph(VisualizationGraphWidget *graphWidget);
76
73 77 protected:
74 78 void closeEvent(QCloseEvent *event) override;
75 79
@@ -79,7 +79,8 gui_sources = [
79 79 'src/Visualization/VisualizationDragWidget.cpp',
80 80 'src/Visualization/AxisRenderingUtils.cpp',
81 81 'src/Visualization/PlottablesRenderingUtils.cpp',
82 'src/Visualization/MacScrollBarStyle.cpp'
82 'src/Visualization/MacScrollBarStyle.cpp',
83 'src/Visualization/VisualizationCursorItem.cpp'
83 84 ]
84 85
85 86 gui_inc = include_directories(['include'])
@@ -21,7 +21,7 struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate {
21 21 QVBoxLayout *m_Layout;
22 22 QHash<QString, VisualizationDragDropContainer::DropBehavior> m_AcceptedMimeTypes;
23 23 QString m_PlaceHolderText;
24 DragDropHelper::PlaceHolderType m_PlaceHolderType = DragDropHelper::PlaceHolderType::Graph;
24 DragDropHelper::PlaceHolderType m_PlaceHolderType;
25 25
26 26 VisualizationDragDropContainer::AcceptMimeDataFunction m_AcceptMimeDataFun
27 27 = [](auto mimeData) { return true; };
@@ -29,6 +29,7 struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate {
29 29 int m_MinContainerHeight = 0;
30 30
31 31 explicit VisualizationDragDropContainerPrivate(QWidget *widget)
32 : m_PlaceHolderType(DragDropHelper::PlaceHolderType::Graph)
32 33 {
33 34 m_Layout = new QVBoxLayout(widget);
34 35 m_Layout->setContentsMargins(0, 0, 0, 0);
@@ -1,5 +1,6
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 #include "Visualization/VisualizationCursorItem.h"
3 4 #include "Visualization/VisualizationDefs.h"
4 5 #include "Visualization/VisualizationGraphHelper.h"
5 6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
@@ -37,6 +38,9 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
37 38 /// Minimum size for the zoom box, in percentage of the axis range
38 39 const auto ZOOM_BOX_MIN_SIZE = 0.8;
39 40
41 /// Format of the dates appearing in the label of a cursor
42 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
43
40 44 } // namespace
41 45
42 46 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
@@ -58,6 +62,8 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
58 62 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
59 63
60 64 QCPItemRect *m_DrawingRect = nullptr;
65 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
66 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
61 67
62 68 void configureDrawingRect()
63 69 {
@@ -96,6 +102,14 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
96 102 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
97 103 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
98 104 }
105
106 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
107 {
108 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
109 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
110
111 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
112 }
99 113 };
100 114
101 115 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
@@ -116,6 +130,12 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget
116 130 // The delegate must be initialized after the ui as it uses the plot
117 131 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
118 132
133 // Init the cursors
134 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
135 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
136 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
137 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
138
119 139 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
120 140 connect(ui->widget, &QCustomPlot::mouseRelease, this,
121 141 &VisualizationGraphWidget::onMouseRelease);
@@ -308,6 +328,55 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
308 328 plot().update();
309 329 }
310 330
331 void VisualizationGraphWidget::addVerticalCursor(double time)
332 {
333 impl->m_VerticalCursor->setPosition(time);
334 impl->m_VerticalCursor->setVisible(true);
335
336 auto text
337 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
338 impl->m_VerticalCursor->setLabelText(text);
339 }
340
341 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
342 {
343 impl->m_VerticalCursor->setAbsolutePosition(position);
344 impl->m_VerticalCursor->setVisible(true);
345
346 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
347 auto text
348 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
349 impl->m_VerticalCursor->setLabelText(text);
350 }
351
352 void VisualizationGraphWidget::removeVerticalCursor()
353 {
354 impl->m_VerticalCursor->setVisible(false);
355 plot().replot(QCustomPlot::rpQueuedReplot);
356 }
357
358 void VisualizationGraphWidget::addHorizontalCursor(double value)
359 {
360 impl->m_HorizontalCursor->setPosition(value);
361 impl->m_HorizontalCursor->setVisible(true);
362 impl->m_HorizontalCursor->setLabelText(QString::number(value));
363 }
364
365 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
366 {
367 impl->m_HorizontalCursor->setAbsolutePosition(position);
368 impl->m_HorizontalCursor->setVisible(true);
369
370 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
371 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
372 }
373
374 void VisualizationGraphWidget::removeHorizontalCursor()
375 {
376 impl->m_HorizontalCursor->setVisible(false);
377 plot().replot(QCustomPlot::rpQueuedReplot);
378 }
379
311 380 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
312 381 {
313 382 Q_UNUSED(event);
@@ -328,6 +397,13 void VisualizationGraphWidget::leaveEvent(QEvent *event)
328 397 {
329 398 Q_UNUSED(event);
330 399 impl->m_RenderingDelegate->showGraphOverlay(false);
400
401 if (auto parentZone = parentZoneWidget()) {
402 parentZone->notifyMouseLeaveGraph(this);
403 }
404 else {
405 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
406 }
331 407 }
332 408
333 409 QCustomPlot &VisualizationGraphWidget::plot() noexcept
@@ -380,6 +456,20 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange
380 456 emit synchronize(graphRange, oldGraphRange);
381 457 }
382 458 }
459
460 auto pos = mapFromGlobal(QCursor::pos());
461 auto axisPos = impl->posToAxisPos(pos, plot());
462 if (auto parentZone = parentZoneWidget()) {
463 if (impl->pointIsInAxisRect(axisPos, plot())) {
464 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
465 }
466 else {
467 parentZone->notifyMouseLeaveGraph(this);
468 }
469 }
470 else {
471 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
472 }
383 473 }
384 474
385 475 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
@@ -387,11 +477,24 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
387 477 // Handles plot rendering when mouse is moving
388 478 impl->m_RenderingDelegate->onMouseMove(event);
389 479
480 auto axisPos = impl->posToAxisPos(event->pos(), plot());
481
390 482 if (impl->m_DrawingRect) {
391 auto axisPos = impl->posToAxisPos(event->pos(), plot());
392 483 impl->m_DrawingRect->bottomRight->setCoords(axisPos);
393 484 }
394 485
486 if (auto parentZone = parentZoneWidget()) {
487 if (impl->pointIsInAxisRect(axisPos, plot())) {
488 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
489 }
490 else {
491 parentZone->notifyMouseLeaveGraph(this);
492 }
493 }
494 else {
495 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
496 }
497
395 498 VisualizationDragWidget::mouseMoveEvent(event);
396 499 }
397 500
@@ -53,7 +53,7 void processGraphs(QLayout &layout, Fun fun)
53 53 for (auto i = 0; i < layout.count(); ++i) {
54 54 if (auto item = layout.itemAt(i)) {
55 55 if (auto visualizationGraphWidget
56 = dynamic_cast<VisualizationGraphWidget *>(item->widget())) {
56 = qobject_cast<VisualizationGraphWidget *>(item->widget())) {
57 57 fun(*visualizationGraphWidget);
58 58 }
59 59 }
@@ -347,6 +347,59 bool VisualizationZoneWidget::isDragAllowed() const
347 347 return true;
348 348 }
349 349
350 void VisualizationZoneWidget::notifyMouseMoveInGraph(const QPointF &graphPosition,
351 const QPointF &plotPosition,
352 VisualizationGraphWidget *graphWidget)
353 {
354 processGraphs(*ui->dragDropContainer->layout(), [&graphPosition, &plotPosition, &graphWidget](
355 VisualizationGraphWidget &processedGraph) {
356
357 switch (sqpApp->plotsCursorMode()) {
358 case SqpApplication::PlotsCursorMode::Vertical:
359 processedGraph.removeHorizontalCursor();
360 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
361 break;
362 case SqpApplication::PlotsCursorMode::Temporal:
363 processedGraph.addVerticalCursor(plotPosition.x());
364 processedGraph.removeHorizontalCursor();
365 break;
366 case SqpApplication::PlotsCursorMode::Horizontal:
367 processedGraph.removeVerticalCursor();
368 if (&processedGraph == graphWidget) {
369 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
370 }
371 else {
372 processedGraph.removeHorizontalCursor();
373 }
374 break;
375 case SqpApplication::PlotsCursorMode::Cross:
376 if (&processedGraph == graphWidget) {
377 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
378 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
379 }
380 else {
381 processedGraph.removeHorizontalCursor();
382 processedGraph.removeVerticalCursor();
383 }
384 break;
385 case SqpApplication::PlotsCursorMode::NoCursor:
386 processedGraph.removeHorizontalCursor();
387 processedGraph.removeVerticalCursor();
388 break;
389 }
390
391
392 });
393 }
394
395 void VisualizationZoneWidget::notifyMouseLeaveGraph(VisualizationGraphWidget *graphWidget)
396 {
397 processGraphs(*ui->dragDropContainer->layout(), [](VisualizationGraphWidget &processedGraph) {
398 processedGraph.removeHorizontalCursor();
399 processedGraph.removeVerticalCursor();
400 });
401 }
402
350 403 void VisualizationZoneWidget::closeEvent(QCloseEvent *event)
351 404 {
352 405 // Closes graphs in the zone
General Comments 1
Under Review
author

Auto status change to "Under Review"

You need to be logged in to leave comments. Login now