##// END OF EJS Templates
Added shortcut 'l' to toggle Y axis log scale (broken for spectro)...
jeandet -
r1501:db2de8e31bd4
parent child
Show More
@@ -1,185 +1,187
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
3 3
4 4 #include "Visualization/IVisualizationWidget.h"
5 5 #include "Visualization/VisualizationDragWidget.h"
6 6
7 7 #include <QLoggingCategory>
8 8 #include <QUuid>
9 9 #include <QWidget>
10 10
11 11 #include <memory>
12 12
13 13 #include <Common/spimpl.h>
14 14
15 15 #include <Data/DateTimeRange.h>
16 16
17 17 Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationGraphWidget)
18 18
19 19 class QCPRange;
20 20 class QCustomPlot;
21 21 class Variable2;
22 22 class VisualizationWidget;
23 23 class VisualizationZoneWidget;
24 24 class VisualizationSelectionZoneItem;
25 25
26 26 namespace Ui
27 27 {
28 28 class VisualizationGraphWidget;
29 29 } // namespace Ui
30 30
31 31 /// Defines options that can be associated with the graph
32 32 enum GraphFlag
33 33 {
34 34 DisableAll = 0x0, ///< Disables acquisition and synchronization
35 35 EnableAcquisition = 0x1, ///< When this flag is set, the change of the graph's range leads to
36 36 /// the acquisition of data
37 37 EnableSynchronization = 0x2, ///< When this flag is set, the change of the graph's range causes
38 38 /// the call to the synchronization of the graphs contained in the
39 39 /// same zone of this graph
40 40 EnableAll = ~DisableAll ///< Enables acquisition and synchronization
41 41 };
42 42
43 43 Q_DECLARE_FLAGS(GraphFlags, GraphFlag)
44 44 Q_DECLARE_OPERATORS_FOR_FLAGS(GraphFlags)
45 45
46 46 class VisualizationGraphWidget : public VisualizationDragWidget, public IVisualizationWidget
47 47 {
48 48 Q_OBJECT
49 49
50 50 friend class QCustomPlotSynchronizer;
51 51 friend class VisualizationGraphRenderingDelegate;
52 52
53 53 public:
54 54 explicit VisualizationGraphWidget(const QString& name = {}, QWidget* parent = 0);
55 55 virtual ~VisualizationGraphWidget();
56 56
57 57 /// Returns the VisualizationZoneWidget which contains the graph or nullptr
58 58 VisualizationZoneWidget* parentZoneWidget() const noexcept;
59 59
60 60 /// Returns the main VisualizationWidget which contains the graph or nullptr
61 61 VisualizationWidget* parentVisualizationWidget() const;
62 62
63 63 /// Sets graph options
64 64 void setFlags(GraphFlags flags);
65 65
66 66 void addVariable(std::shared_ptr<Variable2> variable, DateTimeRange range);
67 67
68 68 /// Removes a variable from the graph
69 69 void removeVariable(std::shared_ptr<Variable2> variable) noexcept;
70 70
71 71 /// Returns the list of all variables used in the graph
72 72 std::vector<std::shared_ptr<Variable2>> variables() const;
73 73
74 74 /// Sets the y-axis range based on the data of a variable
75 75 void setYRange(std::shared_ptr<Variable2> variable);
76 76 DateTimeRange graphRange() const noexcept;
77 77 void setGraphRange(const DateTimeRange& range, bool updateVar = false, bool forward = false);
78 78 void setAutoRangeOnVariableInitialization(bool value);
79 79
80 80 // Zones
81 81 /// Returns the ranges of all the selection zones on the graph
82 82 QVector<DateTimeRange> selectionZoneRanges() const;
83 83 /// Adds new selection zones in the graph
84 84 void addSelectionZones(const QVector<DateTimeRange>& ranges);
85 85 /// Adds a new selection zone in the graph
86 86 VisualizationSelectionZoneItem* addSelectionZone(
87 87 const QString& name, const DateTimeRange& range);
88 88 /// Removes the specified selection zone
89 89 void removeSelectionZone(VisualizationSelectionZoneItem* selectionZone);
90 90
91 91 /// Undo the last zoom done with a zoom box
92 92 void undoZoom();
93 93
94 94 void zoom(double factor, int center, Qt::Orientation orientation, bool forward = true);
95 95 void move(double factor, Qt::Orientation orientation, bool forward = true);
96 96 void move(double dx, double dy, bool forward = true);
97 97 void transform(const DateTimeRangeTransformation& tranformation, bool forward = true);
98 98
99 void toggle_y_log_scale();
100
99 101 // IVisualizationWidget interface
100 102 void accept(IVisualizationWidgetVisitor* visitor) override;
101 103 bool canDrop(Variable2& variable) const override;
102 104 bool contains(Variable2& variable) const override;
103 105 QString name() const override;
104 106
105 107 // VisualisationDragWidget
106 108 QMimeData* mimeData(const QPoint& position) const override;
107 109 QPixmap customDragPixmap(const QPoint& dragPosition) override;
108 110 bool isDragAllowed() const override;
109 111 void highlightForMerge(bool highlighted) override;
110 112
111 113 // Cursors
112 114 /// Adds or moves the vertical cursor at the specified value on the x-axis
113 115 void addVerticalCursor(double time);
114 116 /// Adds or moves the vertical cursor at the specified value on the x-axis
115 117 void addVerticalCursorAtViewportPosition(double position);
116 118 void removeVerticalCursor();
117 119 /// Adds or moves the vertical cursor at the specified value on the y-axis
118 120 void addHorizontalCursor(double value);
119 121 /// Adds or moves the vertical cursor at the specified value on the y-axis
120 122 void addHorizontalCursorAtViewportPosition(double position);
121 123 void removeHorizontalCursor();
122 124
123 125 signals:
124 126 void synchronize(const DateTimeRange& range, const DateTimeRange& oldRange);
125 127 void changeRange(const std::shared_ptr<Variable2>& variable, const DateTimeRange& range);
126 128
127 129 /// Signal emitted when the variable is about to be removed from the graph
128 130 void variableAboutToBeRemoved(std::shared_ptr<Variable2> var);
129 131 /// Signal emitted when the variable has been added to the graph
130 132 void variableAdded(std::shared_ptr<Variable2> var);
131 133
132 134
133 135 void zoom_sig(double factor, int center, Qt::Orientation orientation, bool forward = true);
134 136 void move_sig(double factor, Qt::Orientation orientation, bool forward = true);
135 137 void move_sig(double dx, double dy, bool forward = true);
136 138 void transform_sig(const DateTimeRangeTransformation& tranformation, bool forward = true);
137 139 void setrange_sig(const DateTimeRange& range, bool updateVar = false, bool forward = true);
138 140
139 141 protected:
140 142 void closeEvent(QCloseEvent* event) override;
141 143 void enterEvent(QEvent* event) override;
142 144 void leaveEvent(QEvent* event) override;
143 145 void wheelEvent(QWheelEvent* event) override;
144 146 void mouseMoveEvent(QMouseEvent* event) override;
145 147 void mouseReleaseEvent(QMouseEvent* event) override;
146 148 void mousePressEvent(QMouseEvent* event) override;
147 149 void mouseDoubleClickEvent(QMouseEvent* event) override;
148 150 void keyReleaseEvent(QKeyEvent* event) override;
149 151 void keyPressEvent(QKeyEvent* event) override;
150 152
151 153 QCustomPlot& plot() const noexcept;
152 154
153 155 private:
154 156 Ui::VisualizationGraphWidget* ui;
155 157
156 158 class VisualizationGraphWidgetPrivate;
157 159 spimpl::unique_impl_ptr<VisualizationGraphWidgetPrivate> impl;
158 160
159 161 private slots:
160 162 /// Slot called when right clicking on the graph (displays a menu)
161 163 void onGraphMenuRequested(const QPoint& pos) noexcept;
162 164
163 165 /// Rescale the X axe to range parameter
164 166 // void onRangeChanged(const QCPRange &newRange, const QCPRange &oldRange);
165 167
166 168 /// Slot called when a mouse double click was made
167 169 void onMouseDoubleClick(QMouseEvent* event) noexcept;
168 170 /// Slot called when a mouse move was made
169 171 void onMouseMove(QMouseEvent* event) noexcept;
170 172 /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done
171 173 void onMouseWheel(QWheelEvent* event) noexcept;
172 174 /// Slot called when a mouse press was made, to activate the calibration of a graph
173 175 void onMousePress(QMouseEvent* event) noexcept;
174 176 /// Slot called when a mouse release was made, to deactivate the calibration of a graph
175 177 void onMouseRelease(QMouseEvent* event) noexcept;
176 178
177 179 void onDataCacheVariableUpdated();
178 180
179 181 void onUpdateVarDisplaying(std::shared_ptr<Variable2> variable, const DateTimeRange& range);
180 182
181 183 void variableUpdated(QUuid id);
182 184 void variableDeleted(const std::shared_ptr<Variable2>&);
183 185 };
184 186
185 187 #endif // SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
@@ -1,1578 +1,1601
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationCursorItem.h"
4 4 #include "Visualization/VisualizationDefs.h"
5 5 #include "Visualization/VisualizationGraphHelper.h"
6 6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
7 7 #include "Visualization/VisualizationMultiZoneSelectionDialog.h"
8 8 #include "Visualization/VisualizationSelectionZoneItem.h"
9 9 #include "Visualization/VisualizationSelectionZoneManager.h"
10 10 #include "Visualization/VisualizationWidget.h"
11 11 #include "Visualization/VisualizationZoneWidget.h"
12 12 #include "ui_VisualizationGraphWidget.h"
13 13
14 14 #include <Actions/ActionsGuiController.h>
15 15 #include <Actions/FilteringAction.h>
16 16 #include <MimeTypes/MimeTypes.h>
17 17 #include <cpp_utils_qt/cpp_utils_qt.hpp>
18 18 #include <containers/algorithms.hpp>
19 19 #include <Data/DateTimeRangeHelper.h>
20 20 #include <DragAndDrop/DragDropGuiController.h>
21 21 #include <Settings/SqpSettingsDefs.h>
22 22 #include <SqpApplication.h>
23 23 #include <Time/TimeController.h>
24 24 #include <Variable/Variable2.h>
25 25 #include <Variable/VariableController2.h>
26 26
27 27 #include <unordered_map>
28 28
29 29 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
30 30
31 31 namespace
32 32 {
33 33
34 34 /// Key pressed to enable drag&drop in all modes
35 35 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
36 36
37 37 /// Key pressed to enable zoom on horizontal axis
38 38 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
39 39
40 40 /// Key pressed to enable zoom on vertical axis
41 41 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
42 42
43 43 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
44 44 const auto PAN_SPEED = 5;
45 45
46 46 /// Key pressed to enable a calibration pan
47 47 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
48 48
49 49 /// Key pressed to enable multi selection of selection zones
50 50 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
51 51
52 52 /// Minimum size for the zoom box, in percentage of the axis range
53 53 const auto ZOOM_BOX_MIN_SIZE = 0.8;
54 54
55 55 /// Format of the dates appearing in the label of a cursor
56 56 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
57 57
58 58 } // namespace
59 59
60 60 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate
61 61 {
62 62
63 63 explicit VisualizationGraphWidgetPrivate(const QString& name)
64 64 : m_Name { name }
65 65 , m_Flags { GraphFlag::EnableAll }
66 66 , m_IsCalibration { false }
67 67 , m_RenderingDelegate { nullptr }
68 68 {
69 69 m_plot = new QCustomPlot();
70 70 // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable.
71 71 m_plot->setPlottingHint(QCP::phFastPolylines, true);
72 72 }
73 73
74 74 void updateData(
75 75 PlottablesMap& plottables, std::shared_ptr<Variable2> variable, const DateTimeRange& range)
76 76 {
77 77 VisualizationGraphHelper::updateData(plottables, variable, range);
78 78
79 79 // Prevents that data has changed to update rendering
80 80 m_RenderingDelegate->onPlotUpdated();
81 81 }
82 82
83 83 QString m_Name;
84 84 // 1 variable -> n qcpplot
85 85 std::map<std::shared_ptr<Variable2>, PlottablesMap> m_VariableToPlotMultiMap;
86 86 GraphFlags m_Flags;
87 87 bool m_IsCalibration;
88 88 QCustomPlot* m_plot;
89 89 QPoint m_lastMousePos;
90 90 QCPRange m_lastXRange;
91 91 QCPRange m_lastYRange;
92 92 /// Delegate used to attach rendering features to the plot
93 93 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
94 94
95 95 QCPItemRect* m_DrawingZoomRect = nullptr;
96 96 QStack<QPair<QCPRange, QCPRange>> m_ZoomStack;
97 97
98 98 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
99 99 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
100 100
101 101 VisualizationSelectionZoneItem* m_DrawingZone = nullptr;
102 102 VisualizationSelectionZoneItem* m_HoveredZone = nullptr;
103 103 QVector<VisualizationSelectionZoneItem*> m_SelectionZones;
104 104
105 105 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
106 106
107 107 bool m_VariableAutoRangeOnInit = true;
108 108
109 109 inline void enterPlotDrag(const QPoint& position)
110 110 {
111 111 m_lastMousePos = m_plot->mapFromParent(position);
112 112 m_lastXRange = m_plot->xAxis->range();
113 113 m_lastYRange = m_plot->yAxis->range();
114 114 }
115 115
116 116 inline bool isDrawingZoomRect() { return m_DrawingZoomRect != nullptr; }
117 117 void updateZoomRect(const QPoint& newPos)
118 118 {
119 119 QPointF pos { m_plot->xAxis->pixelToCoord(newPos.x()),
120 120 m_plot->yAxis->pixelToCoord(newPos.y()) };
121 121 m_DrawingZoomRect->bottomRight->setCoords(pos);
122 122 m_plot->replot(QCustomPlot::rpQueuedReplot);
123 123 }
124 124
125 125 void applyZoomRect()
126 126 {
127 127 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
128 128 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
129 129
130 130 auto newAxisXRange = QCPRange { m_DrawingZoomRect->topLeft->coords().x(),
131 131 m_DrawingZoomRect->bottomRight->coords().x() };
132 132
133 133 auto newAxisYRange = QCPRange { m_DrawingZoomRect->topLeft->coords().y(),
134 134 m_DrawingZoomRect->bottomRight->coords().y() };
135 135
136 136 removeDrawingRect();
137 137
138 138 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
139 139 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0))
140 140 {
141 141 m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
142 142 axisX->setRange(newAxisXRange);
143 143 axisY->setRange(newAxisYRange);
144 144
145 145 m_plot->replot(QCustomPlot::rpQueuedReplot);
146 146 }
147 147 }
148 148
149 149 inline bool isDrawingZoneRect() { return m_DrawingZone != nullptr; }
150 150 void updateZoneRect(const QPoint& newPos)
151 151 {
152 152 m_DrawingZone->setEnd(m_plot->xAxis->pixelToCoord(newPos.x()));
153 153 m_plot->replot(QCustomPlot::rpQueuedReplot);
154 154 }
155 155
156 156 void startDrawingRect(const QPoint& pos)
157 157 {
158 158 removeDrawingRect();
159 159
160 160 auto axisPos = posToAxisPos(pos);
161 161
162 162 m_DrawingZoomRect = new QCPItemRect { m_plot };
163 163 QPen p;
164 164 p.setWidth(2);
165 165 m_DrawingZoomRect->setPen(p);
166 166
167 167 m_DrawingZoomRect->topLeft->setCoords(axisPos);
168 168 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
169 169 }
170 170
171 171 void removeDrawingRect()
172 172 {
173 173 if (m_DrawingZoomRect)
174 174 {
175 175 m_plot->removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
176 176 m_DrawingZoomRect = nullptr;
177 177 m_plot->replot(QCustomPlot::rpQueuedReplot);
178 178 }
179 179 }
180 180
181 181 void selectZone(const QPoint& pos)
182 182 {
183 183 auto zoneAtPos = selectionZoneAt(pos);
184 184 setSelectionZonesEditionEnabled(
185 185 sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones);
186 186 }
187 187
188 188 void startDrawingZone(const QPoint& pos)
189 189 {
190 190 endDrawingZone();
191 191
192 192 auto axisPos = posToAxisPos(pos);
193 193
194 194 m_DrawingZone = new VisualizationSelectionZoneItem { m_plot };
195 195 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
196 196 m_DrawingZone->setEditionEnabled(false);
197 197 }
198 198
199 199 void endDrawingZone()
200 200 {
201 201 if (m_DrawingZone)
202 202 {
203 203 auto drawingZoneRange = m_DrawingZone->range();
204 204 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0)
205 205 {
206 206 m_DrawingZone->setEditionEnabled(true);
207 207 addSelectionZone(m_DrawingZone);
208 208 }
209 209 else
210 210 {
211 211 m_plot->removeItem(m_DrawingZone);
212 212 }
213 213
214 214 m_plot->replot(QCustomPlot::rpQueuedReplot);
215 215 m_DrawingZone = nullptr;
216 216 }
217 217 }
218 218
219 219 void moveSelectionZone(const QPoint& destination)
220 220 {
221 221 /*
222 222 * I give up on this for now
223 223 * TODO implement this, the difficulty is that selection zones have their own
224 224 * event handling code which seems to rely on QCP GUI event handling propagation
225 225 * which was a realy bad design choice.
226 226 */
227 227 }
228 228
229 229 void setSelectionZonesEditionEnabled(bool value)
230 230 {
231 231 for (auto s : m_SelectionZones)
232 232 {
233 233 s->setEditionEnabled(value);
234 234 }
235 235 }
236 236
237 237 void addSelectionZone(VisualizationSelectionZoneItem* zone) { m_SelectionZones << zone; }
238 238
239 239 VisualizationSelectionZoneItem* selectionZoneAt(const QPoint& pos) const
240 240 {
241 241 VisualizationSelectionZoneItem* selectionZoneItemUnderCursor = nullptr;
242 242 auto minDistanceToZone = -1;
243 243 for (auto zone : m_SelectionZones)
244 244 {
245 245 auto distanceToZone = zone->selectTest(pos, false);
246 246 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
247 247 && distanceToZone >= 0 && distanceToZone < m_plot->selectionTolerance())
248 248 {
249 249 selectionZoneItemUnderCursor = zone;
250 250 }
251 251 }
252 252
253 253 return selectionZoneItemUnderCursor;
254 254 }
255 255
256 256 QVector<VisualizationSelectionZoneItem*> selectionZonesAt(
257 257 const QPoint& pos, const QCustomPlot& plot) const
258 258 {
259 259 QVector<VisualizationSelectionZoneItem*> zones;
260 260 for (auto zone : m_SelectionZones)
261 261 {
262 262 auto distanceToZone = zone->selectTest(pos, false);
263 263 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance())
264 264 {
265 265 zones << zone;
266 266 }
267 267 }
268 268
269 269 return zones;
270 270 }
271 271
272 272 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem* zone, QCustomPlot& plot)
273 273 {
274 274 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone)
275 275 {
276 276 zone->moveToTop();
277 277 m_SelectionZones.removeAll(zone);
278 278 m_SelectionZones.append(zone);
279 279 }
280 280 }
281 281
282 282 QPointF posToAxisPos(const QPoint& pos) const
283 283 {
284 284 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
285 285 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
286 286 return QPointF { axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y()) };
287 287 }
288 288
289 289 bool pointIsInAxisRect(const QPointF& axisPoint, QCustomPlot& plot) const
290 290 {
291 291 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
292 292 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
293 293 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
294 294 }
295 295
296 296 inline QCPRange _pixDistanceToRange(double pos1, double pos2, QCPAxis* axis)
297 297 {
298 298 if (axis->scaleType() == QCPAxis::stLinear)
299 299 {
300 300 auto diff = axis->pixelToCoord(pos1) - axis->pixelToCoord(pos2);
301 301 return QCPRange { axis->range().lower + diff, axis->range().upper + diff };
302 302 }
303 303 else
304 304 {
305 305 auto diff = axis->pixelToCoord(pos1) / axis->pixelToCoord(pos2);
306 306 return QCPRange { axis->range().lower * diff, axis->range().upper * diff };
307 307 }
308 308 }
309 309
310 310 void setRange(const DateTimeRange& newRange, bool updateVar = true)
311 311 {
312 312 this->m_plot->xAxis->setRange(newRange.m_TStart, newRange.m_TEnd);
313 313 if (updateVar)
314 314 {
315 315 for (auto it = m_VariableToPlotMultiMap.begin(), end = m_VariableToPlotMultiMap.end();
316 316 it != end; it = m_VariableToPlotMultiMap.upper_bound(it->first))
317 317 {
318 318 sqpApp->variableController().asyncChangeRange(it->first, newRange);
319 319 }
320 320 }
321 321 m_plot->replot(QCustomPlot::rpQueuedReplot);
322 322 }
323 323
324 324 void setRange(const QCPRange& newRange)
325 325 {
326 326 auto graphRange = DateTimeRange { newRange.lower, newRange.upper };
327 327 setRange(graphRange);
328 328 }
329 329
330 330 void rescaleY() { m_plot->yAxis->rescale(true); }
331 331
332 332 std::tuple<double, double> moveGraph(const QPoint& destination)
333 333 {
334 334 auto currentPos = m_plot->mapFromParent(destination);
335 335 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
336 336 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
337 337 auto oldXRange = xAxis->range();
338 338 auto oldYRange = yAxis->range();
339 339 double dx = xAxis->pixelToCoord(m_lastMousePos.x()) - xAxis->pixelToCoord(currentPos.x());
340 340 xAxis->setRange(m_lastXRange.lower + dx, m_lastXRange.upper + dx);
341 341 if (yAxis->scaleType() == QCPAxis::stLinear)
342 342 {
343 343 double dy
344 344 = yAxis->pixelToCoord(m_lastMousePos.y()) - yAxis->pixelToCoord(currentPos.y());
345 345 yAxis->setRange(m_lastYRange.lower + dy, m_lastYRange.upper + dy);
346 346 }
347 347 else
348 348 {
349 349 double dy
350 350 = yAxis->pixelToCoord(m_lastMousePos.y()) / yAxis->pixelToCoord(currentPos.y());
351 351 yAxis->setRange(m_lastYRange.lower * dy, m_lastYRange.upper * dy);
352 352 }
353 353 auto newXRange = xAxis->range();
354 354 auto newYRange = yAxis->range();
355 355 setRange(xAxis->range());
356 356 // m_lastMousePos = currentPos;
357 357 return { newXRange.lower - oldXRange.lower, newYRange.lower - oldYRange.lower };
358 358 }
359 359
360 360 void zoom(double factor, int center, Qt::Orientation orientation)
361 361 {
362 362 QCPAxis* axis = m_plot->axisRect()->rangeZoomAxis(orientation);
363 363 axis->scaleRange(factor, axis->pixelToCoord(center));
364 364 if (orientation == Qt::Horizontal)
365 365 setRange(axis->range());
366 366 m_plot->replot(QCustomPlot::rpQueuedReplot);
367 367 }
368 368
369 369 void transform(const DateTimeRangeTransformation& tranformation)
370 370 {
371 371 auto graphRange = m_plot->xAxis->range();
372 372 DateTimeRange range { graphRange.lower, graphRange.upper };
373 373 range = range.transform(tranformation);
374 374 setRange(range);
375 375 m_plot->replot(QCustomPlot::rpQueuedReplot);
376 376 }
377 377
378 378 void move(double dx, double dy)
379 379 {
380 380 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
381 381 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
382 382 xAxis->setRange(QCPRange(xAxis->range().lower + dx, xAxis->range().upper + dx));
383 383 yAxis->setRange(QCPRange(yAxis->range().lower + dy, yAxis->range().upper + dy));
384 384 setRange(xAxis->range());
385 385 m_plot->replot(QCustomPlot::rpQueuedReplot);
386 386 }
387 387
388 388 void move(double factor, Qt::Orientation orientation)
389 389 {
390 390 auto oldRange = m_plot->xAxis->range();
391 391 QCPAxis* axis = m_plot->axisRect()->rangeDragAxis(orientation);
392 392 if (m_plot->xAxis->scaleType() == QCPAxis::stLinear)
393 393 {
394 394 double rg = (axis->range().upper - axis->range().lower) * (factor / 10);
395 395 axis->setRange(axis->range().lower + (rg), axis->range().upper + (rg));
396 396 }
397 397 else if (m_plot->xAxis->scaleType() == QCPAxis::stLogarithmic)
398 398 {
399 399 int start = 0, stop = 0;
400 400 double diff = 0.;
401 401 if (factor > 0.0)
402 402 {
403 403 stop = m_plot->width() * factor / 10;
404 404 start = 2 * m_plot->width() * factor / 10;
405 405 }
406 406 if (factor < 0.0)
407 407 {
408 408 factor *= -1.0;
409 409 start = m_plot->width() * factor / 10;
410 410 stop = 2 * m_plot->width() * factor / 10;
411 411 }
412 412 diff = axis->pixelToCoord(start) / axis->pixelToCoord(stop);
413 413 axis->setRange(m_plot->axisRect()->rangeDragAxis(orientation)->range().lower * diff,
414 414 m_plot->axisRect()->rangeDragAxis(orientation)->range().upper * diff);
415 415 }
416 416 if (orientation == Qt::Horizontal)
417 417 setRange(axis->range());
418 418 m_plot->replot(QCustomPlot::rpQueuedReplot);
419 419 }
420 void toggle_y_log_scale()
421 {
422 if(m_plot->yAxis->scaleType()==QCPAxis::stLinear)
423 {
424 m_plot->yAxis->setScaleType(QCPAxis::stLogarithmic);
425 QSharedPointer<QCPAxisTickerLog> logTicker(new QCPAxisTickerLog);
426 m_plot->yAxis->setTicker(logTicker);
427 }
428 else
429 {
430 m_plot->yAxis->setScaleType(QCPAxis::stLinear);
431 QSharedPointer<QCPAxisTicker> ticker(new QCPAxisTicker);
432 m_plot->yAxis->setTicker(ticker);
433 }
434 }
420 435 };
421 436
422 437 VisualizationGraphWidget::VisualizationGraphWidget(const QString& name, QWidget* parent)
423 438 : VisualizationDragWidget { parent }
424 439 , ui { new Ui::VisualizationGraphWidget }
425 440 , impl { spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name) }
426 441 {
427 442 ui->setupUi(this);
428 443 this->layout()->addWidget(impl->m_plot);
429 444 // 'Close' options : widget is deleted when closed
430 445 setAttribute(Qt::WA_DeleteOnClose);
431 446
432 447 // The delegate must be initialized after the ui as it uses the plot
433 448 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
434 449
435 450 // Init the cursors
436 451 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
437 452 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
438 453 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
439 454 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
440 455
441 456 this->setFocusPolicy(Qt::WheelFocus);
442 457 this->setMouseTracking(true);
443 458 impl->m_plot->setAttribute(Qt::WA_TransparentForMouseEvents);
444 459 impl->m_plot->setContextMenuPolicy(Qt::CustomContextMenu);
445 460 impl->m_plot->setParent(this);
446 461
447 462 connect(&sqpApp->variableController(), &VariableController2::variableDeleted, this,
448 463 &VisualizationGraphWidget::variableDeleted);
449 464 }
450 465
451 466
452 467 VisualizationGraphWidget::~VisualizationGraphWidget()
453 468 {
454 469 delete ui;
455 470 }
456 471
457 472 VisualizationZoneWidget* VisualizationGraphWidget::parentZoneWidget() const noexcept
458 473 {
459 474 auto parent = parentWidget();
460 475 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget*>(parent))
461 476 {
462 477 parent = parent->parentWidget();
463 478 }
464 479
465 480 return qobject_cast<VisualizationZoneWidget*>(parent);
466 481 }
467 482
468 483 VisualizationWidget* VisualizationGraphWidget::parentVisualizationWidget() const
469 484 {
470 485 auto parent = parentWidget();
471 486 while (parent != nullptr && !qobject_cast<VisualizationWidget*>(parent))
472 487 {
473 488 parent = parent->parentWidget();
474 489 }
475 490
476 491 return qobject_cast<VisualizationWidget*>(parent);
477 492 }
478 493
479 494 void VisualizationGraphWidget::setFlags(GraphFlags flags)
480 495 {
481 496 impl->m_Flags = std::move(flags);
482 497 }
483 498
484 499 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable2> variable, DateTimeRange range)
485 500 {
486 501 // Uses delegate to create the qcpplot components according to the variable
487 502 auto createdPlottables = VisualizationGraphHelper::create(variable, *impl->m_plot);
488 503
489 504 // Sets graph properties
490 505 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
491 506
492 507 impl->m_VariableToPlotMultiMap.insert({ variable, std::move(createdPlottables) });
493 508
494 509 setGraphRange(range);
495 510 // If the variable already has its data loaded, load its units and its range in the graph
496 511 if (variable->data() != nullptr)
497 512 {
498 513 impl->m_RenderingDelegate->setAxesUnits(*variable);
499 514 }
500 515 else
501 516 {
502 517 auto context = new QObject { this };
503 518 connect(
504 519 variable.get(), &Variable2::updated, context, [this, variable, context, range](QUuid) {
505 520 this->impl->m_RenderingDelegate->setAxesUnits(*variable);
506 521 this->impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
507 522 delete context;
508 523 });
509 524 }
510 525 //TODO this is bad! when variable is moved to another graph it still fires
511 526 // even if this has been deleted
512 527 connect(variable.get(), &Variable2::updated, this, &VisualizationGraphWidget::variableUpdated);
513 528 this->onUpdateVarDisplaying(variable, range); // My bullshit
514 529 emit variableAdded(variable);
515 530 }
516 531
517 532 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable2> variable) noexcept
518 533 {
519 534 // Each component associated to the variable :
520 535 // - is removed from qcpplot (which deletes it)
521 536 // - is no longer referenced in the map
522 537 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
523 538 if (variableIt != impl->m_VariableToPlotMultiMap.cend())
524 539 {
525 540 emit variableAboutToBeRemoved(variable);
526 541
527 542 auto& plottablesMap = variableIt->second;
528 543
529 544 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
530 545 plottableIt != plottableEnd;)
531 546 {
532 547 impl->m_plot->removePlottable(plottableIt->second);
533 548 plottableIt = plottablesMap.erase(plottableIt);
534 549 }
535 550
536 551 impl->m_VariableToPlotMultiMap.erase(variableIt);
537 552 }
538 553
539 554 // Updates graph
540 555 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
541 556 }
542 557
543 558 std::vector<std::shared_ptr<Variable2>> VisualizationGraphWidget::variables() const
544 559 {
545 560 auto variables = std::vector<std::shared_ptr<Variable2>> {};
546 561 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
547 562 it != std::cend(impl->m_VariableToPlotMultiMap); ++it)
548 563 {
549 564 variables.push_back(it->first);
550 565 }
551 566
552 567 return variables;
553 568 }
554 569
555 570 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable2> variable)
556 571 {
557 572 if (!variable)
558 573 {
559 574 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
560 575 return;
561 576 }
562 577
563 578 VisualizationGraphHelper::setYAxisRange(variable, *impl->m_plot);
564 579 }
565 580
566 581 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
567 582 {
568 583 auto graphRange = impl->m_plot->xAxis->range();
569 584 return DateTimeRange { graphRange.lower, graphRange.upper };
570 585 }
571 586
572 587 void VisualizationGraphWidget::setGraphRange(
573 588 const DateTimeRange& range, bool updateVar, bool forward)
574 589 {
575 590 impl->setRange(range, updateVar);
576 591 if (forward)
577 592 {
578 593 emit this->setrange_sig(this->graphRange(), true, false);
579 594 }
580 595 }
581 596
582 597 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
583 598 {
584 599 impl->m_VariableAutoRangeOnInit = value;
585 600 }
586 601
587 602 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
588 603 {
589 604 QVector<DateTimeRange> ranges;
590 605 for (auto zone : impl->m_SelectionZones)
591 606 {
592 607 ranges << zone->range();
593 608 }
594 609
595 610 return ranges;
596 611 }
597 612
598 613 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange>& ranges)
599 614 {
600 615 for (const auto& range : ranges)
601 616 {
602 617 // note: ownership is transfered to QCustomPlot
603 618 auto zone = new VisualizationSelectionZoneItem(&plot());
604 619 zone->setRange(range.m_TStart, range.m_TEnd);
605 620 impl->addSelectionZone(zone);
606 621 }
607 622
608 623 plot().replot(QCustomPlot::rpQueuedReplot);
609 624 }
610 625
611 626 VisualizationSelectionZoneItem* VisualizationGraphWidget::addSelectionZone(
612 627 const QString& name, const DateTimeRange& range)
613 628 {
614 629 // note: ownership is transfered to QCustomPlot
615 630 auto zone = new VisualizationSelectionZoneItem(&plot());
616 631 zone->setName(name);
617 632 zone->setRange(range.m_TStart, range.m_TEnd);
618 633 impl->addSelectionZone(zone);
619 634
620 635 plot().replot(QCustomPlot::rpQueuedReplot);
621 636
622 637 return zone;
623 638 }
624 639
625 640 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem* selectionZone)
626 641 {
627 642 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
628 643
629 644 if (impl->m_HoveredZone == selectionZone)
630 645 {
631 646 impl->m_HoveredZone = nullptr;
632 647 setCursor(Qt::ArrowCursor);
633 648 }
634 649
635 650 impl->m_SelectionZones.removeAll(selectionZone);
636 651 plot().removeItem(selectionZone);
637 652 plot().replot(QCustomPlot::rpQueuedReplot);
638 653 }
639 654
640 655 void VisualizationGraphWidget::undoZoom()
641 656 {
642 657 auto zoom = impl->m_ZoomStack.pop();
643 658 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
644 659 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
645 660
646 661 axisX->setRange(zoom.first);
647 662 axisY->setRange(zoom.second);
648 663
649 664 plot().replot(QCustomPlot::rpQueuedReplot);
650 665 }
651 666
652 667 void VisualizationGraphWidget::zoom(
653 668 double factor, int center, Qt::Orientation orientation, bool forward)
654 669 {
655 670 impl->zoom(factor, center, orientation);
656 671 if (forward && orientation == Qt::Horizontal)
657 672 emit this->setrange_sig(this->graphRange(), true, false);
658 673 }
659 674
660 675 void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation, bool forward)
661 676 {
662 677 impl->move(factor, orientation);
663 678 if (forward)
664 679 emit this->setrange_sig(this->graphRange(), true, false);
665 680 }
666 681
667 682 void VisualizationGraphWidget::move(double dx, double dy, bool forward)
668 683 {
669 684 impl->move(dx, dy);
670 685 if (forward)
671 686 emit this->setrange_sig(this->graphRange(), true, false);
672 687 }
673 688
674 689 void VisualizationGraphWidget::transform(
675 690 const DateTimeRangeTransformation& tranformation, bool forward)
676 691 {
677 692 impl->transform(tranformation);
678 693 if (forward)
679 694 emit this->setrange_sig(this->graphRange(), true, false);
680 695 }
681 696
697 void VisualizationGraphWidget::toggle_y_log_scale()
698 {
699 impl->toggle_y_log_scale();
700 }
701
682 702 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor* visitor)
683 703 {
684 704 if (visitor)
685 705 {
686 706 visitor->visit(this);
687 707 }
688 708 else
689 709 {
690 710 qCCritical(LOG_VisualizationGraphWidget())
691 711 << tr("Can't visit widget : the visitor is null");
692 712 }
693 713 }
694 714
695 715 bool VisualizationGraphWidget::canDrop(Variable2& variable) const
696 716 {
697 717 auto isSpectrogram
698 718 = [](auto& variable) { return variable.type() == DataSeriesType::SPECTROGRAM; };
699 719
700 720 // - A spectrogram series can't be dropped on graph with existing plottables
701 721 // - No data series can be dropped on graph with existing spectrogram series
702 722 return isSpectrogram(variable)
703 723 ? impl->m_VariableToPlotMultiMap.empty()
704 724 : std::none_of(impl->m_VariableToPlotMultiMap.cbegin(),
705 725 impl->m_VariableToPlotMultiMap.cend(),
706 726 [isSpectrogram](const auto& entry) { return isSpectrogram(*entry.first); });
707 727 }
708 728
709 729 bool VisualizationGraphWidget::contains(Variable2& variable) const
710 730 {
711 731 // Finds the variable among the keys of the map
712 732 auto variablePtr = &variable;
713 733 auto findVariable
714 734 = [variablePtr](const auto& entry) { return variablePtr == entry.first.get(); };
715 735
716 736 auto end = impl->m_VariableToPlotMultiMap.cend();
717 737 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
718 738 return it != end;
719 739 }
720 740
721 741 QString VisualizationGraphWidget::name() const
722 742 {
723 743 return impl->m_Name;
724 744 }
725 745
726 746 QMimeData* VisualizationGraphWidget::mimeData(const QPoint& position) const
727 747 {
728 748 auto mimeData = new QMimeData;
729 749
730 750 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position);
731 751 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
732 752 && selectionZoneItemUnderCursor)
733 753 {
734 754 mimeData->setData(MIME::MIME_TYPE_TIME_RANGE,
735 755 TimeController::mimeDataForTimeRange(selectionZoneItemUnderCursor->range()));
736 756 mimeData->setData(MIME::MIME_TYPE_SELECTION_ZONE,
737 757 TimeController::mimeDataForTimeRange(selectionZoneItemUnderCursor->range()));
738 758 }
739 759 else
740 760 {
741 761 mimeData->setData(MIME::MIME_TYPE_GRAPH, QByteArray {});
742 762
743 763 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
744 764 mimeData->setData(MIME::MIME_TYPE_TIME_RANGE, timeRangeData);
745 765 }
746 766
747 767 return mimeData;
748 768 }
749 769
750 770 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint& dragPosition)
751 771 {
752 772 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition);
753 773 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
754 774 && selectionZoneItemUnderCursor)
755 775 {
756 776
757 777 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
758 778 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
759 779
760 780 auto zoneSize = QSizeF { qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
761 781 qAbs(zoneBottomRight.y() - zoneTopLeft.y()) }
762 782 .toSize();
763 783
764 784 auto pixmap = QPixmap(zoneSize);
765 785 render(&pixmap, QPoint(), QRegion { QRect { zoneTopLeft.toPoint(), zoneSize } });
766 786
767 787 return pixmap;
768 788 }
769 789
770 790 return QPixmap();
771 791 }
772 792
773 793 bool VisualizationGraphWidget::isDragAllowed() const
774 794 {
775 795 return true;
776 796 }
777 797
778 798 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
779 799 {
780 800 if (highlighted)
781 801 {
782 802 plot().setBackground(QBrush(QColor("#BBD5EE")));
783 803 }
784 804 else
785 805 {
786 806 plot().setBackground(QBrush(Qt::white));
787 807 }
788 808
789 809 plot().update();
790 810 }
791 811
792 812 void VisualizationGraphWidget::addVerticalCursor(double time)
793 813 {
794 814 impl->m_VerticalCursor->setPosition(time);
795 815 impl->m_VerticalCursor->setVisible(true);
796 816
797 817 auto text
798 818 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
799 819 impl->m_VerticalCursor->setLabelText(text);
800 820 }
801 821
802 822 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
803 823 {
804 824 impl->m_VerticalCursor->setAbsolutePosition(position);
805 825 impl->m_VerticalCursor->setVisible(true);
806 826
807 827 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
808 828 auto text
809 829 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
810 830 impl->m_VerticalCursor->setLabelText(text);
811 831 }
812 832
813 833 void VisualizationGraphWidget::removeVerticalCursor()
814 834 {
815 835 impl->m_VerticalCursor->setVisible(false);
816 836 plot().replot(QCustomPlot::rpQueuedReplot);
817 837 }
818 838
819 839 void VisualizationGraphWidget::addHorizontalCursor(double value)
820 840 {
821 841 impl->m_HorizontalCursor->setPosition(value);
822 842 impl->m_HorizontalCursor->setVisible(true);
823 843 impl->m_HorizontalCursor->setLabelText(QString::number(value));
824 844 }
825 845
826 846 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
827 847 {
828 848 impl->m_HorizontalCursor->setAbsolutePosition(position);
829 849 impl->m_HorizontalCursor->setVisible(true);
830 850
831 851 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
832 852 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
833 853 }
834 854
835 855 void VisualizationGraphWidget::removeHorizontalCursor()
836 856 {
837 857 impl->m_HorizontalCursor->setVisible(false);
838 858 plot().replot(QCustomPlot::rpQueuedReplot);
839 859 }
840 860
841 861 void VisualizationGraphWidget::closeEvent(QCloseEvent* event)
842 862 {
843 863 Q_UNUSED(event);
844 864
845 865 for (auto i : impl->m_SelectionZones)
846 866 {
847 867 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
848 868 }
849 869
850 870 // Prevents that all variables will be removed from graph when it will be closed
851 871 for (auto& variableEntry : impl->m_VariableToPlotMultiMap)
852 872 {
853 873 emit variableAboutToBeRemoved(variableEntry.first);
854 874 }
855 875 }
856 876
857 877 void VisualizationGraphWidget::enterEvent(QEvent* event)
858 878 {
859 879 Q_UNUSED(event);
860 880 impl->m_RenderingDelegate->showGraphOverlay(true);
861 881 }
862 882
863 883 void VisualizationGraphWidget::leaveEvent(QEvent* event)
864 884 {
865 885 Q_UNUSED(event);
866 886 impl->m_RenderingDelegate->showGraphOverlay(false);
867 887
868 888 if (auto parentZone = parentZoneWidget())
869 889 {
870 890 parentZone->notifyMouseLeaveGraph(this);
871 891 }
872 892 else
873 893 {
874 894 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
875 895 }
876 896
877 897 if (impl->m_HoveredZone)
878 898 {
879 899 impl->m_HoveredZone->setHovered(false);
880 900 impl->m_HoveredZone = nullptr;
881 901 }
882 902 }
883 903
884 904 void VisualizationGraphWidget::wheelEvent(QWheelEvent* event)
885 905 {
886 906 double factor;
887 907 double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually
888 908 if (event->modifiers() == Qt::ControlModifier)
889 909 {
890 910 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
891 911 {
892 912 setCursor(Qt::SizeVerCursor);
893 913 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps);
894 914 zoom(factor, event->pos().y(), Qt::Vertical);
895 915 }
896 916 }
897 917 else if (event->modifiers() == Qt::ShiftModifier)
898 918 {
899 919 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
900 920 {
901 921 setCursor(Qt::SizeHorCursor);
902 922 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps);
903 923 zoom(factor, event->pos().x(), Qt::Horizontal);
904 924 }
905 925 }
906 926 else
907 927 {
908 928 move(wheelSteps, Qt::Horizontal);
909 929 }
910 930 event->accept();
911 931 }
912 932
913 933
914 934 void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent* event)
915 935 {
916 936 if (impl->isDrawingZoomRect())
917 937 {
918 938 impl->updateZoomRect(event->pos());
919 939 }
920 940 else if (impl->isDrawingZoneRect())
921 941 {
922 942 impl->updateZoneRect(event->pos());
923 943 }
924 944 else if (event->buttons() == Qt::LeftButton)
925 945 {
926 946 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::None)
927 947 {
928 948 auto [dx, dy] = impl->moveGraph(event->pos());
929 949 emit this->setrange_sig(this->graphRange(), true, false);
930 950 }
931 951 else if (sqpApp->plotsInteractionMode()
932 952 == SqpApplication::PlotsInteractionMode::SelectionZones)
933 953 {
934 954 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
935 955 if (auto item = impl->m_plot->itemAt(posInPlot))
936 956 {
937 957 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
938 958 {
939 959 QMouseEvent e { QEvent::MouseMove, posInPlot, event->button(), event->buttons(),
940 960 event->modifiers() };
941 961 sqpApp->sendEvent(this->impl->m_plot, &e);
942 962 this->impl->m_plot->replot(QCustomPlot::rpImmediateRefresh);
943 963 }
944 964 }
945 965 }
946 966 }
947 967 else
948 968 {
949 969 impl->m_RenderingDelegate->updateTooltip(event);
950 970 }
951 971 // event->accept();
952 972 VisualizationDragWidget::mouseMoveEvent(event);
953 973 }
954 974
955 975 void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent* event)
956 976 {
957 977 if (impl->isDrawingZoomRect())
958 978 {
959 979 auto oldRange = this->graphRange();
960 980 impl->applyZoomRect();
961 981 auto newRange = this->graphRange();
962 982 if (auto tf = DateTimeRangeHelper::computeTransformation(oldRange, newRange))
963 983 emit this->transform_sig(tf.value(), false);
964 984 }
965 985 else if (impl->isDrawingZoneRect())
966 986 {
967 987 impl->endDrawingZone();
968 988 }
969 989 else
970 990 {
971 991 setCursor(Qt::ArrowCursor);
972 992 }
973 993 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
974 994 if (auto item = impl->m_plot->itemAt(posInPlot))
975 995 {
976 996 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
977 997 {
978 998 QMouseEvent e { QEvent::MouseButtonRelease, posInPlot, event->button(),
979 999 event->buttons(), event->modifiers() };
980 1000 sqpApp->sendEvent(this->impl->m_plot, &e);
981 1001 }
982 1002 }
983 1003 event->accept();
984 1004 }
985 1005
986 1006 void VisualizationGraphWidget::mousePressEvent(QMouseEvent* event)
987 1007 {
988 1008 if (event->button() == Qt::RightButton)
989 1009 {
990 1010 onGraphMenuRequested(event->pos());
991 1011 }
992 1012 else
993 1013 {
994 1014 auto selectedZone = impl->selectionZoneAt(event->pos());
995 1015 switch (sqpApp->plotsInteractionMode())
996 1016 {
997 1017 case SqpApplication::PlotsInteractionMode::DragAndDrop:
998 1018 break;
999 1019 case SqpApplication::PlotsInteractionMode::SelectionZones:
1000 1020 impl->setSelectionZonesEditionEnabled(true);
1001 1021 if ((event->modifiers() == Qt::ControlModifier) && (selectedZone != nullptr))
1002 1022 {
1003 1023 auto alreadySelectedZones
1004 1024 = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1005 1025 selectedZone->setAssociatedEditedZones(alreadySelectedZones);
1006 1026 if (cpp_utils::containers::contains(alreadySelectedZones, selectedZone))
1007 1027 {
1008 1028 alreadySelectedZones.removeOne(selectedZone);
1009 1029 }
1010 1030 else
1011 1031 {
1012 1032 alreadySelectedZones.append(selectedZone);
1013 1033 }
1014 1034 parentVisualizationWidget()->selectionZoneManager().select(
1015 1035 alreadySelectedZones);
1016 1036 }
1017 1037 else
1018 1038 {
1019 1039 if (!selectedZone)
1020 1040 {
1021 1041 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1022 1042 impl->startDrawingZone(event->pos());
1023 1043 }
1024 1044 else
1025 1045 {
1026 1046 parentVisualizationWidget()->selectionZoneManager().select(
1027 1047 { selectedZone });
1028 1048 }
1029 1049 }
1030 1050 {
1031 1051 auto posInPlot = this->impl->m_plot->mapFromParent(event->pos());
1032 1052 if (auto item = impl->m_plot->itemAt(posInPlot))
1033 1053 {
1034 1054 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
1035 1055 {
1036 1056 QMouseEvent e { QEvent::MouseButtonPress, posInPlot, event->button(),
1037 1057 event->buttons(), event->modifiers() };
1038 1058 sqpApp->sendEvent(this->impl->m_plot, &e);
1039 1059 }
1040 1060 }
1041 1061 }
1042 1062 break;
1043 1063 case SqpApplication::PlotsInteractionMode::ZoomBox:
1044 1064 impl->startDrawingRect(event->pos());
1045 1065 break;
1046 1066 default:
1047 1067 if (auto item = impl->m_plot->itemAt(event->pos()))
1048 1068 {
1049 1069 emit impl->m_plot->itemClick(item, event);
1050 1070 if (qobject_cast<VisualizationSelectionZoneItem*>(item))
1051 1071 {
1052 1072 setCursor(Qt::ClosedHandCursor);
1053 1073 impl->enterPlotDrag(event->pos());
1054 1074 }
1055 1075 }
1056 1076 else
1057 1077 {
1058 1078 setCursor(Qt::ClosedHandCursor);
1059 1079 impl->enterPlotDrag(event->pos());
1060 1080 }
1061 1081 }
1062 1082 }
1063 1083 // event->accept();
1064 1084 VisualizationDragWidget::mousePressEvent(event);
1065 1085 }
1066 1086
1067 1087 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent* event)
1068 1088 {
1069 1089 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1070 1090 }
1071 1091
1072 1092 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent* event)
1073 1093 {
1074 1094 switch (event->key())
1075 1095 {
1076 1096 case Qt::Key_Control:
1077 1097 event->accept();
1078 1098 break;
1079 1099 case Qt::Key_Shift:
1080 1100 event->accept();
1081 1101 break;
1082 1102 default:
1083 1103 QWidget::keyReleaseEvent(event);
1084 1104 break;
1085 1105 }
1086 1106 setCursor(Qt::ArrowCursor);
1087 1107 // event->accept();
1088 1108 }
1089 1109
1090 1110 void VisualizationGraphWidget::keyPressEvent(QKeyEvent* event)
1091 1111 {
1092 1112 switch (event->key())
1093 1113 {
1094 1114 case Qt::Key_Control:
1095 1115 setCursor(Qt::CrossCursor);
1096 1116 break;
1097 1117 case Qt::Key_Shift:
1098 1118 break;
1099 1119 case Qt::Key_M:
1100 1120 impl->rescaleY();
1101 1121 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
1102 1122 break;
1103 1123 case Qt::Key_Left:
1104 1124 if (event->modifiers() != Qt::ControlModifier)
1105 1125 {
1106 1126 move(-0.1, Qt::Horizontal);
1107 1127 }
1108 1128 else
1109 1129 {
1110 1130 zoom(2, this->width() / 2, Qt::Horizontal);
1111 1131 }
1112 1132 break;
1113 1133 case Qt::Key_Right:
1114 1134 if (event->modifiers() != Qt::ControlModifier)
1115 1135 {
1116 1136 move(0.1, Qt::Horizontal);
1117 1137 }
1118 1138 else
1119 1139 {
1120 1140 zoom(0.5, this->width() / 2, Qt::Horizontal);
1121 1141 }
1122 1142 break;
1123 1143 case Qt::Key_Up:
1124 1144 if (event->modifiers() != Qt::ControlModifier)
1125 1145 {
1126 1146 move(0.1, Qt::Vertical);
1127 1147 }
1128 1148 else
1129 1149 {
1130 1150 zoom(0.5, this->height() / 2, Qt::Vertical);
1131 1151 }
1132 1152 break;
1133 1153 case Qt::Key_Down:
1134 1154 if (event->modifiers() != Qt::ControlModifier)
1135 1155 {
1136 1156 move(-0.1, Qt::Vertical);
1137 1157 }
1138 1158 else
1139 1159 {
1140 1160 zoom(2, this->height() / 2, Qt::Vertical);
1141 1161 }
1142 1162 break;
1163 case Qt::Key_L:
1164 toggle_y_log_scale();
1165 break;
1143 1166 default:
1144 1167 QWidget::keyPressEvent(event);
1145 1168 break;
1146 1169 }
1147 1170 }
1148 1171
1149 1172 QCustomPlot& VisualizationGraphWidget::plot() const noexcept
1150 1173 {
1151 1174 return *impl->m_plot;
1152 1175 }
1153 1176
1154 1177 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint& pos) noexcept
1155 1178 {
1156 1179 QMenu graphMenu {};
1157 1180
1158 1181 // Iterates on variables (unique keys)
1159 1182 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
1160 1183 end = impl->m_VariableToPlotMultiMap.cend();
1161 1184 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first))
1162 1185 {
1163 1186 // 'Remove variable' action
1164 1187 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
1165 1188 [this, var = it->first]() { removeVariable(var); });
1166 1189 }
1167 1190
1168 1191 if (!impl->m_ZoomStack.isEmpty())
1169 1192 {
1170 1193 if (!graphMenu.isEmpty())
1171 1194 {
1172 1195 graphMenu.addSeparator();
1173 1196 }
1174 1197
1175 1198 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
1176 1199 }
1177 1200
1178 1201 // Selection Zone Actions
1179 1202 auto selectionZoneItem = impl->selectionZoneAt(pos);
1180 1203 if (selectionZoneItem)
1181 1204 {
1182 1205 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1183 1206 selectedItems.removeAll(selectionZoneItem);
1184 1207 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
1185 1208
1186 1209 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
1187 1210 if (!zoneActions.isEmpty() && !graphMenu.isEmpty())
1188 1211 {
1189 1212 graphMenu.addSeparator();
1190 1213 }
1191 1214
1192 1215 QHash<QString, QMenu*> subMenus;
1193 1216 QHash<QString, bool> subMenusEnabled;
1194 1217 QHash<QString, FilteringAction*> filteredMenu;
1195 1218
1196 1219 for (auto zoneAction : zoneActions)
1197 1220 {
1198 1221
1199 1222 auto isEnabled = zoneAction->isEnabled(selectedItems);
1200 1223
1201 1224 auto menu = &graphMenu;
1202 1225 QString menuPath;
1203 1226 for (auto subMenuName : zoneAction->subMenuList())
1204 1227 {
1205 1228 menuPath += '/';
1206 1229 menuPath += subMenuName;
1207 1230
1208 1231 if (!subMenus.contains(menuPath))
1209 1232 {
1210 1233 menu = menu->addMenu(subMenuName);
1211 1234 subMenus[menuPath] = menu;
1212 1235 subMenusEnabled[menuPath] = isEnabled;
1213 1236 }
1214 1237 else
1215 1238 {
1216 1239 menu = subMenus.value(menuPath);
1217 1240 if (isEnabled)
1218 1241 {
1219 1242 // The sub menu is enabled if at least one of its actions is enabled
1220 1243 subMenusEnabled[menuPath] = true;
1221 1244 }
1222 1245 }
1223 1246 }
1224 1247
1225 1248 FilteringAction* filterAction = nullptr;
1226 1249 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList()))
1227 1250 {
1228 1251 filterAction = filteredMenu.value(menuPath);
1229 1252 if (!filterAction)
1230 1253 {
1231 1254 filterAction = new FilteringAction { this };
1232 1255 filteredMenu[menuPath] = filterAction;
1233 1256 menu->addAction(filterAction);
1234 1257 }
1235 1258 }
1236 1259
1237 1260 auto action = menu->addAction(zoneAction->name());
1238 1261 action->setEnabled(isEnabled);
1239 1262 action->setShortcut(zoneAction->displayedShortcut());
1240 1263 QObject::connect(action, &QAction::triggered,
1241 1264 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1242 1265
1243 1266 if (filterAction && zoneAction->isFilteringAllowed())
1244 1267 {
1245 1268 filterAction->addActionToFilter(action);
1246 1269 }
1247 1270 }
1248 1271
1249 1272 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it)
1250 1273 {
1251 1274 it.value()->setEnabled(subMenusEnabled[it.key()]);
1252 1275 }
1253 1276 }
1254 1277
1255 1278 if (!graphMenu.isEmpty())
1256 1279 {
1257 1280 graphMenu.exec(QCursor::pos());
1258 1281 }
1259 1282 }
1260 1283
1261 1284 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent* event) noexcept
1262 1285 {
1263 1286 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1264 1287 }
1265 1288
1266 1289 void VisualizationGraphWidget::onMouseMove(QMouseEvent* event) noexcept
1267 1290 {
1268 1291 // Handles plot rendering when mouse is moving
1269 1292 impl->m_RenderingDelegate->updateTooltip(event);
1270 1293
1271 1294 auto axisPos = impl->posToAxisPos(event->pos());
1272 1295
1273 1296 // Zoom box and zone drawing
1274 1297 if (impl->m_DrawingZoomRect)
1275 1298 {
1276 1299 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1277 1300 }
1278 1301 else if (impl->m_DrawingZone)
1279 1302 {
1280 1303 impl->m_DrawingZone->setEnd(axisPos.x());
1281 1304 }
1282 1305
1283 1306 // Cursor
1284 1307 if (auto parentZone = parentZoneWidget())
1285 1308 {
1286 1309 if (impl->pointIsInAxisRect(axisPos, plot()))
1287 1310 {
1288 1311 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1289 1312 }
1290 1313 else
1291 1314 {
1292 1315 parentZone->notifyMouseLeaveGraph(this);
1293 1316 }
1294 1317 }
1295 1318
1296 1319 // Search for the selection zone under the mouse
1297 1320 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1298 1321 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1299 1322 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones)
1300 1323 {
1301 1324
1302 1325 // Sets the appropriate cursor shape
1303 1326 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1304 1327 setCursor(cursorShape);
1305 1328
1306 1329 // Manages the hovered zone
1307 1330 if (selectionZoneItemUnderCursor != impl->m_HoveredZone)
1308 1331 {
1309 1332 if (impl->m_HoveredZone)
1310 1333 {
1311 1334 impl->m_HoveredZone->setHovered(false);
1312 1335 }
1313 1336 selectionZoneItemUnderCursor->setHovered(true);
1314 1337 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1315 1338 plot().replot(QCustomPlot::rpQueuedReplot);
1316 1339 }
1317 1340 }
1318 1341 else
1319 1342 {
1320 1343 // There is no zone under the mouse or the interaction mode is not "selection zones"
1321 1344 if (impl->m_HoveredZone)
1322 1345 {
1323 1346 impl->m_HoveredZone->setHovered(false);
1324 1347 impl->m_HoveredZone = nullptr;
1325 1348 }
1326 1349
1327 1350 setCursor(Qt::ArrowCursor);
1328 1351 }
1329 1352
1330 1353 impl->m_HasMovedMouse = true;
1331 1354 VisualizationDragWidget::mouseMoveEvent(event);
1332 1355 }
1333 1356
1334 1357 void VisualizationGraphWidget::onMouseWheel(QWheelEvent* event) noexcept
1335 1358 {
1336 1359 // Processes event only if the wheel occurs on axis rect
1337 1360 if (!dynamic_cast<QCPAxisRect*>(impl->m_plot->layoutElementAt(event->posF())))
1338 1361 {
1339 1362 return;
1340 1363 }
1341 1364
1342 1365 auto value = event->angleDelta().x() + event->angleDelta().y();
1343 1366 if (value != 0)
1344 1367 {
1345 1368
1346 1369 auto direction = value > 0 ? 1.0 : -1.0;
1347 1370 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1348 1371 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1349 1372 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1350 1373
1351 1374 auto zoomOrientations = QFlags<Qt::Orientation> {};
1352 1375 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1353 1376 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1354 1377
1355 1378 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1356 1379
1357 1380 if (!isZoomX && !isZoomY)
1358 1381 {
1359 1382 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1360 1383 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1361 1384
1362 1385 axis->setRange(axis->range() + diff);
1363 1386
1364 1387 if (plot().noAntialiasingOnDrag())
1365 1388 {
1366 1389 plot().setNotAntialiasedElements(QCP::aeAll);
1367 1390 }
1368 1391
1369 1392 // plot().replot(QCustomPlot::rpQueuedReplot);
1370 1393 }
1371 1394 }
1372 1395 }
1373 1396
1374 1397 void VisualizationGraphWidget::onMousePress(QMouseEvent* event) noexcept
1375 1398 {
1376 1399 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1377 1400 auto isSelectionZoneMode
1378 1401 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1379 1402 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1380 1403
1381 1404 if (!isDragDropClick && isLeftClick)
1382 1405 {
1383 1406 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox)
1384 1407 {
1385 1408 // Starts a zoom box
1386 1409 impl->startDrawingRect(event->pos());
1387 1410 }
1388 1411 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr)
1389 1412 {
1390 1413 // Starts a new selection zone
1391 1414 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1392 1415 if (!zoneAtPos)
1393 1416 {
1394 1417 impl->startDrawingZone(event->pos());
1395 1418 }
1396 1419 }
1397 1420 }
1398 1421
1399 1422
1400 1423 // Allows zone edition only in selection zone mode without drag&drop
1401 1424 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1402 1425
1403 1426 // Selection / Deselection
1404 1427 if (isSelectionZoneMode)
1405 1428 {
1406 1429 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1407 1430 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1408 1431
1409 1432
1410 1433 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1411 1434 && !isMultiSelectionClick)
1412 1435 {
1413 1436 parentVisualizationWidget()->selectionZoneManager().select(
1414 1437 { selectionZoneItemUnderCursor });
1415 1438 }
1416 1439 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick)
1417 1440 {
1418 1441 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1419 1442 }
1420 1443 else
1421 1444 {
1422 1445 // No selection change
1423 1446 }
1424 1447
1425 1448 if (selectionZoneItemUnderCursor && isLeftClick)
1426 1449 {
1427 1450 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1428 1451 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1429 1452 }
1430 1453 }
1431 1454
1432 1455
1433 1456 impl->m_HasMovedMouse = false;
1434 1457 VisualizationDragWidget::mousePressEvent(event);
1435 1458 }
1436 1459
1437 1460 void VisualizationGraphWidget::onMouseRelease(QMouseEvent* event) noexcept
1438 1461 {
1439 1462 if (impl->m_DrawingZoomRect)
1440 1463 {
1441 1464
1442 1465 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1443 1466 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1444 1467
1445 1468 auto newAxisXRange = QCPRange { impl->m_DrawingZoomRect->topLeft->coords().x(),
1446 1469 impl->m_DrawingZoomRect->bottomRight->coords().x() };
1447 1470
1448 1471 auto newAxisYRange = QCPRange { impl->m_DrawingZoomRect->topLeft->coords().y(),
1449 1472 impl->m_DrawingZoomRect->bottomRight->coords().y() };
1450 1473
1451 1474 impl->removeDrawingRect();
1452 1475
1453 1476 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1454 1477 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0))
1455 1478 {
1456 1479 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1457 1480 axisX->setRange(newAxisXRange);
1458 1481 axisY->setRange(newAxisYRange);
1459 1482
1460 1483 plot().replot(QCustomPlot::rpQueuedReplot);
1461 1484 }
1462 1485 }
1463 1486
1464 1487 impl->endDrawingZone();
1465 1488
1466 1489 // Selection / Deselection
1467 1490 auto isSelectionZoneMode
1468 1491 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1469 1492 if (isSelectionZoneMode)
1470 1493 {
1471 1494 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1472 1495 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1473 1496 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1474 1497 && !impl->m_HasMovedMouse)
1475 1498 {
1476 1499
1477 1500 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1478 1501 if (zonesUnderCursor.count() > 1)
1479 1502 {
1480 1503 // There are multiple zones under the mouse.
1481 1504 // Performs the selection with a selection dialog.
1482 1505 VisualizationMultiZoneSelectionDialog dialog { this };
1483 1506 dialog.setZones(zonesUnderCursor);
1484 1507 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1485 1508 dialog.activateWindow();
1486 1509 dialog.raise();
1487 1510 if (dialog.exec() == QDialog::Accepted)
1488 1511 {
1489 1512 auto selection = dialog.selectedZones();
1490 1513
1491 1514 if (!isMultiSelectionClick)
1492 1515 {
1493 1516 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1494 1517 }
1495 1518
1496 1519 for (auto it = selection.cbegin(); it != selection.cend(); ++it)
1497 1520 {
1498 1521 auto zone = it.key();
1499 1522 auto isSelected = it.value();
1500 1523 parentVisualizationWidget()->selectionZoneManager().setSelected(
1501 1524 zone, isSelected);
1502 1525
1503 1526 if (isSelected)
1504 1527 {
1505 1528 // Puts the zone on top of the stack so it can be moved or resized
1506 1529 impl->moveSelectionZoneOnTop(zone, plot());
1507 1530 }
1508 1531 }
1509 1532 }
1510 1533 }
1511 1534 else
1512 1535 {
1513 1536 if (!isMultiSelectionClick)
1514 1537 {
1515 1538 parentVisualizationWidget()->selectionZoneManager().select(
1516 1539 { selectionZoneItemUnderCursor });
1517 1540 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1518 1541 }
1519 1542 else
1520 1543 {
1521 1544 parentVisualizationWidget()->selectionZoneManager().setSelected(
1522 1545 selectionZoneItemUnderCursor,
1523 1546 !selectionZoneItemUnderCursor->selected()
1524 1547 || event->button() == Qt::RightButton);
1525 1548 }
1526 1549 }
1527 1550 }
1528 1551 else
1529 1552 {
1530 1553 // No selection change
1531 1554 }
1532 1555 }
1533 1556 }
1534 1557
1535 1558 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1536 1559 {
1537 1560 auto graphRange = impl->m_plot->xAxis->range();
1538 1561 auto dateTime = DateTimeRange { graphRange.lower, graphRange.upper };
1539 1562
1540 1563 for (auto& variableEntry : impl->m_VariableToPlotMultiMap)
1541 1564 {
1542 1565 auto variable = variableEntry.first;
1543 1566 qCDebug(LOG_VisualizationGraphWidget())
1544 1567 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1545 1568 qCDebug(LOG_VisualizationGraphWidget())
1546 1569 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1547 1570 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range()))
1548 1571 {
1549 1572 impl->updateData(variableEntry.second, variable, variable->range());
1550 1573 }
1551 1574 }
1552 1575 }
1553 1576
1554 1577 void VisualizationGraphWidget::onUpdateVarDisplaying(
1555 1578 std::shared_ptr<Variable2> variable, const DateTimeRange& range)
1556 1579 {
1557 1580 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1558 1581 if (it != impl->m_VariableToPlotMultiMap.end())
1559 1582 {
1560 1583 impl->updateData(it->second, variable, range);
1561 1584 }
1562 1585 }
1563 1586
1564 1587 void VisualizationGraphWidget::variableUpdated(QUuid id)
1565 1588 {
1566 1589 for (auto& [var, plotables] : impl->m_VariableToPlotMultiMap)
1567 1590 {
1568 1591 if (var->ID() == id)
1569 1592 {
1570 1593 impl->updateData(plotables, var, this->graphRange());
1571 1594 }
1572 1595 }
1573 1596 }
1574 1597
1575 1598 void VisualizationGraphWidget::variableDeleted(const std::shared_ptr<Variable2>& variable)
1576 1599 {
1577 1600 this->removeVariable(variable);
1578 1601 }
General Comments 0
You need to be logged in to leave comments. Login now