##// END OF EJS Templates
Merge branch 'feature/InteractionModesAndCursors' into develop
trabillard -
r966:cf3e9de4d0c6 merge
parent child
Show More
@@ -0,0 +1,26
1 #ifndef SCIQLOP_VISUALIZATIONCURSORITEM_H
2 #define SCIQLOP_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 // SCIQLOP_VISUALIZATIONCURSORITEM_H
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
NO CONTENT: new file 100644, binary diff hidden
@@ -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 }
@@ -168,9 +168,9 MainWindow::MainWindow(QWidget *parent)
168 openInspector(checked, true, openRightInspectorAction);
168 openInspector(checked, true, openRightInspectorAction);
169 });
169 });
170
170
171 // //// //
171 // //////////////// //
172 // Menu //
172 // Menu and Toolbar //
173 // //// //
173 // //////////////// //
174 this->menuBar()->addAction(tr("File"));
174 this->menuBar()->addAction(tr("File"));
175 auto toolsMenu = this->menuBar()->addMenu(tr("Tools"));
175 auto toolsMenu = this->menuBar()->addMenu(tr("Tools"));
176 toolsMenu->addAction(tr("Settings..."), [this]() {
176 toolsMenu->addAction(tr("Settings..."), [this]() {
@@ -189,6 +189,105 MainWindow::MainWindow(QWidget *parent)
189 auto timeWidget = new TimeWidget{};
189 auto timeWidget = new TimeWidget{};
190 mainToolBar->addWidget(timeWidget);
190 mainToolBar->addWidget(timeWidget);
191
191
192 auto actionPointerMode = new QAction{QIcon(":/icones/pointer.png"), "Pointer", this};
193 actionPointerMode->setCheckable(true);
194 actionPointerMode->setChecked(sqpApp->plotsInteractionMode()
195 == SqpApplication::PlotsInteractionMode::None);
196 connect(actionPointerMode, &QAction::triggered,
197 []() { sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::None); });
198
199 auto actionZoomMode = new QAction{QIcon(":/icones/zoom.png"), "Zoom", this};
200 actionZoomMode->setCheckable(true);
201 actionZoomMode->setChecked(sqpApp->plotsInteractionMode()
202 == SqpApplication::PlotsInteractionMode::ZoomBox);
203 connect(actionZoomMode, &QAction::triggered, []() {
204 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::ZoomBox);
205 });
206
207 auto actionOrganisationMode = new QAction{QIcon(":/icones/drag.png"), "Organize", this};
208 actionOrganisationMode->setCheckable(true);
209 actionOrganisationMode->setChecked(sqpApp->plotsInteractionMode()
210 == SqpApplication::PlotsInteractionMode::DragAndDrop);
211 connect(actionOrganisationMode, &QAction::triggered, []() {
212 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::DragAndDrop);
213 });
214
215 auto actionZonesMode = new QAction{QIcon(":/icones/rectangle.png"), "Zones", this};
216 actionZonesMode->setCheckable(true);
217 actionZonesMode->setChecked(sqpApp->plotsInteractionMode()
218 == SqpApplication::PlotsInteractionMode::SelectionZones);
219 connect(actionZonesMode, &QAction::triggered, []() {
220 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::SelectionZones);
221 });
222
223 auto modeActionGroup = new QActionGroup{this};
224 modeActionGroup->addAction(actionZoomMode);
225 modeActionGroup->addAction(actionZonesMode);
226 modeActionGroup->addAction(actionOrganisationMode);
227 modeActionGroup->addAction(actionPointerMode);
228 modeActionGroup->setExclusive(true);
229
230 mainToolBar->addSeparator();
231 mainToolBar->addAction(actionPointerMode);
232 mainToolBar->addAction(actionZoomMode);
233 mainToolBar->addAction(actionOrganisationMode);
234 mainToolBar->addAction(actionZonesMode);
235 mainToolBar->addSeparator();
236
237 auto btnCursor = new QToolButton{this};
238 btnCursor->setIcon(QIcon(":/icones/cursor.png"));
239 btnCursor->setText("Cursor");
240 btnCursor->setToolTip("Cursor");
241 btnCursor->setPopupMode(QToolButton::InstantPopup);
242 auto cursorMenu = new QMenu("CursorMenu", this);
243 btnCursor->setMenu(cursorMenu);
244
245 auto noCursorAction = cursorMenu->addAction("No Cursor");
246 noCursorAction->setCheckable(true);
247 noCursorAction->setChecked(sqpApp->plotsCursorMode()
248 == SqpApplication::PlotsCursorMode::NoCursor);
249 connect(noCursorAction, &QAction::triggered,
250 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::NoCursor); });
251
252 cursorMenu->addSeparator();
253 auto verticalCursorAction = cursorMenu->addAction("Vertical Cursor");
254 verticalCursorAction->setCheckable(true);
255 verticalCursorAction->setChecked(sqpApp->plotsCursorMode()
256 == SqpApplication::PlotsCursorMode::Vertical);
257 connect(verticalCursorAction, &QAction::triggered,
258 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Vertical); });
259
260 auto temporalCursorAction = cursorMenu->addAction("Temporal Cursor");
261 temporalCursorAction->setCheckable(true);
262 temporalCursorAction->setChecked(sqpApp->plotsCursorMode()
263 == SqpApplication::PlotsCursorMode::Temporal);
264 connect(temporalCursorAction, &QAction::triggered,
265 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Temporal); });
266
267 auto horizontalCursorAction = cursorMenu->addAction("Horizontal Cursor");
268 horizontalCursorAction->setCheckable(true);
269 horizontalCursorAction->setChecked(sqpApp->plotsCursorMode()
270 == SqpApplication::PlotsCursorMode::Horizontal);
271 connect(horizontalCursorAction, &QAction::triggered,
272 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Horizontal); });
273
274 auto crossCursorAction = cursorMenu->addAction("Cross Cursor");
275 crossCursorAction->setCheckable(true);
276 crossCursorAction->setChecked(sqpApp->plotsCursorMode()
277 == SqpApplication::PlotsCursorMode::Cross);
278 connect(crossCursorAction, &QAction::triggered,
279 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Cross); });
280
281 mainToolBar->addWidget(btnCursor);
282
283 auto cursorModeActionGroup = new QActionGroup{this};
284 cursorModeActionGroup->setExclusive(true);
285 cursorModeActionGroup->addAction(noCursorAction);
286 cursorModeActionGroup->addAction(verticalCursorAction);
287 cursorModeActionGroup->addAction(temporalCursorAction);
288 cursorModeActionGroup->addAction(horizontalCursorAction);
289 cursorModeActionGroup->addAction(crossCursorAction);
290
192 // //////// //
291 // //////// //
193 // Settings //
292 // Settings //
194 // //////// //
293 // //////// //
@@ -4,4 +4,5
4 /// Minimum height for graph added in zones (in pixels)
4 /// Minimum height for graph added in zones (in pixels)
5 extern const int GRAPH_MINIMUM_HEIGHT;
5 extern const int GRAPH_MINIMUM_HEIGHT;
6
6
7
7 #endif // SCIQLOP_VISUALIZATIONDEF_H
8 #endif // SCIQLOP_VISUALIZATIONDEF_H
@@ -49,6 +49,16 public:
49 /// doesn't live in a thread and access gui
49 /// doesn't live in a thread and access gui
50 DragDropHelper &dragDropHelper() noexcept;
50 DragDropHelper &dragDropHelper() noexcept;
51
51
52 enum class PlotsInteractionMode { None, ZoomBox, DragAndDrop, SelectionZones };
53
54 enum class PlotsCursorMode { NoCursor, Vertical, Temporal, Horizontal, Cross };
55
56 PlotsInteractionMode plotsInteractionMode() const;
57 void setPlotsInteractionMode(PlotsInteractionMode mode);
58
59 PlotsCursorMode plotsCursorMode() const;
60 void setPlotsCursorMode(PlotsCursorMode mode);
61
52 private:
62 private:
53 class SqpApplicationPrivate;
63 class SqpApplicationPrivate;
54 spimpl::unique_impl_ptr<SqpApplicationPrivate> impl;
64 spimpl::unique_impl_ptr<SqpApplicationPrivate> impl;
@@ -62,6 +62,18 public:
62 bool isDragAllowed() const override;
62 bool isDragAllowed() const override;
63 void highlightForMerge(bool highlighted) override;
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 signals:
77 signals:
66 void synchronize(const SqpRange &range, const SqpRange &oldRange);
78 void synchronize(const SqpRange &range, const SqpRange &oldRange);
67 void requestDataLoading(QVector<std::shared_ptr<Variable> > variable, const SqpRange &range,
79 void requestDataLoading(QVector<std::shared_ptr<Variable> > variable, const SqpRange &range,
@@ -70,6 +70,10 public:
70 QMimeData *mimeData() const override;
70 QMimeData *mimeData() const override;
71 bool isDragAllowed() const override;
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 protected:
77 protected:
74 void closeEvent(QCloseEvent *event) override;
78 void closeEvent(QCloseEvent *event) override;
75
79
@@ -76,7 +76,8 gui_sources = [
76 'src/Visualization/VisualizationDragWidget.cpp',
76 'src/Visualization/VisualizationDragWidget.cpp',
77 'src/Visualization/AxisRenderingUtils.cpp',
77 'src/Visualization/AxisRenderingUtils.cpp',
78 'src/Visualization/PlottablesRenderingUtils.cpp',
78 'src/Visualization/PlottablesRenderingUtils.cpp',
79 'src/Visualization/MacScrollBarStyle.cpp'
79 'src/Visualization/MacScrollBarStyle.cpp',
80 'src/Visualization/VisualizationCursorItem.cpp'
80 ]
81 ]
81
82
82 gui_inc = include_directories(['include'])
83 gui_inc = include_directories(['include'])
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
@@ -13,5 +13,10
13 <file>icones/unplot.png</file>
13 <file>icones/unplot.png</file>
14 <file>icones/up.png</file>
14 <file>icones/up.png</file>
15 <file>icones/time.png</file>
15 <file>icones/time.png</file>
16 <file>icones/zoom.png</file>
17 <file>icones/rectangle.png</file>
18 <file>icones/drag.png</file>
19 <file>icones/cursor.png</file>
20 <file>icones/pointer.png</file>
16 </qresource>
21 </qresource>
17 </RCC>
22 </RCC>
@@ -21,7 +21,9 public:
21 m_TimeController{std::make_unique<TimeController>()},
21 m_TimeController{std::make_unique<TimeController>()},
22 m_NetworkController{std::make_unique<NetworkController>()},
22 m_NetworkController{std::make_unique<NetworkController>()},
23 m_VisualizationController{std::make_unique<VisualizationController>()},
23 m_VisualizationController{std::make_unique<VisualizationController>()},
24 m_DragDropHelper{std::make_unique<DragDropHelper>()}
24 m_DragDropHelper{std::make_unique<DragDropHelper>()},
25 m_PlotInterractionMode(SqpApplication::PlotsInteractionMode::None),
26 m_PlotCursorMode(SqpApplication::PlotsCursorMode::NoCursor)
25 {
27 {
26 // /////////////////////////////// //
28 // /////////////////////////////// //
27 // Connections between controllers //
29 // Connections between controllers //
@@ -90,6 +92,9 public:
90 QThread m_VisualizationControllerThread;
92 QThread m_VisualizationControllerThread;
91
93
92 std::unique_ptr<DragDropHelper> m_DragDropHelper;
94 std::unique_ptr<DragDropHelper> m_DragDropHelper;
95
96 SqpApplication::PlotsInteractionMode m_PlotInterractionMode;
97 SqpApplication::PlotsCursorMode m_PlotCursorMode;
93 };
98 };
94
99
95
100
@@ -161,3 +166,23 DragDropHelper &SqpApplication::dragDropHelper() noexcept
161 {
166 {
162 return *impl->m_DragDropHelper;
167 return *impl->m_DragDropHelper;
163 }
168 }
169
170 SqpApplication::PlotsInteractionMode SqpApplication::plotsInteractionMode() const
171 {
172 return impl->m_PlotInterractionMode;
173 }
174
175 void SqpApplication::setPlotsInteractionMode(SqpApplication::PlotsInteractionMode mode)
176 {
177 impl->m_PlotInterractionMode = mode;
178 }
179
180 SqpApplication::PlotsCursorMode SqpApplication::plotsCursorMode() const
181 {
182 return impl->m_PlotCursorMode;
183 }
184
185 void SqpApplication::setPlotsCursorMode(SqpApplication::PlotsCursorMode mode)
186 {
187 impl->m_PlotCursorMode = mode;
188 }
@@ -21,7 +21,7 struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate {
21 QVBoxLayout *m_Layout;
21 QVBoxLayout *m_Layout;
22 QHash<QString, VisualizationDragDropContainer::DropBehavior> m_AcceptedMimeTypes;
22 QHash<QString, VisualizationDragDropContainer::DropBehavior> m_AcceptedMimeTypes;
23 QString m_PlaceHolderText;
23 QString m_PlaceHolderText;
24 DragDropHelper::PlaceHolderType m_PlaceHolderType = DragDropHelper::PlaceHolderType::Graph;
24 DragDropHelper::PlaceHolderType m_PlaceHolderType;
25
25
26 VisualizationDragDropContainer::AcceptMimeDataFunction m_AcceptMimeDataFun
26 VisualizationDragDropContainer::AcceptMimeDataFunction m_AcceptMimeDataFun
27 = [](auto mimeData) { return true; };
27 = [](auto mimeData) { return true; };
@@ -29,6 +29,7 struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate {
29 int m_MinContainerHeight = 0;
29 int m_MinContainerHeight = 0;
30
30
31 explicit VisualizationDragDropContainerPrivate(QWidget *widget)
31 explicit VisualizationDragDropContainerPrivate(QWidget *widget)
32 : m_PlaceHolderType(DragDropHelper::PlaceHolderType::Graph)
32 {
33 {
33 m_Layout = new QVBoxLayout(widget);
34 m_Layout = new QVBoxLayout(widget);
34 m_Layout->setContentsMargins(0, 0, 0, 0);
35 m_Layout->setContentsMargins(0, 0, 0, 0);
@@ -4,6 +4,8
4 #include <QApplication>
4 #include <QApplication>
5 #include <QMouseEvent>
5 #include <QMouseEvent>
6
6
7 #include <SqpApplication.h>
8
7 struct VisualizationDragWidget::VisualizationDragWidgetPrivate {
9 struct VisualizationDragWidget::VisualizationDragWidgetPrivate {
8
10
9 QPoint m_DragStartPosition;
11 QPoint m_DragStartPosition;
@@ -38,16 +40,16 void VisualizationDragWidget::mouseMoveEvent(QMouseEvent *event)
38 return;
40 return;
39 }
41 }
40
42
41 if (!event->modifiers().testFlag(Qt::AltModifier)) {
43 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::DragAndDrop
42 return;
44 || event->modifiers().testFlag(Qt::AltModifier)) {
43 }
44
45
45 if ((event->pos() - impl->m_DragStartPosition).manhattanLength()
46 if ((event->pos() - impl->m_DragStartPosition).manhattanLength()
46 < QApplication::startDragDistance()) {
47 < QApplication::startDragDistance()) {
47 return;
48 return;
48 }
49 }
49
50
50 emit dragDetected(this, impl->m_DragStartPosition);
51 emit dragDetected(this, impl->m_DragStartPosition);
52 }
51
53
52 QWidget::mouseMoveEvent(event);
54 QWidget::mouseMoveEvent(event);
53 }
55 }
@@ -1,5 +1,6
1 #include "Visualization/VisualizationGraphWidget.h"
1 #include "Visualization/VisualizationGraphWidget.h"
2 #include "Visualization/IVisualizationWidgetVisitor.h"
2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 #include "Visualization/VisualizationCursorItem.h"
3 #include "Visualization/VisualizationDefs.h"
4 #include "Visualization/VisualizationDefs.h"
4 #include "Visualization/VisualizationGraphHelper.h"
5 #include "Visualization/VisualizationGraphHelper.h"
5 #include "Visualization/VisualizationGraphRenderingDelegate.h"
6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
@@ -23,10 +24,22 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
23 namespace {
24 namespace {
24
25
25 /// Key pressed to enable zoom on horizontal axis
26 /// Key pressed to enable zoom on horizontal axis
26 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::NoModifier;
27 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
27
28
28 /// Key pressed to enable zoom on vertical axis
29 /// Key pressed to enable zoom on vertical axis
29 const auto VERTICAL_ZOOM_MODIFIER = Qt::ControlModifier;
30 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
31
32 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
33 const auto PAN_SPEED = 5;
34
35 /// Key pressed to enable a calibration pan
36 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
37
38 /// Minimum size for the zoom box, in percentage of the axis range
39 const auto ZOOM_BOX_MIN_SIZE = 0.8;
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");
30
43
31 } // namespace
44 } // namespace
32
45
@@ -47,6 +60,56 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
47 bool m_IsCalibration;
60 bool m_IsCalibration;
48 /// Delegate used to attach rendering features to the plot
61 /// Delegate used to attach rendering features to the plot
49 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
62 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
63
64 QCPItemRect *m_DrawingRect = nullptr;
65 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
66 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
67
68 void configureDrawingRect()
69 {
70 if (m_DrawingRect) {
71 QPen p;
72 p.setWidth(2);
73 m_DrawingRect->setPen(p);
74 }
75 }
76
77 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
78 {
79 removeDrawingRect(plot);
80
81 auto axisPos = posToAxisPos(pos, plot);
82
83 m_DrawingRect = new QCPItemRect{&plot};
84 configureDrawingRect();
85
86 m_DrawingRect->topLeft->setCoords(axisPos);
87 m_DrawingRect->bottomRight->setCoords(axisPos);
88 }
89
90 void removeDrawingRect(QCustomPlot &plot)
91 {
92 if (m_DrawingRect) {
93 plot.removeItem(m_DrawingRect); // the item is deleted by QCustomPlot
94 m_DrawingRect = nullptr;
95 plot.replot(QCustomPlot::rpQueuedReplot);
96 }
97 }
98
99 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
100 {
101 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
102 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
103 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
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 }
50 };
113 };
51
114
52 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
115 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
@@ -62,12 +125,17 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget
62 // Set qcpplot properties :
125 // Set qcpplot properties :
63 // - Drag (on x-axis) and zoom are enabled
126 // - Drag (on x-axis) and zoom are enabled
64 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
127 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
65 ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectItems);
128 ui->widget->setInteractions(QCP::iRangeZoom | QCP::iSelectItems);
66 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal);
67
129
68 // The delegate must be initialized after the ui as it uses the plot
130 // The delegate must be initialized after the ui as it uses the plot
69 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
131 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
70
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
71 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
139 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
72 connect(ui->widget, &QCustomPlot::mouseRelease, this,
140 connect(ui->widget, &QCustomPlot::mouseRelease, this,
73 &VisualizationGraphWidget::onMouseRelease);
141 &VisualizationGraphWidget::onMouseRelease);
@@ -264,6 +332,55 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
264 plot().update();
332 plot().update();
265 }
333 }
266
334
335 void VisualizationGraphWidget::addVerticalCursor(double time)
336 {
337 impl->m_VerticalCursor->setPosition(time);
338 impl->m_VerticalCursor->setVisible(true);
339
340 auto text
341 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
342 impl->m_VerticalCursor->setLabelText(text);
343 }
344
345 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
346 {
347 impl->m_VerticalCursor->setAbsolutePosition(position);
348 impl->m_VerticalCursor->setVisible(true);
349
350 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
351 auto text
352 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
353 impl->m_VerticalCursor->setLabelText(text);
354 }
355
356 void VisualizationGraphWidget::removeVerticalCursor()
357 {
358 impl->m_VerticalCursor->setVisible(false);
359 plot().replot(QCustomPlot::rpQueuedReplot);
360 }
361
362 void VisualizationGraphWidget::addHorizontalCursor(double value)
363 {
364 impl->m_HorizontalCursor->setPosition(value);
365 impl->m_HorizontalCursor->setVisible(true);
366 impl->m_HorizontalCursor->setLabelText(QString::number(value));
367 }
368
369 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
370 {
371 impl->m_HorizontalCursor->setAbsolutePosition(position);
372 impl->m_HorizontalCursor->setVisible(true);
373
374 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
375 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
376 }
377
378 void VisualizationGraphWidget::removeHorizontalCursor()
379 {
380 impl->m_HorizontalCursor->setVisible(false);
381 plot().replot(QCustomPlot::rpQueuedReplot);
382 }
383
267 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
384 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
268 {
385 {
269 Q_UNUSED(event);
386 Q_UNUSED(event);
@@ -284,6 +401,13 void VisualizationGraphWidget::leaveEvent(QEvent *event)
284 {
401 {
285 Q_UNUSED(event);
402 Q_UNUSED(event);
286 impl->m_RenderingDelegate->showGraphOverlay(false);
403 impl->m_RenderingDelegate->showGraphOverlay(false);
404
405 if (auto parentZone = parentZoneWidget()) {
406 parentZone->notifyMouseLeaveGraph(this);
407 }
408 else {
409 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
410 }
287 }
411 }
288
412
289 QCustomPlot &VisualizationGraphWidget::plot() noexcept
413 QCustomPlot &VisualizationGraphWidget::plot() noexcept
@@ -336,6 +460,20 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange
336 emit synchronize(graphRange, oldGraphRange);
460 emit synchronize(graphRange, oldGraphRange);
337 }
461 }
338 }
462 }
463
464 auto pos = mapFromGlobal(QCursor::pos());
465 auto axisPos = impl->posToAxisPos(pos, plot());
466 if (auto parentZone = parentZoneWidget()) {
467 if (impl->pointIsInAxisRect(axisPos, plot())) {
468 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
469 }
470 else {
471 parentZone->notifyMouseLeaveGraph(this);
472 }
473 }
474 else {
475 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
476 }
339 }
477 }
340
478
341 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
479 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
@@ -343,38 +481,91 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
343 // Handles plot rendering when mouse is moving
481 // Handles plot rendering when mouse is moving
344 impl->m_RenderingDelegate->onMouseMove(event);
482 impl->m_RenderingDelegate->onMouseMove(event);
345
483
484 auto axisPos = impl->posToAxisPos(event->pos(), plot());
485
486 if (impl->m_DrawingRect) {
487 impl->m_DrawingRect->bottomRight->setCoords(axisPos);
488 }
489
490 if (auto parentZone = parentZoneWidget()) {
491 if (impl->pointIsInAxisRect(axisPos, plot())) {
492 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
493 }
494 else {
495 parentZone->notifyMouseLeaveGraph(this);
496 }
497 }
498 else {
499 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
500 }
501
346 VisualizationDragWidget::mouseMoveEvent(event);
502 VisualizationDragWidget::mouseMoveEvent(event);
347 }
503 }
348
504
349 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
505 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
350 {
506 {
351 auto zoomOrientations = QFlags<Qt::Orientation>{};
507 auto value = event->angleDelta().x() + event->angleDelta().y();
508 if (value != 0) {
509
510 auto direction = value > 0 ? 1.0 : -1.0;
511 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
512 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
513 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
514
515 auto zoomOrientations = QFlags<Qt::Orientation>{};
516 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
517 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
518
519 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
352
520
353 // Lambda that enables a zoom orientation if the key modifier related to this orientation
521 if (!isZoomX && !isZoomY) {
354 // has
522 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
355 // been pressed
523 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
356 auto enableOrientation
357 = [&zoomOrientations, event](const auto &orientation, const auto &modifier) {
358 auto orientationEnabled = event->modifiers().testFlag(modifier);
359 zoomOrientations.setFlag(orientation, orientationEnabled);
360 };
361 enableOrientation(Qt::Vertical, VERTICAL_ZOOM_MODIFIER);
362 enableOrientation(Qt::Horizontal, HORIZONTAL_ZOOM_MODIFIER);
363
524
364 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
525 axis->setRange(axis->range() + diff);
526
527 if (plot().noAntialiasingOnDrag()) {
528 plot().setNotAntialiasedElements(QCP::aeAll);
529 }
530
531 plot().replot(QCustomPlot::rpQueuedReplot);
532 }
533 }
365 }
534 }
366
535
367 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
536 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
368 {
537 {
369 impl->m_IsCalibration = event->modifiers().testFlag(Qt::ControlModifier);
538 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
370
539 impl->startDrawingRect(event->pos(), plot());
371 plot().setInteraction(QCP::iRangeDrag, !event->modifiers().testFlag(Qt::AltModifier));
540 }
372
541
373 VisualizationDragWidget::mousePressEvent(event);
542 VisualizationDragWidget::mousePressEvent(event);
374 }
543 }
375
544
376 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
545 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
377 {
546 {
547 if (impl->m_DrawingRect) {
548
549 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
550 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
551
552 auto newAxisXRange = QCPRange{impl->m_DrawingRect->topLeft->coords().x(),
553 impl->m_DrawingRect->bottomRight->coords().x()};
554
555 auto newAxisYRange = QCPRange{impl->m_DrawingRect->topLeft->coords().y(),
556 impl->m_DrawingRect->bottomRight->coords().y()};
557
558 impl->removeDrawingRect(plot());
559
560 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
561 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
562 axisX->setRange(newAxisXRange);
563 axisY->setRange(newAxisYRange);
564
565 plot().replot(QCustomPlot::rpQueuedReplot);
566 }
567 }
568
378 impl->m_IsCalibration = false;
569 impl->m_IsCalibration = false;
379 }
570 }
380
571
@@ -53,7 +53,7 void processGraphs(QLayout &layout, Fun fun)
53 for (auto i = 0; i < layout.count(); ++i) {
53 for (auto i = 0; i < layout.count(); ++i) {
54 if (auto item = layout.itemAt(i)) {
54 if (auto item = layout.itemAt(i)) {
55 if (auto visualizationGraphWidget
55 if (auto visualizationGraphWidget
56 = dynamic_cast<VisualizationGraphWidget *>(item->widget())) {
56 = qobject_cast<VisualizationGraphWidget *>(item->widget())) {
57 fun(*visualizationGraphWidget);
57 fun(*visualizationGraphWidget);
58 }
58 }
59 }
59 }
@@ -347,6 +347,59 bool VisualizationZoneWidget::isDragAllowed() const
347 return true;
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 void VisualizationZoneWidget::closeEvent(QCloseEvent *event)
403 void VisualizationZoneWidget::closeEvent(QCloseEvent *event)
351 {
404 {
352 // Closes graphs in the zone
405 // Closes graphs in the zone
General Comments 0
You need to be logged in to leave comments. Login now