##// END OF EJS Templates
Force range for each transformation among synchronized graphs, ignore missing X unit in data files since it's always time...
jeandet -
r1384:e344de146add
parent child
Show More
@@ -1,180 +1,181
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 <QWidget>
9 9 #include <QUuid>
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 Variable;
22 22 class VisualizationWidget;
23 23 class VisualizationZoneWidget;
24 24 class VisualizationSelectionZoneItem;
25 25
26 26 namespace Ui {
27 27 class VisualizationGraphWidget;
28 28 } // namespace Ui
29 29
30 30 /// Defines options that can be associated with the graph
31 31 enum GraphFlag {
32 32 DisableAll = 0x0, ///< Disables acquisition and synchronization
33 33 EnableAcquisition = 0x1, ///< When this flag is set, the change of the graph's range leads to
34 34 /// the acquisition of data
35 35 EnableSynchronization = 0x2, ///< When this flag is set, the change of the graph's range causes
36 36 /// the call to the synchronization of the graphs contained in the
37 37 /// same zone of this graph
38 38 EnableAll = ~DisableAll ///< Enables acquisition and synchronization
39 39 };
40 40
41 41 Q_DECLARE_FLAGS(GraphFlags, GraphFlag)
42 42 Q_DECLARE_OPERATORS_FOR_FLAGS(GraphFlags)
43 43
44 44 class VisualizationGraphWidget : public VisualizationDragWidget, public IVisualizationWidget {
45 45 Q_OBJECT
46 46
47 47 friend class QCustomPlotSynchronizer;
48 48 friend class VisualizationGraphRenderingDelegate;
49 49
50 50 public:
51 51 explicit VisualizationGraphWidget(const QString &name = {}, QWidget *parent = 0);
52 52 virtual ~VisualizationGraphWidget();
53 53
54 54 /// Returns the VisualizationZoneWidget which contains the graph or nullptr
55 55 VisualizationZoneWidget *parentZoneWidget() const noexcept;
56 56
57 57 /// Returns the main VisualizationWidget which contains the graph or nullptr
58 58 VisualizationWidget *parentVisualizationWidget() const;
59 59
60 60 /// Sets graph options
61 61 void setFlags(GraphFlags flags);
62 62
63 63 void addVariable(std::shared_ptr<Variable> variable, DateTimeRange range);
64 64
65 65 /// Removes a variable from the graph
66 66 void removeVariable(std::shared_ptr<Variable> variable) noexcept;
67 67
68 68 /// Returns the list of all variables used in the graph
69 69 std::vector<std::shared_ptr<Variable> > variables() const;
70 70
71 71 /// Sets the y-axis range based on the data of a variable
72 72 void setYRange(std::shared_ptr<Variable> variable);
73 73 DateTimeRange graphRange() const noexcept;
74 74 void setGraphRange(const DateTimeRange &range, bool updateVar=false, bool forward=false);
75 75 void setAutoRangeOnVariableInitialization(bool value);
76 76
77 77 // Zones
78 78 /// Returns the ranges of all the selection zones on the graph
79 79 QVector<DateTimeRange> selectionZoneRanges() const;
80 80 /// Adds new selection zones in the graph
81 81 void addSelectionZones(const QVector<DateTimeRange> &ranges);
82 82 /// Adds a new selection zone in the graph
83 83 VisualizationSelectionZoneItem *addSelectionZone(const QString &name, const DateTimeRange &range);
84 84 /// Removes the specified selection zone
85 85 void removeSelectionZone(VisualizationSelectionZoneItem *selectionZone);
86 86
87 87 /// Undo the last zoom done with a zoom box
88 88 void undoZoom();
89 89
90 90 void zoom(double factor, int center, Qt::Orientation orientation, bool forward=true);
91 91 void move(double factor, Qt::Orientation orientation, bool forward=true);
92 92 void move(double dx, double dy, bool forward=true);
93 93 void transform(const DateTimeRangeTransformation& tranformation, bool forward=true);
94 94
95 95 // IVisualizationWidget interface
96 96 void accept(IVisualizationWidgetVisitor *visitor) override;
97 97 bool canDrop(const Variable &variable) const override;
98 98 bool contains(const Variable &variable) const override;
99 99 QString name() const override;
100 100
101 101 // VisualisationDragWidget
102 102 QMimeData *mimeData(const QPoint &position) const override;
103 103 QPixmap customDragPixmap(const QPoint &dragPosition) override;
104 104 bool isDragAllowed() const override;
105 105 void highlightForMerge(bool highlighted) override;
106 106
107 107 // Cursors
108 108 /// Adds or moves the vertical cursor at the specified value on the x-axis
109 109 void addVerticalCursor(double time);
110 110 /// Adds or moves the vertical cursor at the specified value on the x-axis
111 111 void addVerticalCursorAtViewportPosition(double position);
112 112 void removeVerticalCursor();
113 113 /// Adds or moves the vertical cursor at the specified value on the y-axis
114 114 void addHorizontalCursor(double value);
115 115 /// Adds or moves the vertical cursor at the specified value on the y-axis
116 116 void addHorizontalCursorAtViewportPosition(double position);
117 117 void removeHorizontalCursor();
118 118
119 119 signals:
120 120 void synchronize(const DateTimeRange &range, const DateTimeRange &oldRange);
121 121 void changeRange(const std::shared_ptr<Variable>& variable, const DateTimeRange &range);
122 122
123 123 /// Signal emitted when the variable is about to be removed from the graph
124 124 void variableAboutToBeRemoved(std::shared_ptr<Variable> var);
125 125 /// Signal emitted when the variable has been added to the graph
126 126 void variableAdded(std::shared_ptr<Variable> var);
127 127
128 128
129 129 void zoom_sig(double factor, int center, Qt::Orientation orientation, bool forward=true);
130 130 void move_sig(double factor, Qt::Orientation orientation, bool forward=true);
131 131 void move_sig(double dx, double dy, bool forward=true);
132 132 void transform_sig(const DateTimeRangeTransformation& tranformation, bool forward=true);
133 void setrange_sig(const DateTimeRange& range, bool updateVar=false, bool forward=true);
133 134
134 135 protected:
135 136 void closeEvent(QCloseEvent *event) override;
136 137 void enterEvent(QEvent *event) override;
137 138 void leaveEvent(QEvent *event) override;
138 139 void wheelEvent(QWheelEvent * event) override;
139 140 void mouseMoveEvent(QMouseEvent *event) override;
140 141 void mouseReleaseEvent(QMouseEvent *event) override;
141 142 void mousePressEvent(QMouseEvent *event) override;
142 143 void mouseDoubleClickEvent(QMouseEvent *event) override;
143 144 void keyReleaseEvent(QKeyEvent * event) override;
144 145 void keyPressEvent(QKeyEvent * event) override;
145 146
146 147 QCustomPlot &plot() const noexcept;
147 148
148 149 private:
149 150 Ui::VisualizationGraphWidget *ui;
150 151
151 152 class VisualizationGraphWidgetPrivate;
152 153 spimpl::unique_impl_ptr<VisualizationGraphWidgetPrivate> impl;
153 154
154 155 private slots:
155 156 /// Slot called when right clicking on the graph (displays a menu)
156 157 void onGraphMenuRequested(const QPoint &pos) noexcept;
157 158
158 159 /// Rescale the X axe to range parameter
159 160 // void onRangeChanged(const QCPRange &newRange, const QCPRange &oldRange);
160 161
161 162 /// Slot called when a mouse double click was made
162 163 void onMouseDoubleClick(QMouseEvent *event) noexcept;
163 164 /// Slot called when a mouse move was made
164 165 void onMouseMove(QMouseEvent *event) noexcept;
165 166 /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done
166 167 void onMouseWheel(QWheelEvent *event) noexcept;
167 168 /// Slot called when a mouse press was made, to activate the calibration of a graph
168 169 void onMousePress(QMouseEvent *event) noexcept;
169 170 /// Slot called when a mouse release was made, to deactivate the calibration of a graph
170 171 void onMouseRelease(QMouseEvent *event) noexcept;
171 172
172 173 void onDataCacheVariableUpdated();
173 174
174 175 void onUpdateVarDisplaying(std::shared_ptr<Variable> variable, const DateTimeRange &range);
175 176
176 177 void variableUpdated(QUuid id);
177 178 void variableDeleted(const std::shared_ptr<Variable>&);
178 179 };
179 180
180 181 #endif // SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
@@ -1,1430 +1,1427
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 <Common/MimeTypesDef.h>
17 17 #include <Data/ArrayData.h>
18 18 #include <Data/IDataSeries.h>
19 19 #include <Data/SpectrogramSeries.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/Variable.h>
25 25 #include <Variable/VariableController2.h>
26 26 #include <Data/DateTimeRangeHelper.h>
27 27
28 28 #include <unordered_map>
29 29
30 30 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
31 31
32 32 namespace {
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 explicit VisualizationGraphWidgetPrivate(const QString &name)
63 63 : m_Name{name},
64 64 m_Flags{GraphFlag::EnableAll},
65 65 m_IsCalibration{false},
66 66 m_RenderingDelegate{nullptr}
67 67 {
68 68 m_plot = new QCustomPlot();
69 69 // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable.
70 70 m_plot->setPlottingHint(QCP::phFastPolylines, true);
71 71 }
72 72
73 73 void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
74 74 const DateTimeRange &range)
75 75 {
76 76 VisualizationGraphHelper::updateData(plottables, variable, range);
77 77
78 78 // Prevents that data has changed to update rendering
79 79 m_RenderingDelegate->onPlotUpdated();
80 80 }
81 81
82 82 QString m_Name;
83 83 // 1 variable -> n qcpplot
84 84 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
85 85 GraphFlags m_Flags;
86 86 bool m_IsCalibration;
87 87 QCustomPlot* m_plot;
88 88 QPoint m_lastMousePos;
89 89 QCPRange m_lastXRange;
90 90 QCPRange m_lastYRange;
91 91 /// Delegate used to attach rendering features to the plot
92 92 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
93 93
94 94 QCPItemRect* m_DrawingZoomRect = nullptr;
95 95 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
96 96
97 97 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
98 98 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
99 99
100 100 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
101 101 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
102 102 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
103 103
104 104 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
105 105
106 106 bool m_VariableAutoRangeOnInit = true;
107 107
108 108 inline void enterPlotDrag(const QPoint& position)
109 109 {
110 110 m_lastMousePos = m_plot->mapFromParent(position);
111 111 m_lastXRange = m_plot->xAxis->range();
112 112 m_lastYRange = m_plot->yAxis->range();
113 113
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()), m_plot->yAxis->pixelToCoord(newPos.y())};
120 120 m_DrawingZoomRect->bottomRight->setCoords(pos);
121 121 m_plot->replot(QCustomPlot::rpQueuedReplot);
122 122 }
123 123
124 124 void applyZoomRect()
125 125 {
126 126 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
127 127 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
128 128
129 129 auto newAxisXRange = QCPRange{m_DrawingZoomRect->topLeft->coords().x(),
130 130 m_DrawingZoomRect->bottomRight->coords().x()};
131 131
132 132 auto newAxisYRange = QCPRange{m_DrawingZoomRect->topLeft->coords().y(),
133 133 m_DrawingZoomRect->bottomRight->coords().y()};
134 134
135 135 removeDrawingRect();
136 136
137 137 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
138 138 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
139 139 m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
140 140 axisX->setRange(newAxisXRange);
141 141 axisY->setRange(newAxisYRange);
142 142
143 143 m_plot->replot(QCustomPlot::rpQueuedReplot);
144 144 }
145 145 }
146 146
147 147 inline bool isDrawingZoneRect(){return m_DrawingZone!=nullptr;}
148 148 void updateZoneRect(const QPoint& newPos)
149 149 {
150 150 m_DrawingZone->setEnd(m_plot->xAxis->pixelToCoord(newPos.x()));
151 151 m_plot->replot(QCustomPlot::rpQueuedReplot);
152 152 }
153 153
154 154 void startDrawingRect(const QPoint &pos)
155 155 {
156 156 removeDrawingRect();
157 157
158 158 auto axisPos = posToAxisPos(pos);
159 159
160 160 m_DrawingZoomRect = new QCPItemRect{m_plot};
161 161 QPen p;
162 162 p.setWidth(2);
163 163 m_DrawingZoomRect->setPen(p);
164 164
165 165 m_DrawingZoomRect->topLeft->setCoords(axisPos);
166 166 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
167 167 }
168 168
169 169 void removeDrawingRect()
170 170 {
171 171 if (m_DrawingZoomRect) {
172 172 m_plot->removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
173 173 m_DrawingZoomRect = nullptr;
174 174 m_plot->replot(QCustomPlot::rpQueuedReplot);
175 175 }
176 176 }
177 177
178 178 void selectZone(const QPoint &pos)
179 179 {
180 180 auto zoneAtPos = selectionZoneAt(pos);
181 181 setSelectionZonesEditionEnabled(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones);
182 182 }
183 183
184 184 void startDrawingZone(const QPoint &pos)
185 185 {
186 186 endDrawingZone();
187 187
188 188 auto axisPos = posToAxisPos(pos);
189 189
190 190 m_DrawingZone = new VisualizationSelectionZoneItem{m_plot};
191 191 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
192 192 m_DrawingZone->setEditionEnabled(false);
193 193 }
194 194
195 195 void endDrawingZone()
196 196 {
197 197 if (m_DrawingZone) {
198 198 auto drawingZoneRange = m_DrawingZone->range();
199 199 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
200 200 m_DrawingZone->setEditionEnabled(true);
201 201 addSelectionZone(m_DrawingZone);
202 202 }
203 203 else {
204 204 m_plot->removeItem(m_DrawingZone);
205 205 }
206 206
207 207 m_plot->replot(QCustomPlot::rpQueuedReplot);
208 208 m_DrawingZone = nullptr;
209 209 }
210 210 }
211 211
212 212 void moveSelectionZone(const QPoint& destination)
213 213 {
214 214 /*
215 215 * I give up on this for now
216 216 * @TODO implement this, the difficulty is that selection zones have their own
217 217 * event handling code which seems to rely on QCP GUI event handling propagation
218 218 * which was a realy bad design choice.
219 219 */
220 220 }
221 221
222 222 void setSelectionZonesEditionEnabled(bool value)
223 223 {
224 224 for (auto s : m_SelectionZones) {
225 225 s->setEditionEnabled(value);
226 226 }
227 227 }
228 228
229 229 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
230 230
231 231 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos) const
232 232 {
233 233 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
234 234 auto minDistanceToZone = -1;
235 235 for (auto zone : m_SelectionZones) {
236 236 auto distanceToZone = zone->selectTest(pos, false);
237 237 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
238 238 && distanceToZone >= 0 && distanceToZone < m_plot->selectionTolerance()) {
239 239 selectionZoneItemUnderCursor = zone;
240 240 }
241 241 }
242 242
243 243 return selectionZoneItemUnderCursor;
244 244 }
245 245
246 246 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
247 247 const QCustomPlot &plot) const
248 248 {
249 249 QVector<VisualizationSelectionZoneItem *> zones;
250 250 for (auto zone : m_SelectionZones) {
251 251 auto distanceToZone = zone->selectTest(pos, false);
252 252 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
253 253 zones << zone;
254 254 }
255 255 }
256 256
257 257 return zones;
258 258 }
259 259
260 260 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
261 261 {
262 262 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
263 263 zone->moveToTop();
264 264 m_SelectionZones.removeAll(zone);
265 265 m_SelectionZones.append(zone);
266 266 }
267 267 }
268 268
269 269 QPointF posToAxisPos(const QPoint &pos) const
270 270 {
271 271 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
272 272 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
273 273 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
274 274 }
275 275
276 276 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
277 277 {
278 278 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
279 279 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
280 280 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
281 281 }
282 282
283 283 inline QCPRange _pixDistanceToRange(double pos1, double pos2, QCPAxis *axis)
284 284 {
285 285 if (axis->scaleType() == QCPAxis::stLinear)
286 286 {
287 287 auto diff = axis->pixelToCoord(pos1) - axis->pixelToCoord(pos2);
288 288 return QCPRange{axis->range().lower + diff, axis->range().upper + diff};
289 289 }
290 290 else
291 291 {
292 292 auto diff = axis->pixelToCoord(pos1) / axis->pixelToCoord(pos2);
293 293 return QCPRange{axis->range().lower * diff, axis->range().upper * diff};
294 294 }
295 295 }
296 296
297 297 void setRange(const DateTimeRange &newRange, bool updateVar=true)
298 298 {
299 299 this->m_plot->xAxis->setRange(newRange.m_TStart, newRange.m_TEnd);
300 300 if(updateVar)
301 301 {
302 302 for (auto it = m_VariableToPlotMultiMap.begin(),
303 303 end = m_VariableToPlotMultiMap.end();
304 304 it != end; it = m_VariableToPlotMultiMap.upper_bound(it->first))
305 305 {
306 306 sqpApp->variableController().asyncChangeRange(it->first, newRange);
307 307 }
308 308 }
309 309 m_plot->replot(QCustomPlot::rpQueuedReplot);
310 310 }
311 311
312 312 void setRange(const QCPRange &newRange)
313 313 {
314 314 auto graphRange = DateTimeRange{newRange.lower, newRange.upper};
315 315 setRange(graphRange);
316 316 }
317 317
318 318 void rescaleY()
319 319 {
320 320 m_plot->yAxis->rescale(true);
321 321 }
322 322
323 323 std::tuple<double,double> moveGraph(const QPoint& destination)
324 324 {
325 325 auto currentPos = m_plot->mapFromParent(destination);
326 326 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
327 327 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
328 328 auto oldXRange = xAxis->range();
329 329 auto oldYRange = yAxis->range();
330 330 double dx = xAxis->pixelToCoord(m_lastMousePos.x()) - xAxis->pixelToCoord(currentPos.x());
331 331 xAxis->setRange(m_lastXRange.lower+dx, m_lastXRange.upper+dx);
332 332 if(yAxis->scaleType() == QCPAxis::stLinear)
333 333 {
334 334 double dy = yAxis->pixelToCoord(m_lastMousePos.y()) - yAxis->pixelToCoord(currentPos.y());
335 335 yAxis->setRange(m_lastYRange.lower+dy, m_lastYRange.upper+dy);
336 336 }
337 337 else
338 338 {
339 339 double dy = yAxis->pixelToCoord(m_lastMousePos.y()) / yAxis->pixelToCoord(currentPos.y());
340 340 yAxis->setRange(m_lastYRange.lower*dy, m_lastYRange.upper*dy);
341 341 }
342 342 auto newXRange = xAxis->range();
343 343 auto newYRange = yAxis->range();
344 344 setRange(xAxis->range());
345 345 //m_lastMousePos = currentPos;
346 346 return {newXRange.lower - oldXRange.lower, newYRange.lower - oldYRange.lower};
347 347 }
348 348
349 349 void zoom(double factor, int center, Qt::Orientation orientation)
350 350 {
351 351 QCPAxis *axis = m_plot->axisRect()->rangeZoomAxis(orientation);
352 352 axis->scaleRange(factor, axis->pixelToCoord(center));
353 353 if (orientation == Qt::Horizontal)
354 354 setRange(axis->range());
355 355 m_plot->replot(QCustomPlot::rpQueuedReplot);
356 356 }
357 357
358 358 void transform(const DateTimeRangeTransformation &tranformation)
359 359 {
360 360 auto graphRange = m_plot->xAxis->range();
361 361 DateTimeRange range{graphRange.lower, graphRange.upper};
362 362 range = range.transform(tranformation);
363 363 setRange(range);
364 364 m_plot->replot(QCustomPlot::rpQueuedReplot);
365 365 }
366 366
367 367 void move(double dx, double dy)
368 368 {
369 369 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
370 370 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
371 371 xAxis->setRange(QCPRange(xAxis->range().lower+dx, xAxis->range().upper+dx));
372 372 yAxis->setRange(QCPRange(yAxis->range().lower+dy, yAxis->range().upper+dy));
373 373 setRange(xAxis->range());
374 374 m_plot->replot(QCustomPlot::rpQueuedReplot);
375 375 }
376 376
377 377 void move(double factor, Qt::Orientation orientation)
378 378 {
379 379 auto oldRange = m_plot->xAxis->range();
380 380 QCPAxis *axis = m_plot->axisRect()->rangeDragAxis(orientation);
381 381 if (m_plot->xAxis->scaleType() == QCPAxis::stLinear) {
382 382 double rg = (axis->range().upper - axis->range().lower) * (factor / 10);
383 383 axis->setRange(axis->range().lower + (rg), axis->range().upper + (rg));
384 384 }
385 385 else if (m_plot->xAxis->scaleType() == QCPAxis::stLogarithmic) {
386 386 int start = 0, stop = 0;
387 387 double diff = 0.;
388 388 if (factor > 0.0) {
389 389 stop = m_plot->width() * factor / 10;
390 390 start = 2 * m_plot->width() * factor / 10;
391 391 }
392 392 if (factor < 0.0) {
393 393 factor *= -1.0;
394 394 start = m_plot->width() * factor / 10;
395 395 stop = 2 * m_plot->width() * factor / 10;
396 396 }
397 397 diff = axis->pixelToCoord(start) / axis->pixelToCoord(stop);
398 398 axis->setRange(m_plot->axisRect()->rangeDragAxis(orientation)->range().lower * diff,
399 399 m_plot->axisRect()->rangeDragAxis(orientation)->range().upper * diff);
400 400 }
401 401 if (orientation == Qt::Horizontal)
402 402 setRange(axis->range());
403 403 m_plot->replot(QCustomPlot::rpQueuedReplot);
404 404 }
405 405 };
406 406
407 407 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
408 408 : VisualizationDragWidget{parent},
409 409 ui{new Ui::VisualizationGraphWidget},
410 410 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
411 411 {
412 412 ui->setupUi(this);
413 413 this->layout()->addWidget(impl->m_plot);
414 414 // 'Close' options : widget is deleted when closed
415 415 setAttribute(Qt::WA_DeleteOnClose);
416 416
417 417 // The delegate must be initialized after the ui as it uses the plot
418 418 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
419 419
420 420 // Init the cursors
421 421 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
422 422 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
423 423 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
424 424 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
425 425
426 426 this->setFocusPolicy(Qt::WheelFocus);
427 427 this->setMouseTracking(true);
428 428 impl->m_plot->setAttribute(Qt::WA_TransparentForMouseEvents);
429 429 impl->m_plot->setContextMenuPolicy(Qt::CustomContextMenu);
430 430 impl->m_plot->setParent(this);
431 431
432 432 connect(&sqpApp->variableController(), &VariableController2::variableDeleted, this, &VisualizationGraphWidget::variableDeleted);
433 433 }
434 434
435 435
436 436 VisualizationGraphWidget::~VisualizationGraphWidget()
437 437 {
438 438 delete ui;
439 439 }
440 440
441 441 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
442 442 {
443 443 auto parent = parentWidget();
444 444 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
445 445 parent = parent->parentWidget();
446 446 }
447 447
448 448 return qobject_cast<VisualizationZoneWidget *>(parent);
449 449 }
450 450
451 451 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
452 452 {
453 453 auto parent = parentWidget();
454 454 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
455 455 parent = parent->parentWidget();
456 456 }
457 457
458 458 return qobject_cast<VisualizationWidget *>(parent);
459 459 }
460 460
461 461 void VisualizationGraphWidget::setFlags(GraphFlags flags)
462 462 {
463 463 impl->m_Flags = std::move(flags);
464 464 }
465 465
466 466 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, DateTimeRange range)
467 467 {
468 468 // Uses delegate to create the qcpplot components according to the variable
469 469 auto createdPlottables = VisualizationGraphHelper::create(variable, *impl->m_plot);
470 470
471 471 // Sets graph properties
472 472 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
473 473
474 474 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
475 475
476 476 setGraphRange(range);
477 477 // If the variable already has its data loaded, load its units and its range in the graph
478 478 if (variable->dataSeries() != nullptr) {
479 479 impl->m_RenderingDelegate->setAxesUnits(*variable);
480 480 }
481 481 else
482 482 {
483 483 auto context = new QObject{this};
484 484 connect(variable.get(), &Variable::updated, context,
485 485 [this, variable, context, range](QUuid)
486 486 {
487 487 this->impl->m_RenderingDelegate->setAxesUnits(*variable);
488 488 this->impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
489 489 delete context;
490 490 }
491 491 );
492 492 }
493 493 //@TODO this is bad! when variable is moved to another graph it still fires
494 494 // even if this has been deleted
495 495 connect(variable.get(), &Variable::updated, this, &VisualizationGraphWidget::variableUpdated);
496 496 this->onUpdateVarDisplaying(variable, range); // My bullshit
497 497 emit variableAdded(variable);
498 498 }
499 499
500 500 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
501 501 {
502 502 // Each component associated to the variable :
503 503 // - is removed from qcpplot (which deletes it)
504 504 // - is no longer referenced in the map
505 505 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
506 506 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
507 507 emit variableAboutToBeRemoved(variable);
508 508
509 509 auto &plottablesMap = variableIt->second;
510 510
511 511 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
512 512 plottableIt != plottableEnd;) {
513 513 impl->m_plot->removePlottable(plottableIt->second);
514 514 plottableIt = plottablesMap.erase(plottableIt);
515 515 }
516 516
517 517 impl->m_VariableToPlotMultiMap.erase(variableIt);
518 518 }
519 519
520 520 // Updates graph
521 521 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
522 522 }
523 523
524 524 std::vector<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
525 525 {
526 526 auto variables = std::vector<std::shared_ptr<Variable> >{};
527 527 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
528 528 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
529 529 variables.push_back(it->first);
530 530 }
531 531
532 532 return variables;
533 533 }
534 534
535 535 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
536 536 {
537 537 if (!variable) {
538 538 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
539 539 return;
540 540 }
541 541
542 542 VisualizationGraphHelper::setYAxisRange(variable, *impl->m_plot);
543 543 }
544 544
545 545 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
546 546 {
547 547 auto graphRange = impl->m_plot->xAxis->range();
548 548 return DateTimeRange{graphRange.lower, graphRange.upper};
549 549 }
550 550
551 551 void VisualizationGraphWidget::setGraphRange(const DateTimeRange &range, bool updateVar, bool forward)
552 552 {
553 auto oldRange = graphRange();
554 553 impl->setRange(range, updateVar);
555 554 if(forward)
556 555 {
557 auto newRange = graphRange();
558 if(auto tf = DateTimeRangeHelper::computeTransformation(oldRange,newRange))
559 emit this->transform_sig(tf.value(), false);
556 emit this->setrange_sig(this->graphRange(), true, false);
560 557 }
561 558
562 559 }
563 560
564 561 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
565 562 {
566 563 impl->m_VariableAutoRangeOnInit = value;
567 564 }
568 565
569 566 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
570 567 {
571 568 QVector<DateTimeRange> ranges;
572 569 for (auto zone : impl->m_SelectionZones) {
573 570 ranges << zone->range();
574 571 }
575 572
576 573 return ranges;
577 574 }
578 575
579 576 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange> &ranges)
580 577 {
581 578 for (const auto &range : ranges) {
582 579 // note: ownership is transfered to QCustomPlot
583 580 auto zone = new VisualizationSelectionZoneItem(&plot());
584 581 zone->setRange(range.m_TStart, range.m_TEnd);
585 582 impl->addSelectionZone(zone);
586 583 }
587 584
588 585 plot().replot(QCustomPlot::rpQueuedReplot);
589 586 }
590 587
591 588 VisualizationSelectionZoneItem *
592 589 VisualizationGraphWidget::addSelectionZone(const QString &name, const DateTimeRange &range)
593 590 {
594 591 // note: ownership is transfered to QCustomPlot
595 592 auto zone = new VisualizationSelectionZoneItem(&plot());
596 593 zone->setName(name);
597 594 zone->setRange(range.m_TStart, range.m_TEnd);
598 595 impl->addSelectionZone(zone);
599 596
600 597 plot().replot(QCustomPlot::rpQueuedReplot);
601 598
602 599 return zone;
603 600 }
604 601
605 602 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
606 603 {
607 604 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
608 605
609 606 if (impl->m_HoveredZone == selectionZone) {
610 607 impl->m_HoveredZone = nullptr;
611 608 setCursor(Qt::ArrowCursor);
612 609 }
613 610
614 611 impl->m_SelectionZones.removeAll(selectionZone);
615 612 plot().removeItem(selectionZone);
616 613 plot().replot(QCustomPlot::rpQueuedReplot);
617 614 }
618 615
619 616 void VisualizationGraphWidget::undoZoom()
620 617 {
621 618 auto zoom = impl->m_ZoomStack.pop();
622 619 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
623 620 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
624 621
625 622 axisX->setRange(zoom.first);
626 623 axisY->setRange(zoom.second);
627 624
628 625 plot().replot(QCustomPlot::rpQueuedReplot);
629 626 }
630 627
631 628 void VisualizationGraphWidget::zoom(double factor, int center, Qt::Orientation orientation, bool forward)
632 629 {
633 630 impl->zoom(factor, center, orientation);
634 631 if(forward && orientation==Qt::Horizontal)
635 emit this->zoom_sig(factor, center, orientation, false);
632 emit this->setrange_sig(this->graphRange(), true, false);
636 633 }
637 634
638 635 void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation, bool forward)
639 636 {
640 637 impl->move(factor, orientation);
641 638 if(forward)
642 emit this->move_sig(factor, orientation, false);
639 emit this->setrange_sig(this->graphRange(), true, false);
643 640 }
644 641
645 642 void VisualizationGraphWidget::move(double dx, double dy, bool forward)
646 643 {
647 644 impl->move(dx, dy);
648 645 if(forward)
649 emit this->move_sig(dx, dy, false);
646 emit this->setrange_sig(this->graphRange(), true, false);
650 647 }
651 648
652 649 void VisualizationGraphWidget::transform(const DateTimeRangeTransformation &tranformation, bool forward)
653 650 {
654 651 impl->transform(tranformation);
655 652 if(forward)
656 emit this->transform_sig(tranformation, false);
653 emit this->setrange_sig(this->graphRange(), true, false);
657 654 }
658 655
659 656 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
660 657 {
661 658 if (visitor) {
662 659 visitor->visit(this);
663 660 }
664 661 else {
665 662 qCCritical(LOG_VisualizationGraphWidget())
666 663 << tr("Can't visit widget : the visitor is null");
667 664 }
668 665 }
669 666
670 667 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
671 668 {
672 669 auto isSpectrogram = [](const auto &variable) {
673 670 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
674 671 };
675 672
676 673 // - A spectrogram series can't be dropped on graph with existing plottables
677 674 // - No data series can be dropped on graph with existing spectrogram series
678 675 return isSpectrogram(variable)
679 676 ? impl->m_VariableToPlotMultiMap.empty()
680 677 : std::none_of(
681 678 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
682 679 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
683 680 }
684 681
685 682 bool VisualizationGraphWidget::contains(const Variable &variable) const
686 683 {
687 684 // Finds the variable among the keys of the map
688 685 auto variablePtr = &variable;
689 686 auto findVariable
690 687 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
691 688
692 689 auto end = impl->m_VariableToPlotMultiMap.cend();
693 690 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
694 691 return it != end;
695 692 }
696 693
697 694 QString VisualizationGraphWidget::name() const
698 695 {
699 696 return impl->m_Name;
700 697 }
701 698
702 699 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
703 700 {
704 701 auto mimeData = new QMimeData;
705 702
706 703 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position);
707 704 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
708 705 && selectionZoneItemUnderCursor) {
709 706 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
710 707 selectionZoneItemUnderCursor->range()));
711 708 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
712 709 selectionZoneItemUnderCursor->range()));
713 710 }
714 711 else {
715 712 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
716 713
717 714 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
718 715 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
719 716 }
720 717
721 718 return mimeData;
722 719 }
723 720
724 721 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
725 722 {
726 723 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition);
727 724 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
728 725 && selectionZoneItemUnderCursor) {
729 726
730 727 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
731 728 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
732 729
733 730 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
734 731 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
735 732 .toSize();
736 733
737 734 auto pixmap = QPixmap(zoneSize);
738 735 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
739 736
740 737 return pixmap;
741 738 }
742 739
743 740 return QPixmap();
744 741 }
745 742
746 743 bool VisualizationGraphWidget::isDragAllowed() const
747 744 {
748 745 return true;
749 746 }
750 747
751 748 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
752 749 {
753 750 if (highlighted) {
754 751 plot().setBackground(QBrush(QColor("#BBD5EE")));
755 752 }
756 753 else {
757 754 plot().setBackground(QBrush(Qt::white));
758 755 }
759 756
760 757 plot().update();
761 758 }
762 759
763 760 void VisualizationGraphWidget::addVerticalCursor(double time)
764 761 {
765 762 impl->m_VerticalCursor->setPosition(time);
766 763 impl->m_VerticalCursor->setVisible(true);
767 764
768 765 auto text
769 766 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
770 767 impl->m_VerticalCursor->setLabelText(text);
771 768 }
772 769
773 770 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
774 771 {
775 772 impl->m_VerticalCursor->setAbsolutePosition(position);
776 773 impl->m_VerticalCursor->setVisible(true);
777 774
778 775 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
779 776 auto text
780 777 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
781 778 impl->m_VerticalCursor->setLabelText(text);
782 779 }
783 780
784 781 void VisualizationGraphWidget::removeVerticalCursor()
785 782 {
786 783 impl->m_VerticalCursor->setVisible(false);
787 784 plot().replot(QCustomPlot::rpQueuedReplot);
788 785 }
789 786
790 787 void VisualizationGraphWidget::addHorizontalCursor(double value)
791 788 {
792 789 impl->m_HorizontalCursor->setPosition(value);
793 790 impl->m_HorizontalCursor->setVisible(true);
794 791 impl->m_HorizontalCursor->setLabelText(QString::number(value));
795 792 }
796 793
797 794 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
798 795 {
799 796 impl->m_HorizontalCursor->setAbsolutePosition(position);
800 797 impl->m_HorizontalCursor->setVisible(true);
801 798
802 799 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
803 800 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
804 801 }
805 802
806 803 void VisualizationGraphWidget::removeHorizontalCursor()
807 804 {
808 805 impl->m_HorizontalCursor->setVisible(false);
809 806 plot().replot(QCustomPlot::rpQueuedReplot);
810 807 }
811 808
812 809 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
813 810 {
814 811 Q_UNUSED(event);
815 812
816 813 for (auto i : impl->m_SelectionZones) {
817 814 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
818 815 }
819 816
820 817 // Prevents that all variables will be removed from graph when it will be closed
821 818 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
822 819 emit variableAboutToBeRemoved(variableEntry.first);
823 820 }
824 821 }
825 822
826 823 void VisualizationGraphWidget::enterEvent(QEvent *event)
827 824 {
828 825 Q_UNUSED(event);
829 826 impl->m_RenderingDelegate->showGraphOverlay(true);
830 827 }
831 828
832 829 void VisualizationGraphWidget::leaveEvent(QEvent *event)
833 830 {
834 831 Q_UNUSED(event);
835 832 impl->m_RenderingDelegate->showGraphOverlay(false);
836 833
837 834 if (auto parentZone = parentZoneWidget()) {
838 835 parentZone->notifyMouseLeaveGraph(this);
839 836 }
840 837 else {
841 838 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
842 839 }
843 840
844 841 if (impl->m_HoveredZone) {
845 842 impl->m_HoveredZone->setHovered(false);
846 843 impl->m_HoveredZone = nullptr;
847 844 }
848 845 }
849 846
850 847 void VisualizationGraphWidget::wheelEvent(QWheelEvent *event)
851 848 {
852 849 double factor;
853 850 double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually
854 851 if (event->modifiers() == Qt::ControlModifier) {
855 852 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
856 853 {
857 854 setCursor(Qt::SizeVerCursor);
858 855 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps);
859 856 zoom(factor, event->pos().y(), Qt::Vertical);
860 857 }
861 858 }
862 859 else if (event->modifiers() == Qt::ShiftModifier) {
863 860 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
864 861 {
865 862 setCursor(Qt::SizeHorCursor);
866 863 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps);
867 864 zoom(factor, event->pos().x(), Qt::Horizontal);
868 865 }
869 866 }
870 867 else {
871 868 move(wheelSteps, Qt::Horizontal);
872 869 }
873 870 event->accept();
874 871 }
875 872
876 873
877 874
878 875 void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent *event)
879 876 {
880 877 if(impl->isDrawingZoomRect())
881 878 {
882 879 impl->updateZoomRect(event->pos());
883 880 }
884 881 else if (impl->isDrawingZoneRect())
885 882 {
886 883 impl->updateZoneRect(event->pos());
887 884 }
888 885 else if (event->buttons() == Qt::LeftButton)
889 886 {
890 887 if(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::None)
891 888 {
892 889 auto [dx,dy] = impl->moveGraph(event->pos());
893 emit this->move_sig(dx,0., false); // don't sync Y transformations
890 emit this->setrange_sig(this->graphRange(), true, false);
894 891 }
895 892 else if(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones)
896 893 {
897 894
898 895 }
899 896 }
900 897 else
901 898 {
902 899 impl->m_RenderingDelegate->updateTooltip(event);
903 900 }
904 901 //event->accept();
905 902 QWidget::mouseMoveEvent(event);
906 903 }
907 904
908 905 void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent *event)
909 906 {
910 907 if(impl->isDrawingZoomRect())
911 908 {
912 909 auto oldRange = this->graphRange();
913 910 impl->applyZoomRect();
914 911 auto newRange = this->graphRange();
915 912 if(auto tf = DateTimeRangeHelper::computeTransformation(oldRange,newRange))
916 913 emit this->transform_sig(tf.value(), false);
917 914 }
918 915 else if(impl->isDrawingZoneRect())
919 916 {
920 917 impl->endDrawingZone();
921 918 }
922 919 else
923 920 {
924 921 setCursor(Qt::ArrowCursor);
925 922 }
926 923 event->accept();
927 924 }
928 925
929 926 void VisualizationGraphWidget::mousePressEvent(QMouseEvent *event)
930 927 {
931 928 if (event->button()==Qt::RightButton)
932 929 {
933 930 onGraphMenuRequested(event->pos());
934 931 }
935 932 else
936 933 {
937 934 auto selectedZone = impl->selectionZoneAt(event->pos());
938 935 switch (sqpApp->plotsInteractionMode())
939 936 {
940 937 case SqpApplication::PlotsInteractionMode::DragAndDrop :
941 938 break;
942 939 case SqpApplication::PlotsInteractionMode::SelectionZones :
943 940 impl->setSelectionZonesEditionEnabled(true);
944 941 if ((event->modifiers() == Qt::ControlModifier) && (selectedZone != nullptr))
945 942 {
946 943 selectedZone->setAssociatedEditedZones(parentVisualizationWidget()->selectionZoneManager().selectedItems());
947 944 }
948 945 else
949 946 {
950 947 if (!selectedZone)
951 948 {
952 949 parentVisualizationWidget()->selectionZoneManager().clearSelection();
953 950 impl->startDrawingZone(event->pos());
954 951 }
955 952 else
956 953 {
957 954 parentVisualizationWidget()->selectionZoneManager().select({ selectedZone });
958 955 }
959 956 }
960 957 break;
961 958 case SqpApplication::PlotsInteractionMode::ZoomBox :
962 959 impl->startDrawingRect(event->pos());
963 960 break;
964 961 default:
965 962 if(auto item = impl->m_plot->itemAt(event->pos()))
966 963 {
967 964 emit impl->m_plot->itemClick(item,event);
968 965 if(qobject_cast<VisualizationSelectionZoneItem*>(item))
969 966 {
970 967 setCursor(Qt::ClosedHandCursor);
971 968 impl->enterPlotDrag(event->pos());
972 969 }
973 970 }
974 971 else
975 972 {
976 973 setCursor(Qt::ClosedHandCursor);
977 974 impl->enterPlotDrag(event->pos());
978 975 }
979 976 }
980 977 }
981 978 //event->accept();
982 979 QWidget::mousePressEvent(event);
983 980 }
984 981
985 982 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent *event)
986 983 {
987 984 impl->m_RenderingDelegate->onMouseDoubleClick(event);
988 985 }
989 986
990 987 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent *event)
991 988 {
992 989 switch (event->key()) {
993 990 case Qt::Key_Control:
994 991 event->accept();
995 992 break;
996 993 case Qt::Key_Shift:
997 994 event->accept();
998 995 break;
999 996 default:
1000 997 QWidget::keyReleaseEvent(event);
1001 998 break;
1002 999 }
1003 1000 setCursor(Qt::ArrowCursor);
1004 1001 //event->accept();
1005 1002 }
1006 1003
1007 1004 void VisualizationGraphWidget::keyPressEvent(QKeyEvent *event)
1008 1005 {
1009 1006 switch (event->key()) {
1010 1007 case Qt::Key_Control:
1011 1008 setCursor(Qt::CrossCursor);
1012 1009 break;
1013 1010 case Qt::Key_Shift:
1014 1011 break;
1015 1012 case Qt::Key_M:
1016 1013 impl->rescaleY();
1017 1014 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
1018 1015 break;
1019 1016 case Qt::Key_Left:
1020 1017 if (event->modifiers() != Qt::ControlModifier) {
1021 1018 move(-0.1, Qt::Horizontal);
1022 1019 }
1023 1020 else {
1024 1021 zoom(2, this->width() / 2, Qt::Horizontal);
1025 1022 }
1026 1023 break;
1027 1024 case Qt::Key_Right:
1028 1025 if (event->modifiers() != Qt::ControlModifier) {
1029 1026 move(0.1, Qt::Horizontal);
1030 1027 }
1031 1028 else {
1032 1029 zoom(0.5, this->width() / 2, Qt::Horizontal);
1033 1030 }
1034 1031 break;
1035 1032 case Qt::Key_Up:
1036 1033 if (event->modifiers() != Qt::ControlModifier) {
1037 1034 move(0.1, Qt::Vertical);
1038 1035 }
1039 1036 else {
1040 1037 zoom(0.5, this->height() / 2, Qt::Vertical);
1041 1038 }
1042 1039 break;
1043 1040 case Qt::Key_Down:
1044 1041 if (event->modifiers() != Qt::ControlModifier) {
1045 1042 move(-0.1, Qt::Vertical);
1046 1043 }
1047 1044 else {
1048 1045 zoom(2, this->height() / 2, Qt::Vertical);
1049 1046 }
1050 1047 break;
1051 1048 default:
1052 1049 QWidget::keyPressEvent(event);
1053 1050 break;
1054 1051 }
1055 1052 }
1056 1053
1057 1054 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
1058 1055 {
1059 1056 return *impl->m_plot;
1060 1057 }
1061 1058
1062 1059 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
1063 1060 {
1064 1061 QMenu graphMenu{};
1065 1062
1066 1063 // Iterates on variables (unique keys)
1067 1064 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
1068 1065 end = impl->m_VariableToPlotMultiMap.cend();
1069 1066 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
1070 1067 // 'Remove variable' action
1071 1068 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
1072 1069 [this, var = it->first]() { removeVariable(var); });
1073 1070 }
1074 1071
1075 1072 if (!impl->m_ZoomStack.isEmpty()) {
1076 1073 if (!graphMenu.isEmpty()) {
1077 1074 graphMenu.addSeparator();
1078 1075 }
1079 1076
1080 1077 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
1081 1078 }
1082 1079
1083 1080 // Selection Zone Actions
1084 1081 auto selectionZoneItem = impl->selectionZoneAt(pos);
1085 1082 if (selectionZoneItem) {
1086 1083 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1087 1084 selectedItems.removeAll(selectionZoneItem);
1088 1085 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
1089 1086
1090 1087 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
1091 1088 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
1092 1089 graphMenu.addSeparator();
1093 1090 }
1094 1091
1095 1092 QHash<QString, QMenu *> subMenus;
1096 1093 QHash<QString, bool> subMenusEnabled;
1097 1094 QHash<QString, FilteringAction *> filteredMenu;
1098 1095
1099 1096 for (auto zoneAction : zoneActions) {
1100 1097
1101 1098 auto isEnabled = zoneAction->isEnabled(selectedItems);
1102 1099
1103 1100 auto menu = &graphMenu;
1104 1101 QString menuPath;
1105 1102 for (auto subMenuName : zoneAction->subMenuList()) {
1106 1103 menuPath += '/';
1107 1104 menuPath += subMenuName;
1108 1105
1109 1106 if (!subMenus.contains(menuPath)) {
1110 1107 menu = menu->addMenu(subMenuName);
1111 1108 subMenus[menuPath] = menu;
1112 1109 subMenusEnabled[menuPath] = isEnabled;
1113 1110 }
1114 1111 else {
1115 1112 menu = subMenus.value(menuPath);
1116 1113 if (isEnabled) {
1117 1114 // The sub menu is enabled if at least one of its actions is enabled
1118 1115 subMenusEnabled[menuPath] = true;
1119 1116 }
1120 1117 }
1121 1118 }
1122 1119
1123 1120 FilteringAction *filterAction = nullptr;
1124 1121 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList())) {
1125 1122 filterAction = filteredMenu.value(menuPath);
1126 1123 if (!filterAction) {
1127 1124 filterAction = new FilteringAction{this};
1128 1125 filteredMenu[menuPath] = filterAction;
1129 1126 menu->addAction(filterAction);
1130 1127 }
1131 1128 }
1132 1129
1133 1130 auto action = menu->addAction(zoneAction->name());
1134 1131 action->setEnabled(isEnabled);
1135 1132 action->setShortcut(zoneAction->displayedShortcut());
1136 1133 QObject::connect(action, &QAction::triggered,
1137 1134 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1138 1135
1139 1136 if (filterAction && zoneAction->isFilteringAllowed()) {
1140 1137 filterAction->addActionToFilter(action);
1141 1138 }
1142 1139 }
1143 1140
1144 1141 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
1145 1142 it.value()->setEnabled(subMenusEnabled[it.key()]);
1146 1143 }
1147 1144 }
1148 1145
1149 1146 if (!graphMenu.isEmpty()) {
1150 1147 graphMenu.exec(QCursor::pos());
1151 1148 }
1152 1149 }
1153 1150
1154 1151 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
1155 1152 {
1156 1153 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1157 1154 }
1158 1155
1159 1156 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
1160 1157 {
1161 1158 // Handles plot rendering when mouse is moving
1162 1159 impl->m_RenderingDelegate->updateTooltip(event);
1163 1160
1164 1161 auto axisPos = impl->posToAxisPos(event->pos());
1165 1162
1166 1163 // Zoom box and zone drawing
1167 1164 if (impl->m_DrawingZoomRect) {
1168 1165 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1169 1166 }
1170 1167 else if (impl->m_DrawingZone) {
1171 1168 impl->m_DrawingZone->setEnd(axisPos.x());
1172 1169 }
1173 1170
1174 1171 // Cursor
1175 1172 if (auto parentZone = parentZoneWidget()) {
1176 1173 if (impl->pointIsInAxisRect(axisPos, plot())) {
1177 1174 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1178 1175 }
1179 1176 else {
1180 1177 parentZone->notifyMouseLeaveGraph(this);
1181 1178 }
1182 1179 }
1183 1180
1184 1181 // Search for the selection zone under the mouse
1185 1182 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1186 1183 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1187 1184 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
1188 1185
1189 1186 // Sets the appropriate cursor shape
1190 1187 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1191 1188 setCursor(cursorShape);
1192 1189
1193 1190 // Manages the hovered zone
1194 1191 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
1195 1192 if (impl->m_HoveredZone) {
1196 1193 impl->m_HoveredZone->setHovered(false);
1197 1194 }
1198 1195 selectionZoneItemUnderCursor->setHovered(true);
1199 1196 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1200 1197 plot().replot(QCustomPlot::rpQueuedReplot);
1201 1198 }
1202 1199 }
1203 1200 else {
1204 1201 // There is no zone under the mouse or the interaction mode is not "selection zones"
1205 1202 if (impl->m_HoveredZone) {
1206 1203 impl->m_HoveredZone->setHovered(false);
1207 1204 impl->m_HoveredZone = nullptr;
1208 1205 }
1209 1206
1210 1207 setCursor(Qt::ArrowCursor);
1211 1208 }
1212 1209
1213 1210 impl->m_HasMovedMouse = true;
1214 1211 VisualizationDragWidget::mouseMoveEvent(event);
1215 1212 }
1216 1213
1217 1214 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
1218 1215 {
1219 1216 // Processes event only if the wheel occurs on axis rect
1220 1217 if (!dynamic_cast<QCPAxisRect *>(impl->m_plot->layoutElementAt(event->posF()))) {
1221 1218 return;
1222 1219 }
1223 1220
1224 1221 auto value = event->angleDelta().x() + event->angleDelta().y();
1225 1222 if (value != 0) {
1226 1223
1227 1224 auto direction = value > 0 ? 1.0 : -1.0;
1228 1225 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1229 1226 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1230 1227 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1231 1228
1232 1229 auto zoomOrientations = QFlags<Qt::Orientation>{};
1233 1230 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1234 1231 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1235 1232
1236 1233 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1237 1234
1238 1235 if (!isZoomX && !isZoomY) {
1239 1236 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1240 1237 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1241 1238
1242 1239 axis->setRange(axis->range() + diff);
1243 1240
1244 1241 if (plot().noAntialiasingOnDrag()) {
1245 1242 plot().setNotAntialiasedElements(QCP::aeAll);
1246 1243 }
1247 1244
1248 1245 // plot().replot(QCustomPlot::rpQueuedReplot);
1249 1246 }
1250 1247 }
1251 1248 }
1252 1249
1253 1250 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
1254 1251 {
1255 1252 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1256 1253 auto isSelectionZoneMode
1257 1254 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1258 1255 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1259 1256
1260 1257 if (!isDragDropClick && isLeftClick) {
1261 1258 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
1262 1259 // Starts a zoom box
1263 1260 impl->startDrawingRect(event->pos());
1264 1261 }
1265 1262 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
1266 1263 // Starts a new selection zone
1267 1264 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1268 1265 if (!zoneAtPos) {
1269 1266 impl->startDrawingZone(event->pos());
1270 1267 }
1271 1268 }
1272 1269 }
1273 1270
1274 1271
1275 1272 // Allows zone edition only in selection zone mode without drag&drop
1276 1273 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1277 1274
1278 1275 // Selection / Deselection
1279 1276 if (isSelectionZoneMode) {
1280 1277 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1281 1278 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1282 1279
1283 1280
1284 1281 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1285 1282 && !isMultiSelectionClick) {
1286 1283 parentVisualizationWidget()->selectionZoneManager().select(
1287 1284 {selectionZoneItemUnderCursor});
1288 1285 }
1289 1286 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
1290 1287 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1291 1288 }
1292 1289 else {
1293 1290 // No selection change
1294 1291 }
1295 1292
1296 1293 if (selectionZoneItemUnderCursor && isLeftClick) {
1297 1294 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1298 1295 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1299 1296 }
1300 1297 }
1301 1298
1302 1299
1303 1300 impl->m_HasMovedMouse = false;
1304 1301 VisualizationDragWidget::mousePressEvent(event);
1305 1302 }
1306 1303
1307 1304 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
1308 1305 {
1309 1306 if (impl->m_DrawingZoomRect) {
1310 1307
1311 1308 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1312 1309 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1313 1310
1314 1311 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
1315 1312 impl->m_DrawingZoomRect->bottomRight->coords().x()};
1316 1313
1317 1314 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
1318 1315 impl->m_DrawingZoomRect->bottomRight->coords().y()};
1319 1316
1320 1317 impl->removeDrawingRect();
1321 1318
1322 1319 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1323 1320 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
1324 1321 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1325 1322 axisX->setRange(newAxisXRange);
1326 1323 axisY->setRange(newAxisYRange);
1327 1324
1328 1325 plot().replot(QCustomPlot::rpQueuedReplot);
1329 1326 }
1330 1327 }
1331 1328
1332 1329 impl->endDrawingZone();
1333 1330
1334 1331 // Selection / Deselection
1335 1332 auto isSelectionZoneMode
1336 1333 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1337 1334 if (isSelectionZoneMode) {
1338 1335 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1339 1336 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1340 1337 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1341 1338 && !impl->m_HasMovedMouse) {
1342 1339
1343 1340 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1344 1341 if (zonesUnderCursor.count() > 1) {
1345 1342 // There are multiple zones under the mouse.
1346 1343 // Performs the selection with a selection dialog.
1347 1344 VisualizationMultiZoneSelectionDialog dialog{this};
1348 1345 dialog.setZones(zonesUnderCursor);
1349 1346 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1350 1347 dialog.activateWindow();
1351 1348 dialog.raise();
1352 1349 if (dialog.exec() == QDialog::Accepted) {
1353 1350 auto selection = dialog.selectedZones();
1354 1351
1355 1352 if (!isMultiSelectionClick) {
1356 1353 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1357 1354 }
1358 1355
1359 1356 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
1360 1357 auto zone = it.key();
1361 1358 auto isSelected = it.value();
1362 1359 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
1363 1360 isSelected);
1364 1361
1365 1362 if (isSelected) {
1366 1363 // Puts the zone on top of the stack so it can be moved or resized
1367 1364 impl->moveSelectionZoneOnTop(zone, plot());
1368 1365 }
1369 1366 }
1370 1367 }
1371 1368 }
1372 1369 else {
1373 1370 if (!isMultiSelectionClick) {
1374 1371 parentVisualizationWidget()->selectionZoneManager().select(
1375 1372 {selectionZoneItemUnderCursor});
1376 1373 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1377 1374 }
1378 1375 else {
1379 1376 parentVisualizationWidget()->selectionZoneManager().setSelected(
1380 1377 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
1381 1378 || event->button() == Qt::RightButton);
1382 1379 }
1383 1380 }
1384 1381 }
1385 1382 else {
1386 1383 // No selection change
1387 1384 }
1388 1385 }
1389 1386 }
1390 1387
1391 1388 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1392 1389 {
1393 1390 auto graphRange = impl->m_plot->xAxis->range();
1394 1391 auto dateTime = DateTimeRange{graphRange.lower, graphRange.upper};
1395 1392
1396 1393 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
1397 1394 auto variable = variableEntry.first;
1398 1395 qCDebug(LOG_VisualizationGraphWidget())
1399 1396 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1400 1397 qCDebug(LOG_VisualizationGraphWidget())
1401 1398 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1402 1399 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
1403 1400 impl->updateData(variableEntry.second, variable, variable->range());
1404 1401 }
1405 1402 }
1406 1403 }
1407 1404
1408 1405 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
1409 1406 const DateTimeRange &range)
1410 1407 {
1411 1408 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1412 1409 if (it != impl->m_VariableToPlotMultiMap.end()) {
1413 1410 impl->updateData(it->second, variable, range);
1414 1411 }
1415 1412 }
1416 1413
1417 1414 void VisualizationGraphWidget::variableUpdated(QUuid id)
1418 1415 {
1419 1416 for (auto &[var, plotables] : impl->m_VariableToPlotMultiMap) {
1420 1417 if (var->ID() == id) {
1421 1418 impl->updateData(plotables, var, this->graphRange());
1422 1419 }
1423 1420 }
1424 1421 this->impl->rescaleY();
1425 1422 }
1426 1423
1427 1424 void VisualizationGraphWidget::variableDeleted(const std::shared_ptr<Variable> & variable)
1428 1425 {
1429 1426 this->removeVariable(variable);
1430 1427 }
@@ -1,675 +1,677
1 1 #include "Visualization/VisualizationZoneWidget.h"
2 2
3 3 #include "Visualization/IVisualizationWidgetVisitor.h"
4 4 #include "Visualization/QCustomPlotSynchronizer.h"
5 5 #include "Visualization/VisualizationGraphWidget.h"
6 6 #include "Visualization/VisualizationWidget.h"
7 7 #include "ui_VisualizationZoneWidget.h"
8 8
9 9 #include "Common/MimeTypesDef.h"
10 10 #include "Common/VisualizationDef.h"
11 11
12 12 #include <Data/DateTimeRange.h>
13 13 #include <Data/DateTimeRangeHelper.h>
14 14 #include <DataSource/DataSourceController.h>
15 15 #include <Time/TimeController.h>
16 16 #include <Variable/Variable.h>
17 17 #include <Variable/VariableController2.h>
18 18
19 19 #include <Visualization/operations/FindVariableOperation.h>
20 20
21 21 #include <DragAndDrop/DragDropGuiController.h>
22 22 #include <QUuid>
23 23 #include <SqpApplication.h>
24 24 #include <cmath>
25 25
26 26 #include <QLayout>
27 27 #include <QStyle>
28 28
29 29 Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget")
30 30
31 31 namespace {
32 32
33 33 /**
34 34 * Applies a function to all graphs of the zone represented by its layout
35 35 * @param layout the layout that contains graphs
36 36 * @param fun the function to apply to each graph
37 37 */
38 38 template <typename Fun>
39 39 void processGraphs(QLayout &layout, Fun fun)
40 40 {
41 41 for (auto i = 0; i < layout.count(); ++i) {
42 42 if (auto item = layout.itemAt(i)) {
43 43 if (auto visualizationGraphWidget
44 44 = qobject_cast<VisualizationGraphWidget *>(item->widget())) {
45 45 fun(*visualizationGraphWidget);
46 46 }
47 47 }
48 48 }
49 49 }
50 50
51 51 /// Generates a default name for a new graph, according to the number of graphs already displayed in
52 52 /// the zone
53 53 QString defaultGraphName(QLayout &layout)
54 54 {
55 55 QSet<QString> existingNames;
56 56 processGraphs(
57 57 layout, [&existingNames](auto &graphWidget) { existingNames.insert(graphWidget.name()); });
58 58
59 59 int zoneNum = 1;
60 60 QString name;
61 61 do {
62 62 name = QObject::tr("Graph ").append(QString::number(zoneNum));
63 63 ++zoneNum;
64 64 } while (existingNames.contains(name));
65 65
66 66 return name;
67 67 }
68 68
69 69 } // namespace
70 70
71 71 struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate {
72 72
73 73 explicit VisualizationZoneWidgetPrivate()
74 74 : m_SynchronisationGroupId{QUuid::createUuid()},
75 75 m_Synchronizer{std::make_unique<QCustomPlotSynchronizer>()}
76 76 {
77 77 }
78 78 QUuid m_SynchronisationGroupId;
79 79 std::unique_ptr<IGraphSynchronizer> m_Synchronizer;
80 80
81 81 void dropGraph(int index, VisualizationZoneWidget *zoneWidget);
82 82 void dropVariables(const std::vector<std::shared_ptr<Variable> > &variables, int index,
83 83 VisualizationZoneWidget *zoneWidget);
84 84 void dropProducts(const QVariantList &productsData, int index,
85 85 VisualizationZoneWidget *zoneWidget);
86 86 };
87 87
88 88 VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent)
89 89 : VisualizationDragWidget{parent},
90 90 ui{new Ui::VisualizationZoneWidget},
91 91 impl{spimpl::make_unique_impl<VisualizationZoneWidgetPrivate>()}
92 92 {
93 93 ui->setupUi(this);
94 94
95 95 ui->zoneNameLabel->setText(name);
96 96
97 97 ui->dragDropContainer->setPlaceHolderType(DragDropGuiController::PlaceHolderType::Graph);
98 98 ui->dragDropContainer->setMimeType(MIME_TYPE_GRAPH,
99 99 VisualizationDragDropContainer::DropBehavior::Inserted);
100 100 ui->dragDropContainer->setMimeType(
101 101 MIME_TYPE_VARIABLE_LIST, VisualizationDragDropContainer::DropBehavior::InsertedAndMerged);
102 102 ui->dragDropContainer->setMimeType(
103 103 MIME_TYPE_PRODUCT_LIST, VisualizationDragDropContainer::DropBehavior::InsertedAndMerged);
104 104 ui->dragDropContainer->setMimeType(MIME_TYPE_TIME_RANGE,
105 105 VisualizationDragDropContainer::DropBehavior::Merged);
106 106 ui->dragDropContainer->setMimeType(MIME_TYPE_ZONE,
107 107 VisualizationDragDropContainer::DropBehavior::Forbidden);
108 108 ui->dragDropContainer->setMimeType(MIME_TYPE_SELECTION_ZONE,
109 109 VisualizationDragDropContainer::DropBehavior::Forbidden);
110 110 ui->dragDropContainer->setAcceptMimeDataFunction([this](auto mimeData) {
111 111 return sqpApp->dragDropGuiController().checkMimeDataForVisualization(mimeData,
112 112 ui->dragDropContainer);
113 113 });
114 114
115 115 auto acceptDragWidgetFun = [](auto dragWidget, auto mimeData) {
116 116 if (!mimeData) {
117 117 return false;
118 118 }
119 119
120 120 if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
121 121 auto variables = sqpApp->variableController().variables(
122 122 Variable::variablesIDs(mimeData->data(MIME_TYPE_VARIABLE_LIST)));
123 123
124 124 if (variables.size() != 1) {
125 125 return false;
126 126 }
127 127 auto variable = variables.front();
128 128
129 129 if (auto graphWidget = dynamic_cast<const VisualizationGraphWidget *>(dragWidget)) {
130 130 return graphWidget->canDrop(*variable);
131 131 }
132 132 }
133 133
134 134 return true;
135 135 };
136 136 ui->dragDropContainer->setAcceptDragWidgetFunction(acceptDragWidgetFun);
137 137
138 138 connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccuredInContainer, this,
139 139 &VisualizationZoneWidget::dropMimeData);
140 140 connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccuredOnWidget, this,
141 141 &VisualizationZoneWidget::dropMimeDataOnGraph);
142 142
143 143 // 'Close' options : widget is deleted when closed
144 144 setAttribute(Qt::WA_DeleteOnClose);
145 145 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationZoneWidget::close);
146 146 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
147 147
148 148 // Synchronisation id
149 149 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronizationGroupId",
150 150 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
151 151 }
152 152
153 153 VisualizationZoneWidget::~VisualizationZoneWidget()
154 154 {
155 155 delete ui;
156 156 }
157 157
158 158 void VisualizationZoneWidget::setZoneRange(const DateTimeRange &range)
159 159 {
160 160 if (auto graph = firstGraph()) {
161 161 graph->setGraphRange(range);
162 162 }
163 163 else {
164 164 qCWarning(LOG_VisualizationZoneWidget())
165 165 << tr("setZoneRange:Cannot set the range of an empty zone.");
166 166 }
167 167 }
168 168
169 169 void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget)
170 170 {
171 171 // Synchronize new graph with others in the zone
172 172 // impl->m_Synchronizer->addGraph(*graphWidget);
173 173
174 174 // ui->dragDropContainer->addDragWidget(graphWidget);
175 175 insertGraph(0,graphWidget);
176 176
177 177 }
178 178
179 179 void VisualizationZoneWidget::insertGraph(int index, VisualizationGraphWidget *graphWidget)
180 180 {
181 181 DEPRECATE(
182 182 auto layout = ui->dragDropContainer->layout();
183 183 for(int i=0;i<layout->count();i++)
184 184 {
185 185 auto graph = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(i)->widget());
186 connect(graphWidget, &VisualizationGraphWidget::zoom_sig, graph, &VisualizationGraphWidget::zoom);
187 connect(graphWidget, &VisualizationGraphWidget::transform_sig, graph, &VisualizationGraphWidget::transform);
188
189 connect(graphWidget, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move_sig),
190 graph, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move));
191 connect(graphWidget, qOverload<double,double,bool>(&VisualizationGraphWidget::move_sig),
192 graph, qOverload<double,double,bool>(&VisualizationGraphWidget::move));
193
194 connect(graph, &VisualizationGraphWidget::zoom_sig, graphWidget, &VisualizationGraphWidget::zoom);
195 connect(graph, &VisualizationGraphWidget::transform_sig, graphWidget, &VisualizationGraphWidget::transform);
196
197 connect(graph, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move_sig),
198 graphWidget, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move));
199 connect(graph, qOverload<double,double,bool>(&VisualizationGraphWidget::move_sig),
200 graphWidget, qOverload<double,double,bool>(&VisualizationGraphWidget::move));
186 connect(graphWidget, &VisualizationGraphWidget::setrange_sig, graph, &VisualizationGraphWidget::setGraphRange);
187 connect(graph, &VisualizationGraphWidget::setrange_sig, graphWidget, &VisualizationGraphWidget::setGraphRange);
188 // connect(graphWidget, &VisualizationGraphWidget::zoom_sig, graph, &VisualizationGraphWidget::zoom);
189 // connect(graphWidget, &VisualizationGraphWidget::transform_sig, graph, &VisualizationGraphWidget::transform);
190
191 // connect(graphWidget, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move_sig),
192 // graph, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move));
193 // connect(graphWidget, qOverload<double,double,bool>(&VisualizationGraphWidget::move_sig),
194 // graph, qOverload<double,double,bool>(&VisualizationGraphWidget::move));
195
196 // connect(graph, &VisualizationGraphWidget::zoom_sig, graphWidget, &VisualizationGraphWidget::zoom);
197 // connect(graph, &VisualizationGraphWidget::transform_sig, graphWidget, &VisualizationGraphWidget::transform);
198
199 // connect(graph, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move_sig),
200 // graphWidget, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move));
201 // connect(graph, qOverload<double,double,bool>(&VisualizationGraphWidget::move_sig),
202 // graphWidget, qOverload<double,double,bool>(&VisualizationGraphWidget::move));
201 203 }
202 204 if(auto graph = firstGraph())
203 205 {
204 206 graphWidget->setGraphRange(graph->graphRange(), true);
205 207 }
206 208 )
207 209
208 210 // Synchronize new graph with others in the zone
209 211 impl->m_Synchronizer->addGraph(*graphWidget);
210 212
211 213 ui->dragDropContainer->insertDragWidget(index, graphWidget);
212 214
213 215 }
214 216
215 217 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable)
216 218 {
217 219 return createGraph(variable, -1);
218 220 }
219 221
220 222 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable,
221 223 int index)
222 224 {
223 225 auto graphWidget
224 226 = new VisualizationGraphWidget{defaultGraphName(*ui->dragDropContainer->layout()), this};
225 227
226 228
227 229 // Set graph properties
228 230 graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
229 231 graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT);
230 232
231 233
232 234 // Lambda to synchronize zone widget
233 235 auto synchronizeZoneWidget = [this, graphWidget](const DateTimeRange &graphRange,
234 236 const DateTimeRange &oldGraphRange) {
235 237
236 238 auto zoomType = DateTimeRangeHelper::getTransformationType(oldGraphRange, graphRange);
237 239 auto frameLayout = ui->dragDropContainer->layout();
238 240 for (auto i = 0; i < frameLayout->count(); ++i) {
239 241 auto graphChild
240 242 = dynamic_cast<VisualizationGraphWidget *>(frameLayout->itemAt(i)->widget());
241 243 if (graphChild && (graphChild != graphWidget)) {
242 244
243 245 auto graphChildRange = graphChild->graphRange();
244 246 switch (zoomType) {
245 247 case TransformationType::ZoomIn: {
246 248 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
247 249 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
248 250 graphChildRange.m_TStart += deltaLeft;
249 251 graphChildRange.m_TEnd -= deltaRight;
250 252 break;
251 253 }
252 254
253 255 case TransformationType::ZoomOut: {
254 256 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
255 257 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
256 258 graphChildRange.m_TStart -= deltaLeft;
257 259 graphChildRange.m_TEnd += deltaRight;
258 260 break;
259 261 }
260 262 case TransformationType::PanRight: {
261 263 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
262 264 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
263 265 graphChildRange.m_TStart += deltaLeft;
264 266 graphChildRange.m_TEnd += deltaRight;
265 267 break;
266 268 }
267 269 case TransformationType::PanLeft: {
268 270 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
269 271 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
270 272 graphChildRange.m_TStart -= deltaLeft;
271 273 graphChildRange.m_TEnd -= deltaRight;
272 274 break;
273 275 }
274 276 case TransformationType::Unknown: {
275 277 break;
276 278 }
277 279 default:
278 280 qCCritical(LOG_VisualizationZoneWidget())
279 281 << tr("Impossible to synchronize: zoom type not take into account");
280 282 // No action
281 283 break;
282 284 }
283 285 graphChild->setFlags(GraphFlag::DisableAll);
284 286 graphChild->setGraphRange(graphChildRange);
285 287 graphChild->setFlags(GraphFlag::EnableAll);
286 288 }
287 289 }
288 290 };
289 291
290 292 // connection for synchronization
291 293 connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget);
292 294 connect(graphWidget, &VisualizationGraphWidget::variableAdded, this,
293 295 &VisualizationZoneWidget::onVariableAdded);
294 296 connect(graphWidget, &VisualizationGraphWidget::variableAboutToBeRemoved, this,
295 297 &VisualizationZoneWidget::onVariableAboutToBeRemoved);
296 298
297 299 auto range = DateTimeRange{};
298 300 if (auto firstGraph = this->firstGraph()) {
299 301 // Case of a new graph in a existant zone
300 302 range = firstGraph->graphRange();
301 303 }
302 304 else {
303 305 // Case of a new graph as the first of the zone
304 306 range = variable->range();
305 307 }
306 308
307 309 this->insertGraph(index, graphWidget);
308 310
309 311 graphWidget->addVariable(variable, range);
310 312 graphWidget->setYRange(variable);
311 313
312 314 return graphWidget;
313 315 }
314 316
315 317 VisualizationGraphWidget *
316 318 VisualizationZoneWidget::createGraph(const std::vector<std::shared_ptr<Variable> > variables, int index)
317 319 {
318 320 if (variables.empty()) {
319 321 return nullptr;
320 322 }
321 323
322 324 auto graphWidget = createGraph(variables.front(), index);
323 325 for (auto variableIt = variables.cbegin() + 1; variableIt != variables.cend(); ++variableIt) {
324 326 graphWidget->addVariable(*variableIt, graphWidget->graphRange());
325 327 }
326 328
327 329 return graphWidget;
328 330 }
329 331
330 332 VisualizationGraphWidget *VisualizationZoneWidget::firstGraph() const
331 333 {
332 334 VisualizationGraphWidget *firstGraph = nullptr;
333 335 auto layout = ui->dragDropContainer->layout();
334 336 if (layout->count() > 0) {
335 337 if (auto visualizationGraphWidget
336 338 = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
337 339 firstGraph = visualizationGraphWidget;
338 340 }
339 341 }
340 342
341 343 return firstGraph;
342 344 }
343 345
344 346 void VisualizationZoneWidget::closeAllGraphs()
345 347 {
346 348 processGraphs(*ui->dragDropContainer->layout(),
347 349 [](VisualizationGraphWidget &graphWidget) { graphWidget.close(); });
348 350 }
349 351
350 352 void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor)
351 353 {
352 354 if (visitor) {
353 355 visitor->visitEnter(this);
354 356
355 357 // Apply visitor to graph children: widgets different from graphs are not visited (no
356 358 // action)
357 359 processGraphs(
358 360 *ui->dragDropContainer->layout(),
359 361 [visitor](VisualizationGraphWidget &graphWidget) { graphWidget.accept(visitor); });
360 362
361 363 visitor->visitLeave(this);
362 364 }
363 365 else {
364 366 qCCritical(LOG_VisualizationZoneWidget()) << tr("Can't visit widget : the visitor is null");
365 367 }
366 368 }
367 369
368 370 bool VisualizationZoneWidget::canDrop(const Variable &variable) const
369 371 {
370 372 // A tab can always accomodate a variable
371 373 Q_UNUSED(variable);
372 374 return true;
373 375 }
374 376
375 377 bool VisualizationZoneWidget::contains(const Variable &variable) const
376 378 {
377 379 Q_UNUSED(variable);
378 380 return false;
379 381 }
380 382
381 383 QString VisualizationZoneWidget::name() const
382 384 {
383 385 return ui->zoneNameLabel->text();
384 386 }
385 387
386 388 QMimeData *VisualizationZoneWidget::mimeData(const QPoint &position) const
387 389 {
388 390 Q_UNUSED(position);
389 391
390 392 auto mimeData = new QMimeData;
391 393 mimeData->setData(MIME_TYPE_ZONE, QByteArray{});
392 394
393 395 if (auto firstGraph = this->firstGraph()) {
394 396 auto timeRangeData = TimeController::mimeDataForTimeRange(firstGraph->graphRange());
395 397 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
396 398 }
397 399
398 400 return mimeData;
399 401 }
400 402
401 403 bool VisualizationZoneWidget::isDragAllowed() const
402 404 {
403 405 return true;
404 406 }
405 407
406 408 void VisualizationZoneWidget::notifyMouseMoveInGraph(const QPointF &graphPosition,
407 409 const QPointF &plotPosition,
408 410 VisualizationGraphWidget *graphWidget)
409 411 {
410 412 processGraphs(*ui->dragDropContainer->layout(), [&graphPosition, &plotPosition, &graphWidget](
411 413 VisualizationGraphWidget &processedGraph) {
412 414
413 415 switch (sqpApp->plotsCursorMode()) {
414 416 case SqpApplication::PlotsCursorMode::Vertical:
415 417 processedGraph.removeHorizontalCursor();
416 418 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
417 419 break;
418 420 case SqpApplication::PlotsCursorMode::Temporal:
419 421 processedGraph.addVerticalCursor(plotPosition.x());
420 422 processedGraph.removeHorizontalCursor();
421 423 break;
422 424 case SqpApplication::PlotsCursorMode::Horizontal:
423 425 processedGraph.removeVerticalCursor();
424 426 if (&processedGraph == graphWidget) {
425 427 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
426 428 }
427 429 else {
428 430 processedGraph.removeHorizontalCursor();
429 431 }
430 432 break;
431 433 case SqpApplication::PlotsCursorMode::Cross:
432 434 if (&processedGraph == graphWidget) {
433 435 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
434 436 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
435 437 }
436 438 else {
437 439 processedGraph.removeHorizontalCursor();
438 440 processedGraph.removeVerticalCursor();
439 441 }
440 442 break;
441 443 case SqpApplication::PlotsCursorMode::NoCursor:
442 444 processedGraph.removeHorizontalCursor();
443 445 processedGraph.removeVerticalCursor();
444 446 break;
445 447 }
446 448
447 449
448 450 });
449 451 }
450 452
451 453 void VisualizationZoneWidget::notifyMouseLeaveGraph(VisualizationGraphWidget *graphWidget)
452 454 {
453 455 processGraphs(*ui->dragDropContainer->layout(), [](VisualizationGraphWidget &processedGraph) {
454 456 processedGraph.removeHorizontalCursor();
455 457 processedGraph.removeVerticalCursor();
456 458 });
457 459 }
458 460
459 461 void VisualizationZoneWidget::closeEvent(QCloseEvent *event)
460 462 {
461 463 // Closes graphs in the zone
462 464 processGraphs(*ui->dragDropContainer->layout(),
463 465 [](VisualizationGraphWidget &graphWidget) { graphWidget.close(); });
464 466
465 467 // Delete synchronization group from variable controller
466 468 QMetaObject::invokeMethod(&sqpApp->variableController(), "onRemoveSynchronizationGroupId",
467 469 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
468 470
469 471 QWidget::closeEvent(event);
470 472 }
471 473
472 474 void VisualizationZoneWidget::onVariableAdded(std::shared_ptr<Variable> variable)
473 475 {
474 476 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronized",
475 477 Qt::QueuedConnection, Q_ARG(std::shared_ptr<Variable>, variable),
476 478 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
477 479 }
478 480
479 481 void VisualizationZoneWidget::onVariableAboutToBeRemoved(std::shared_ptr<Variable> variable)
480 482 {
481 483 QMetaObject::invokeMethod(&sqpApp->variableController(), "desynchronize", Qt::QueuedConnection,
482 484 Q_ARG(std::shared_ptr<Variable>, variable),
483 485 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
484 486 }
485 487
486 488 void VisualizationZoneWidget::dropMimeData(int index, const QMimeData *mimeData)
487 489 {
488 490 if (mimeData->hasFormat(MIME_TYPE_GRAPH)) {
489 491 impl->dropGraph(index, this);
490 492 }
491 493 else if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
492 494 auto variables = sqpApp->variableController().variables(
493 495 Variable::variablesIDs(mimeData->data(MIME_TYPE_VARIABLE_LIST)));
494 496 impl->dropVariables(variables, index, this);
495 497 }
496 498 else if (mimeData->hasFormat(MIME_TYPE_PRODUCT_LIST)) {
497 499 auto products = sqpApp->dataSourceController().productsDataForMimeData(
498 500 mimeData->data(MIME_TYPE_PRODUCT_LIST));
499 501 impl->dropProducts(products, index, this);
500 502 }
501 503 else {
502 504 qCWarning(LOG_VisualizationZoneWidget())
503 505 << tr("VisualizationZoneWidget::dropMimeData, unknown MIME data received.");
504 506 }
505 507 }
506 508
507 509 void VisualizationZoneWidget::dropMimeDataOnGraph(VisualizationDragWidget *dragWidget,
508 510 const QMimeData *mimeData)
509 511 {
510 512 auto graphWidget = qobject_cast<VisualizationGraphWidget *>(dragWidget);
511 513 if (!graphWidget) {
512 514 qCWarning(LOG_VisualizationZoneWidget())
513 515 << tr("VisualizationZoneWidget::dropMimeDataOnGraph, dropping in an unknown widget, "
514 516 "drop aborted");
515 517 Q_ASSERT(false);
516 518 return;
517 519 }
518 520
519 521 if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
520 522 auto variables = sqpApp->variableController().variables(
521 523 Variable::variablesIDs(mimeData->data(MIME_TYPE_VARIABLE_LIST)));
522 524 for (const auto &var : variables) {
523 525 graphWidget->addVariable(var, graphWidget->graphRange());
524 526 }
525 527 }
526 528 else if (mimeData->hasFormat(MIME_TYPE_PRODUCT_LIST)) {
527 529 auto products = sqpApp->dataSourceController().productsDataForMimeData(
528 530 mimeData->data(MIME_TYPE_PRODUCT_LIST));
529 531
530 532 auto context = new QObject{this};
531 533 auto range = TimeController::timeRangeForMimeData(mimeData->data(MIME_TYPE_TIME_RANGE));
532 534 // BTW this is really dangerous, this assumes the next created variable will be this one...
533 535 connect(&sqpApp->variableController(), &VariableController2::variableAdded, context,
534 536 [this, graphWidget, context, range](auto variable) {
535 537 if(sqpApp->variableController().isReady(variable))
536 538 {
537 539 graphWidget->addVariable(variable, range);
538 540 delete context;
539 541 }
540 542 else
541 543 {
542 544 // -> this is pure insanity! this is a workaround to make a bad design work
543 545 QObject::connect(variable.get(), &Variable::updated,context,
544 546 [graphWidget, context, range, variable]()
545 547 {
546 548 graphWidget->addVariable(variable, range);
547 549 delete context;
548 550 });
549 551 }
550 552
551 553 },
552 554 Qt::QueuedConnection);
553 555
554 556 auto productData = products.first().toHash();
555 557 QMetaObject::invokeMethod(&sqpApp->dataSourceController(), "requestVariable",
556 558 Qt::QueuedConnection, Q_ARG(QVariantHash, productData));
557 559 }
558 560 else if (mimeData->hasFormat(MIME_TYPE_TIME_RANGE)) {
559 561 auto range = TimeController::timeRangeForMimeData(mimeData->data(MIME_TYPE_TIME_RANGE));
560 562 graphWidget->setGraphRange(range, true, true);
561 563 }
562 564 else {
563 565 qCWarning(LOG_VisualizationZoneWidget())
564 566 << tr("VisualizationZoneWidget::dropMimeDataOnGraph, unknown MIME data received.");
565 567 }
566 568 }
567 569
568 570 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropGraph(
569 571 int index, VisualizationZoneWidget *zoneWidget)
570 572 {
571 573 auto &helper = sqpApp->dragDropGuiController();
572 574
573 575 auto graphWidget = qobject_cast<VisualizationGraphWidget *>(helper.getCurrentDragWidget());
574 576 if (!graphWidget) {
575 577 qCWarning(LOG_VisualizationZoneWidget())
576 578 << tr("VisualizationZoneWidget::dropGraph, drop aborted, the dropped graph is not "
577 579 "found or invalid.");
578 580 Q_ASSERT(false);
579 581 return;
580 582 }
581 583
582 584 auto parentDragDropContainer
583 585 = qobject_cast<VisualizationDragDropContainer *>(graphWidget->parentWidget());
584 586 if (!parentDragDropContainer) {
585 587 qCWarning(LOG_VisualizationZoneWidget())
586 588 << tr("VisualizationZoneWidget::dropGraph, drop aborted, the parent container of "
587 589 "the dropped graph is not found.");
588 590 Q_ASSERT(false);
589 591 return;
590 592 }
591 593
592 594 const auto &variables = graphWidget->variables();
593 595
594 596 if (parentDragDropContainer != zoneWidget->ui->dragDropContainer && !variables.empty()) {
595 597 // The drop didn't occur in the same zone
596 598
597 599 // Abort the requests for the variables (if any)
598 600 // Commented, because it's not sure if it's needed or not
599 601 // for (const auto& var : variables)
600 602 //{
601 603 // sqpApp->variableController().onAbortProgressRequested(var);
602 604 //}
603 605
604 606 auto previousParentZoneWidget = graphWidget->parentZoneWidget();
605 607 auto nbGraph = parentDragDropContainer->countDragWidget();
606 608 if (nbGraph == 1) {
607 609 // This is the only graph in the previous zone, close the zone
608 610 helper.delayedCloseWidget(previousParentZoneWidget);
609 611 }
610 612 else {
611 613 // Close the graph
612 614 helper.delayedCloseWidget(graphWidget);
613 615 }
614 616
615 617 // Creates the new graph in the zone
616 618 auto newGraphWidget = zoneWidget->createGraph(variables, index);
617 619 newGraphWidget->addSelectionZones(graphWidget->selectionZoneRanges());
618 620 }
619 621 else {
620 622 // The drop occurred in the same zone or the graph is empty
621 623 // Simple move of the graph, no variable operation associated
622 624 parentDragDropContainer->layout()->removeWidget(graphWidget);
623 625
624 626 if (variables.empty() && parentDragDropContainer != zoneWidget->ui->dragDropContainer) {
625 627 // The graph is empty and dropped in a different zone.
626 628 // Take the range of the first graph in the zone (if existing).
627 629 auto layout = zoneWidget->ui->dragDropContainer->layout();
628 630 if (layout->count() > 0) {
629 631 if (auto visualizationGraphWidget
630 632 = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
631 633 graphWidget->setGraphRange(visualizationGraphWidget->graphRange());
632 634 }
633 635 }
634 636 }
635 637
636 638 zoneWidget->ui->dragDropContainer->insertDragWidget(index, graphWidget);
637 639 }
638 640 }
639 641
640 642 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropVariables(
641 643 const std::vector<std::shared_ptr<Variable> > &variables, int index,
642 644 VisualizationZoneWidget *zoneWidget)
643 645 {
644 646 // Note: the AcceptMimeDataFunction (set on the drop container) ensure there is a single and
645 647 // compatible variable here
646 648 if (variables.size() > 1) {
647 649 return;
648 650 }
649 651 zoneWidget->createGraph(variables, index);
650 652 }
651 653
652 654 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropProducts(
653 655 const QVariantList &productsData, int index, VisualizationZoneWidget *zoneWidget)
654 656 {
655 657 // Note: the AcceptMimeDataFunction (set on the drop container) ensure there is a single and
656 658 // compatible variable here
657 659 if (productsData.count() != 1) {
658 660 qCWarning(LOG_VisualizationZoneWidget())
659 661 << tr("VisualizationTabWidget::dropProducts, dropping multiple products, operation "
660 662 "aborted.");
661 663 return;
662 664 }
663 665
664 666 auto context = new QObject{zoneWidget};
665 667 connect(&sqpApp->variableController(), &VariableController2::variableAdded, context,
666 668 [this, index, zoneWidget, context](auto variable) {
667 669 zoneWidget->createGraph(variable, index);
668 670 delete context; // removes the connection
669 671 },
670 672 Qt::QueuedConnection);
671 673
672 674 auto productData = productsData.first().toHash();
673 675 QMetaObject::invokeMethod(&sqpApp->dataSourceController(), "requestVariable",
674 676 Qt::QueuedConnection, Q_ARG(QVariantHash, productData));
675 677 }
@@ -1,432 +1,438
1 1 #include "AmdaResultParserHelper.h"
2 2
3 3 #include <Common/DateUtils.h>
4 4 #include <Common/SortUtils.h>
5 5
6 6 #include <Data/DataSeriesUtils.h>
7 7 #include <Data/ScalarSeries.h>
8 8 #include <Data/SpectrogramSeries.h>
9 9 #include <Data/Unit.h>
10 10 #include <Data/VectorSeries.h>
11 11
12 12 #include <QtCore/QDateTime>
13 13 #include <QtCore/QRegularExpression>
14 14
15 15 #include <functional>
16 16
17 17 Q_LOGGING_CATEGORY(LOG_AmdaResultParserHelper, "AmdaResultParserHelper")
18 18
19 19 namespace {
20 20
21 21 // ///////// //
22 22 // Constants //
23 23 // ///////// //
24 24
25 25 /// Separator between values in a result line
26 26 const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")};
27 27
28 28 /// Format for dates in result files
29 29 const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz");
30 30
31 31 // /////// //
32 32 // Methods //
33 33 // /////// //
34 34
35 35 /**
36 36 * Checks that the properties contain a specific unit and that this unit is valid
37 37 * @param properties the properties map in which to search unit
38 38 * @param key the key to search for the unit in the properties
39 39 * @param errorMessage the error message to log in case the unit is invalid
40 40 * @return true if the unit is valid, false it it's invalid or was not found in the properties
41 41 */
42 42 bool checkUnit(const Properties &properties, const QString &key, const QString &errorMessage)
43 43 {
44 44 auto unit = properties.value(key).value<Unit>();
45 45 if (unit.m_Name.isEmpty()) {
46 46 qCWarning(LOG_AmdaResultParserHelper()) << errorMessage;
47 47 return false;
48 48 }
49 49
50 50 return true;
51 51 }
52 52
53 53 QDateTime dateTimeFromString(const QString &stringDate) noexcept
54 54 {
55 55 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
56 56 return QDateTime::fromString(stringDate, Qt::ISODateWithMs);
57 57 #else
58 58 return QDateTime::fromString(stringDate, DATE_FORMAT);
59 59 #endif
60 60 }
61 61
62 62 /// Converts a string date to a double date
63 63 /// @return a double that represents the date in seconds, NaN if the string date can't be converted
64 64 double doubleDate(const QString &stringDate) noexcept
65 65 {
66 66 // Format: yyyy-MM-ddThh:mm:ss.zzz
67 67 auto dateTime = dateTimeFromString(stringDate);
68 68 dateTime.setTimeSpec(Qt::UTC);
69 69 return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime)
70 70 : std::numeric_limits<double>::quiet_NaN();
71 71 }
72 72
73 73 /**
74 74 * Reads a line from the AMDA file and tries to extract a x-axis data and value data from it
75 75 * @param xAxisData the vector in which to store the x-axis data extracted
76 76 * @param valuesData the vector in which to store the value extracted
77 77 * @param line the line to read to extract the property
78 78 * @param valuesIndexes indexes of insertion of read values. For example, if the line contains three
79 79 * columns of values, and valuesIndexes are {2, 0, 1}, the value of the third column will be read
80 80 * and inserted first, then the value of the first column, and finally the value of the second
81 81 * column.
82 82 * @param fillValue value that tags an invalid data. For example, if fillValue is -1 and a read
83 83 * value is -1, then this value is considered as invalid and converted to NaN
84 84 */
85 85 void tryReadResult(std::vector<double> &xAxisData, std::vector<double> &valuesData,
86 86 const QString &line, const std::vector<int> &valuesIndexes,
87 87 double fillValue = std::numeric_limits<double>::quiet_NaN())
88 88 {
89 89 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
90 90
91 91 // Checks that the line contains expected number of values + x-axis value
92 92 if (static_cast<size_t>(lineData.size()) == valuesIndexes.size() + 1) {
93 93 // X : the data is converted from date to double (in secs)
94 94 auto x = doubleDate(lineData.at(0));
95 95
96 96 // Adds result only if x is valid. Then, if value is invalid, it is set to NaN
97 97 if (!std::isnan(x)) {
98 98 xAxisData.push_back(x);
99 99
100 100 // Values
101 101 for (auto valueIndex : valuesIndexes) {
102 102 bool valueOk;
103 103 // we use valueIndex + 1 to skip column 0 (x-axis value)
104 104 auto value = lineData.at(valueIndex + 1).toDouble(&valueOk);
105 105
106 106 if (!valueOk) {
107 107 qCWarning(LOG_AmdaResultParserHelper())
108 108 << QObject::tr(
109 109 "Value from (line %1, column %2) is invalid and will be "
110 110 "converted to NaN")
111 111 .arg(line, valueIndex);
112 112 value = std::numeric_limits<double>::quiet_NaN();
113 113 }
114 114
115 115 // Handles fill value
116 116 if (!std::isnan(fillValue) && !std::isnan(value) && fillValue == value) {
117 117 value = std::numeric_limits<double>::quiet_NaN();
118 118 }
119 119
120 120 valuesData.push_back(value);
121 121 }
122 122 }
123 123 else {
124 124 qCWarning(LOG_AmdaResultParserHelper())
125 125 << QObject::tr("Can't retrieve results from line %1: x is invalid").arg(line);
126 126 }
127 127 }
128 128 else {
129 129 qCWarning(LOG_AmdaResultParserHelper())
130 130 << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line);
131 131 }
132 132 }
133 133
134 134 /**
135 135 * Reads a line from the AMDA file and tries to extract a property from it
136 136 * @param properties the properties map in which to put the property extracted from the line
137 137 * @param key the key to which the property is added in the properties map
138 138 * @param line the line to read to extract the property
139 139 * @param regexes the expected regexes to extract the property. If the line matches one regex, the
140 140 * property is generated
141 141 * @param fun the function used to generate the property
142 142 * @return true if the property could be generated, false if the line does not match the regex, or
143 143 * if a property has already been generated for the key
144 144 */
145 145 template <typename GeneratePropertyFun>
146 146 bool tryReadProperty(Properties &properties, const QString &key, const QString &line,
147 147 const std::vector<QRegularExpression> &regexes, GeneratePropertyFun fun)
148 148 {
149 149 if (properties.contains(key)) {
150 150 return false;
151 151 }
152 152
153 153 // Searches for a match among all possible regexes
154 154 auto hasMatch = false;
155 155 for (auto regexIt = regexes.cbegin(), end = regexes.cend(); regexIt != end && !hasMatch;
156 156 ++regexIt) {
157 157 auto match = regexIt->match(line);
158 158 auto hasMatch = match.hasMatch();
159 159 if (hasMatch) {
160 160 properties.insert(key, fun(match));
161 161 }
162 162 }
163 163
164 164 return hasMatch;
165 165 }
166 166
167 167 /**
168 168 * Reads a line from the AMDA file and tries to extract a data from it. Date is converted to double
169 169 * @sa tryReadProperty()
170 170 */
171 171 bool tryReadDate(Properties &properties, const QString &key, const QString &line,
172 172 const std::vector<QRegularExpression> &regexes, bool timeUnit = false)
173 173 {
174 174 return tryReadProperty(properties, key, line, regexes, [timeUnit](const auto &match) {
175 175 return QVariant::fromValue(doubleDate(match.captured(1)));
176 176 });
177 177 }
178 178
179 179 /**
180 180 * Reads a line from the AMDA file and tries to extract a double from it
181 181 * @sa tryReadProperty()
182 182 */
183 183 bool tryReadDouble(Properties &properties, const QString &key, const QString &line,
184 184 const std::vector<QRegularExpression> &regexes)
185 185 {
186 186 return tryReadProperty(properties, key, line, regexes, [](const auto &match) {
187 187 bool ok;
188 188
189 189 // If the value can't be converted to double, it is set to NaN
190 190 auto doubleValue = match.captured(1).toDouble(&ok);
191 191 if (!ok) {
192 192 doubleValue = std::numeric_limits<double>::quiet_NaN();
193 193 }
194 194
195 195 return QVariant::fromValue(doubleValue);
196 196 });
197 197 }
198 198
199 199 /**
200 200 * Reads a line from the AMDA file and tries to extract a vector of doubles from it
201 201 * @param sep the separator of double values in the line
202 202 * @sa tryReadProperty()
203 203 */
204 204 bool tryReadDoubles(Properties &properties, const QString &key, const QString &line,
205 205 const std::vector<QRegularExpression> &regexes,
206 206 const QString &sep = QStringLiteral(","))
207 207 {
208 208 return tryReadProperty(properties, key, line, regexes, [sep](const auto &match) {
209 209 std::vector<double> doubleValues{};
210 210
211 211 // If the value can't be converted to double, it is set to NaN
212 212 auto values = match.captured(1).split(sep);
213 213 for (auto value : values) {
214 214 bool ok;
215 215
216 216 auto doubleValue = value.toDouble(&ok);
217 217 if (!ok) {
218 218 doubleValue = std::numeric_limits<double>::quiet_NaN();
219 219 }
220 220
221 221 doubleValues.push_back(doubleValue);
222 222 }
223 223
224 224 return QVariant::fromValue(doubleValues);
225 225 });
226 226 }
227 227
228 228 /**
229 229 * Reads a line from the AMDA file and tries to extract a unit from it
230 230 * @sa tryReadProperty()
231 231 */
232 232 bool tryReadUnit(Properties &properties, const QString &key, const QString &line,
233 233 const std::vector<QRegularExpression> &regexes, bool timeUnit = false)
234 234 {
235 235 return tryReadProperty(properties, key, line, regexes, [timeUnit](const auto &match) {
236 236 return QVariant::fromValue(Unit{match.captured(1), timeUnit});
237 237 });
238 238 }
239 239
240 240 } // namespace
241 241
242 242 // ////////////////// //
243 243 // ScalarParserHelper //
244 244 // ////////////////// //
245 245
246 246 bool ScalarParserHelper::checkProperties()
247 247 {
248 return checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY,
249 QObject::tr("The x-axis unit could not be found in the file"));
248 if(auto hasXUnit = checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY, QObject::tr("The x-axis unit could not be found in the file"));!hasXUnit)
249 {
250 m_Properties[X_AXIS_UNIT_PROPERTY] = QVariant::fromValue(Unit{"s",true});
251 }
252 return true;
250 253 }
251 254
252 255 IDataSeries* ScalarParserHelper::createSeries()
253 256 {
254 257 return new ScalarSeries(std::move(m_XAxisData), std::move(m_ValuesData),
255 258 m_Properties.value(X_AXIS_UNIT_PROPERTY).value<Unit>(),
256 259 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>());
257 260 }
258 261
259 262 void ScalarParserHelper::readPropertyLine(const QString &line)
260 263 {
261 264 tryReadUnit(m_Properties, X_AXIS_UNIT_PROPERTY, line,
262 265 {DEFAULT_X_AXIS_UNIT_REGEX, ALTERNATIVE_X_AXIS_UNIT_REGEX}, true);
263 266 }
264 267
265 268 void ScalarParserHelper::readResultLine(const QString &line)
266 269 {
267 270 tryReadResult(m_XAxisData, m_ValuesData, line, valuesIndexes());
268 271 }
269 272
270 273 std::vector<int> ScalarParserHelper::valuesIndexes() const
271 274 {
272 275 // Only one value to read
273 276 static auto result = std::vector<int>{0};
274 277 return result;
275 278 }
276 279
277 280 // /////////////////////// //
278 281 // SpectrogramParserHelper //
279 282 // /////////////////////// //
280 283
281 284 bool SpectrogramParserHelper::checkProperties()
282 285 {
283 286 // Generates y-axis data from bands extracted (take the middle of the intervals)
284 287 auto minBands = m_Properties.value(MIN_BANDS_PROPERTY).value<std::vector<double> >();
285 288 auto maxBands = m_Properties.value(MAX_BANDS_PROPERTY).value<std::vector<double> >();
286 289
287 290 if (minBands.size() < 2 || minBands.size() != maxBands.size()) {
288 291 qCWarning(LOG_AmdaResultParserHelper()) << QObject::tr(
289 292 "Can't generate y-axis data from bands extracted: bands intervals are invalid");
290 293 return false;
291 294 }
292 295
293 296 std::transform(
294 297 minBands.begin(), minBands.end(), maxBands.begin(), std::back_inserter(m_YAxisData),
295 298 [](const auto &minValue, const auto &maxValue) { return (minValue + maxValue) / 2.; });
296 299
297 300 // Generates values indexes, i.e. the order in which each value will be retrieved (in ascending
298 301 // order of the associated bands)
299 302 m_ValuesIndexes = SortUtils::sortPermutation(m_YAxisData, std::less<double>());
300 303
301 304 // Sorts y-axis data accoding to the ascending order
302 305 m_YAxisData = SortUtils::sort(m_YAxisData, 1, m_ValuesIndexes);
303 306
304 307 // Sets fill value
305 308 m_FillValue = m_Properties.value(FILL_VALUE_PROPERTY).value<double>();
306 309
307 310 return true;
308 311 }
309 312
310 313 IDataSeries* SpectrogramParserHelper::createSeries()
311 314 {
312 315 // Before creating the series, we handle its data holes
313 316 handleDataHoles();
314 317
315 318 return new SpectrogramSeries(
316 319 std::move(m_XAxisData), std::move(m_YAxisData), std::move(m_ValuesData),
317 320 Unit{"t", true}, // x-axis unit is always a time unit
318 321 m_Properties.value(Y_AXIS_UNIT_PROPERTY).value<Unit>(),
319 322 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>(),
320 323 m_Properties.value(MIN_SAMPLING_PROPERTY).value<double>());
321 324 }
322 325
323 326 void SpectrogramParserHelper::readPropertyLine(const QString &line)
324 327 {
325 328 // Set of functions to test on the line to generate a property. If a function is valid (i.e. a
326 329 // property has been generated for the line), the line is treated as processed and the other
327 330 // functions are not called
328 331 std::vector<std::function<bool()> > functions{
329 332 // values unit
330 333 [&] {
331 334 return tryReadUnit(m_Properties, VALUES_UNIT_PROPERTY, line,
332 335 {SPECTROGRAM_VALUES_UNIT_REGEX});
333 336 },
334 337 // y-axis unit
335 338 [&] {
336 339 return tryReadUnit(m_Properties, Y_AXIS_UNIT_PROPERTY, line,
337 340 {SPECTROGRAM_Y_AXIS_UNIT_REGEX});
338 341 },
339 342 // min sampling
340 343 [&] {
341 344 return tryReadDouble(m_Properties, MIN_SAMPLING_PROPERTY, line,
342 345 {SPECTROGRAM_MIN_SAMPLING_REGEX});
343 346 },
344 347 // max sampling
345 348 [&] {
346 349 return tryReadDouble(m_Properties, MAX_SAMPLING_PROPERTY, line,
347 350 {SPECTROGRAM_MAX_SAMPLING_REGEX});
348 351 },
349 352 // fill value
350 353 [&] {
351 354 return tryReadDouble(m_Properties, FILL_VALUE_PROPERTY, line,
352 355 {SPECTROGRAM_FILL_VALUE_REGEX});
353 356 },
354 357 // min bounds of each band
355 358 [&] {
356 359 return tryReadDoubles(m_Properties, MIN_BANDS_PROPERTY, line,
357 360 {SPECTROGRAM_MIN_BANDS_REGEX});
358 361 },
359 362 // max bounds of each band
360 363 [&] {
361 364 return tryReadDoubles(m_Properties, MAX_BANDS_PROPERTY, line,
362 365 {SPECTROGRAM_MAX_BANDS_REGEX});
363 366 },
364 367 // start time of data
365 368 [&] {
366 369 return tryReadDate(m_Properties, START_TIME_PROPERTY, line,
367 370 {SPECTROGRAM_START_TIME_REGEX});
368 371 },
369 372 // end time of data
370 373 [&] {
371 374 return tryReadDate(m_Properties, END_TIME_PROPERTY, line, {SPECTROGRAM_END_TIME_REGEX});
372 375 }};
373 376
374 377 for (auto function : functions) {
375 378 // Stops at the first function that is valid
376 379 if (function()) {
377 380 return;
378 381 }
379 382 }
380 383 }
381 384
382 385 void SpectrogramParserHelper::readResultLine(const QString &line)
383 386 {
384 387 tryReadResult(m_XAxisData, m_ValuesData, line, m_ValuesIndexes, m_FillValue);
385 388 }
386 389
387 390 void SpectrogramParserHelper::handleDataHoles()
388 391 {
389 392 // Fills data holes according to the max resolution found in the AMDA file
390 393 auto resolution = m_Properties.value(MAX_SAMPLING_PROPERTY).value<double>();
391 394 auto fillValue = m_Properties.value(FILL_VALUE_PROPERTY).value<double>();
392 395 auto minBound = m_Properties.value(START_TIME_PROPERTY).value<double>();
393 396 auto maxBound = m_Properties.value(END_TIME_PROPERTY).value<double>();
394 397
395 398 DataSeriesUtils::fillDataHoles(m_XAxisData, m_ValuesData, resolution, fillValue, minBound,
396 399 maxBound);
397 400 }
398 401
399 402 // ////////////////// //
400 403 // VectorParserHelper //
401 404 // ////////////////// //
402 405
403 406 bool VectorParserHelper::checkProperties()
404 407 {
405 return checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY,
406 QObject::tr("The x-axis unit could not be found in the file"));
408 if(auto hasXUnit = checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY, QObject::tr("The x-axis unit could not be found in the file"));!hasXUnit)
409 {
410 m_Properties[X_AXIS_UNIT_PROPERTY] = QVariant::fromValue(Unit{"s",true});
411 }
412 return true;
407 413 }
408 414
409 415 IDataSeries* VectorParserHelper::createSeries()
410 416 {
411 417 return new VectorSeries(std::move(m_XAxisData), std::move(m_ValuesData),
412 418 m_Properties.value(X_AXIS_UNIT_PROPERTY).value<Unit>(),
413 419 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>());
414 420 }
415 421
416 422 void VectorParserHelper::readPropertyLine(const QString &line)
417 423 {
418 424 tryReadUnit(m_Properties, X_AXIS_UNIT_PROPERTY, line,
419 425 {DEFAULT_X_AXIS_UNIT_REGEX, ALTERNATIVE_X_AXIS_UNIT_REGEX}, true);
420 426 }
421 427
422 428 void VectorParserHelper::readResultLine(const QString &line)
423 429 {
424 430 tryReadResult(m_XAxisData, m_ValuesData, line, valuesIndexes());
425 431 }
426 432
427 433 std::vector<int> VectorParserHelper::valuesIndexes() const
428 434 {
429 435 // 3 values to read, in order in the file (x, y, z)
430 436 static auto result = std::vector<int>{0, 1, 2};
431 437 return result;
432 438 }
General Comments 0
You need to be logged in to leave comments. Login now