##// END OF EJS Templates
Removed Y autoscale on each variable update...
jeandet -
r1385:27de5ac17fca
parent child
Show More
@@ -1,1427 +1,1426
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 553 impl->setRange(range, updateVar);
554 554 if(forward)
555 555 {
556 556 emit this->setrange_sig(this->graphRange(), true, false);
557 557 }
558 558
559 559 }
560 560
561 561 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
562 562 {
563 563 impl->m_VariableAutoRangeOnInit = value;
564 564 }
565 565
566 566 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
567 567 {
568 568 QVector<DateTimeRange> ranges;
569 569 for (auto zone : impl->m_SelectionZones) {
570 570 ranges << zone->range();
571 571 }
572 572
573 573 return ranges;
574 574 }
575 575
576 576 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange> &ranges)
577 577 {
578 578 for (const auto &range : ranges) {
579 579 // note: ownership is transfered to QCustomPlot
580 580 auto zone = new VisualizationSelectionZoneItem(&plot());
581 581 zone->setRange(range.m_TStart, range.m_TEnd);
582 582 impl->addSelectionZone(zone);
583 583 }
584 584
585 585 plot().replot(QCustomPlot::rpQueuedReplot);
586 586 }
587 587
588 588 VisualizationSelectionZoneItem *
589 589 VisualizationGraphWidget::addSelectionZone(const QString &name, const DateTimeRange &range)
590 590 {
591 591 // note: ownership is transfered to QCustomPlot
592 592 auto zone = new VisualizationSelectionZoneItem(&plot());
593 593 zone->setName(name);
594 594 zone->setRange(range.m_TStart, range.m_TEnd);
595 595 impl->addSelectionZone(zone);
596 596
597 597 plot().replot(QCustomPlot::rpQueuedReplot);
598 598
599 599 return zone;
600 600 }
601 601
602 602 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
603 603 {
604 604 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
605 605
606 606 if (impl->m_HoveredZone == selectionZone) {
607 607 impl->m_HoveredZone = nullptr;
608 608 setCursor(Qt::ArrowCursor);
609 609 }
610 610
611 611 impl->m_SelectionZones.removeAll(selectionZone);
612 612 plot().removeItem(selectionZone);
613 613 plot().replot(QCustomPlot::rpQueuedReplot);
614 614 }
615 615
616 616 void VisualizationGraphWidget::undoZoom()
617 617 {
618 618 auto zoom = impl->m_ZoomStack.pop();
619 619 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
620 620 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
621 621
622 622 axisX->setRange(zoom.first);
623 623 axisY->setRange(zoom.second);
624 624
625 625 plot().replot(QCustomPlot::rpQueuedReplot);
626 626 }
627 627
628 628 void VisualizationGraphWidget::zoom(double factor, int center, Qt::Orientation orientation, bool forward)
629 629 {
630 630 impl->zoom(factor, center, orientation);
631 631 if(forward && orientation==Qt::Horizontal)
632 632 emit this->setrange_sig(this->graphRange(), true, false);
633 633 }
634 634
635 635 void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation, bool forward)
636 636 {
637 637 impl->move(factor, orientation);
638 638 if(forward)
639 639 emit this->setrange_sig(this->graphRange(), true, false);
640 640 }
641 641
642 642 void VisualizationGraphWidget::move(double dx, double dy, bool forward)
643 643 {
644 644 impl->move(dx, dy);
645 645 if(forward)
646 646 emit this->setrange_sig(this->graphRange(), true, false);
647 647 }
648 648
649 649 void VisualizationGraphWidget::transform(const DateTimeRangeTransformation &tranformation, bool forward)
650 650 {
651 651 impl->transform(tranformation);
652 652 if(forward)
653 653 emit this->setrange_sig(this->graphRange(), true, false);
654 654 }
655 655
656 656 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
657 657 {
658 658 if (visitor) {
659 659 visitor->visit(this);
660 660 }
661 661 else {
662 662 qCCritical(LOG_VisualizationGraphWidget())
663 663 << tr("Can't visit widget : the visitor is null");
664 664 }
665 665 }
666 666
667 667 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
668 668 {
669 669 auto isSpectrogram = [](const auto &variable) {
670 670 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
671 671 };
672 672
673 673 // - A spectrogram series can't be dropped on graph with existing plottables
674 674 // - No data series can be dropped on graph with existing spectrogram series
675 675 return isSpectrogram(variable)
676 676 ? impl->m_VariableToPlotMultiMap.empty()
677 677 : std::none_of(
678 678 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
679 679 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
680 680 }
681 681
682 682 bool VisualizationGraphWidget::contains(const Variable &variable) const
683 683 {
684 684 // Finds the variable among the keys of the map
685 685 auto variablePtr = &variable;
686 686 auto findVariable
687 687 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
688 688
689 689 auto end = impl->m_VariableToPlotMultiMap.cend();
690 690 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
691 691 return it != end;
692 692 }
693 693
694 694 QString VisualizationGraphWidget::name() const
695 695 {
696 696 return impl->m_Name;
697 697 }
698 698
699 699 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
700 700 {
701 701 auto mimeData = new QMimeData;
702 702
703 703 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position);
704 704 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
705 705 && selectionZoneItemUnderCursor) {
706 706 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
707 707 selectionZoneItemUnderCursor->range()));
708 708 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
709 709 selectionZoneItemUnderCursor->range()));
710 710 }
711 711 else {
712 712 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
713 713
714 714 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
715 715 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
716 716 }
717 717
718 718 return mimeData;
719 719 }
720 720
721 721 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
722 722 {
723 723 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition);
724 724 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
725 725 && selectionZoneItemUnderCursor) {
726 726
727 727 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
728 728 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
729 729
730 730 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
731 731 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
732 732 .toSize();
733 733
734 734 auto pixmap = QPixmap(zoneSize);
735 735 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
736 736
737 737 return pixmap;
738 738 }
739 739
740 740 return QPixmap();
741 741 }
742 742
743 743 bool VisualizationGraphWidget::isDragAllowed() const
744 744 {
745 745 return true;
746 746 }
747 747
748 748 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
749 749 {
750 750 if (highlighted) {
751 751 plot().setBackground(QBrush(QColor("#BBD5EE")));
752 752 }
753 753 else {
754 754 plot().setBackground(QBrush(Qt::white));
755 755 }
756 756
757 757 plot().update();
758 758 }
759 759
760 760 void VisualizationGraphWidget::addVerticalCursor(double time)
761 761 {
762 762 impl->m_VerticalCursor->setPosition(time);
763 763 impl->m_VerticalCursor->setVisible(true);
764 764
765 765 auto text
766 766 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
767 767 impl->m_VerticalCursor->setLabelText(text);
768 768 }
769 769
770 770 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
771 771 {
772 772 impl->m_VerticalCursor->setAbsolutePosition(position);
773 773 impl->m_VerticalCursor->setVisible(true);
774 774
775 775 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
776 776 auto text
777 777 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
778 778 impl->m_VerticalCursor->setLabelText(text);
779 779 }
780 780
781 781 void VisualizationGraphWidget::removeVerticalCursor()
782 782 {
783 783 impl->m_VerticalCursor->setVisible(false);
784 784 plot().replot(QCustomPlot::rpQueuedReplot);
785 785 }
786 786
787 787 void VisualizationGraphWidget::addHorizontalCursor(double value)
788 788 {
789 789 impl->m_HorizontalCursor->setPosition(value);
790 790 impl->m_HorizontalCursor->setVisible(true);
791 791 impl->m_HorizontalCursor->setLabelText(QString::number(value));
792 792 }
793 793
794 794 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
795 795 {
796 796 impl->m_HorizontalCursor->setAbsolutePosition(position);
797 797 impl->m_HorizontalCursor->setVisible(true);
798 798
799 799 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
800 800 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
801 801 }
802 802
803 803 void VisualizationGraphWidget::removeHorizontalCursor()
804 804 {
805 805 impl->m_HorizontalCursor->setVisible(false);
806 806 plot().replot(QCustomPlot::rpQueuedReplot);
807 807 }
808 808
809 809 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
810 810 {
811 811 Q_UNUSED(event);
812 812
813 813 for (auto i : impl->m_SelectionZones) {
814 814 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
815 815 }
816 816
817 817 // Prevents that all variables will be removed from graph when it will be closed
818 818 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
819 819 emit variableAboutToBeRemoved(variableEntry.first);
820 820 }
821 821 }
822 822
823 823 void VisualizationGraphWidget::enterEvent(QEvent *event)
824 824 {
825 825 Q_UNUSED(event);
826 826 impl->m_RenderingDelegate->showGraphOverlay(true);
827 827 }
828 828
829 829 void VisualizationGraphWidget::leaveEvent(QEvent *event)
830 830 {
831 831 Q_UNUSED(event);
832 832 impl->m_RenderingDelegate->showGraphOverlay(false);
833 833
834 834 if (auto parentZone = parentZoneWidget()) {
835 835 parentZone->notifyMouseLeaveGraph(this);
836 836 }
837 837 else {
838 838 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
839 839 }
840 840
841 841 if (impl->m_HoveredZone) {
842 842 impl->m_HoveredZone->setHovered(false);
843 843 impl->m_HoveredZone = nullptr;
844 844 }
845 845 }
846 846
847 847 void VisualizationGraphWidget::wheelEvent(QWheelEvent *event)
848 848 {
849 849 double factor;
850 850 double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually
851 851 if (event->modifiers() == Qt::ControlModifier) {
852 852 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
853 853 {
854 854 setCursor(Qt::SizeVerCursor);
855 855 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps);
856 856 zoom(factor, event->pos().y(), Qt::Vertical);
857 857 }
858 858 }
859 859 else if (event->modifiers() == Qt::ShiftModifier) {
860 860 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
861 861 {
862 862 setCursor(Qt::SizeHorCursor);
863 863 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps);
864 864 zoom(factor, event->pos().x(), Qt::Horizontal);
865 865 }
866 866 }
867 867 else {
868 868 move(wheelSteps, Qt::Horizontal);
869 869 }
870 870 event->accept();
871 871 }
872 872
873 873
874 874
875 875 void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent *event)
876 876 {
877 877 if(impl->isDrawingZoomRect())
878 878 {
879 879 impl->updateZoomRect(event->pos());
880 880 }
881 881 else if (impl->isDrawingZoneRect())
882 882 {
883 883 impl->updateZoneRect(event->pos());
884 884 }
885 885 else if (event->buttons() == Qt::LeftButton)
886 886 {
887 887 if(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::None)
888 888 {
889 889 auto [dx,dy] = impl->moveGraph(event->pos());
890 890 emit this->setrange_sig(this->graphRange(), true, false);
891 891 }
892 892 else if(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones)
893 893 {
894 894
895 895 }
896 896 }
897 897 else
898 898 {
899 899 impl->m_RenderingDelegate->updateTooltip(event);
900 900 }
901 901 //event->accept();
902 902 QWidget::mouseMoveEvent(event);
903 903 }
904 904
905 905 void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent *event)
906 906 {
907 907 if(impl->isDrawingZoomRect())
908 908 {
909 909 auto oldRange = this->graphRange();
910 910 impl->applyZoomRect();
911 911 auto newRange = this->graphRange();
912 912 if(auto tf = DateTimeRangeHelper::computeTransformation(oldRange,newRange))
913 913 emit this->transform_sig(tf.value(), false);
914 914 }
915 915 else if(impl->isDrawingZoneRect())
916 916 {
917 917 impl->endDrawingZone();
918 918 }
919 919 else
920 920 {
921 921 setCursor(Qt::ArrowCursor);
922 922 }
923 923 event->accept();
924 924 }
925 925
926 926 void VisualizationGraphWidget::mousePressEvent(QMouseEvent *event)
927 927 {
928 928 if (event->button()==Qt::RightButton)
929 929 {
930 930 onGraphMenuRequested(event->pos());
931 931 }
932 932 else
933 933 {
934 934 auto selectedZone = impl->selectionZoneAt(event->pos());
935 935 switch (sqpApp->plotsInteractionMode())
936 936 {
937 937 case SqpApplication::PlotsInteractionMode::DragAndDrop :
938 938 break;
939 939 case SqpApplication::PlotsInteractionMode::SelectionZones :
940 940 impl->setSelectionZonesEditionEnabled(true);
941 941 if ((event->modifiers() == Qt::ControlModifier) && (selectedZone != nullptr))
942 942 {
943 943 selectedZone->setAssociatedEditedZones(parentVisualizationWidget()->selectionZoneManager().selectedItems());
944 944 }
945 945 else
946 946 {
947 947 if (!selectedZone)
948 948 {
949 949 parentVisualizationWidget()->selectionZoneManager().clearSelection();
950 950 impl->startDrawingZone(event->pos());
951 951 }
952 952 else
953 953 {
954 954 parentVisualizationWidget()->selectionZoneManager().select({ selectedZone });
955 955 }
956 956 }
957 957 break;
958 958 case SqpApplication::PlotsInteractionMode::ZoomBox :
959 959 impl->startDrawingRect(event->pos());
960 960 break;
961 961 default:
962 962 if(auto item = impl->m_plot->itemAt(event->pos()))
963 963 {
964 964 emit impl->m_plot->itemClick(item,event);
965 965 if(qobject_cast<VisualizationSelectionZoneItem*>(item))
966 966 {
967 967 setCursor(Qt::ClosedHandCursor);
968 968 impl->enterPlotDrag(event->pos());
969 969 }
970 970 }
971 971 else
972 972 {
973 973 setCursor(Qt::ClosedHandCursor);
974 974 impl->enterPlotDrag(event->pos());
975 975 }
976 976 }
977 977 }
978 978 //event->accept();
979 979 QWidget::mousePressEvent(event);
980 980 }
981 981
982 982 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent *event)
983 983 {
984 984 impl->m_RenderingDelegate->onMouseDoubleClick(event);
985 985 }
986 986
987 987 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent *event)
988 988 {
989 989 switch (event->key()) {
990 990 case Qt::Key_Control:
991 991 event->accept();
992 992 break;
993 993 case Qt::Key_Shift:
994 994 event->accept();
995 995 break;
996 996 default:
997 997 QWidget::keyReleaseEvent(event);
998 998 break;
999 999 }
1000 1000 setCursor(Qt::ArrowCursor);
1001 1001 //event->accept();
1002 1002 }
1003 1003
1004 1004 void VisualizationGraphWidget::keyPressEvent(QKeyEvent *event)
1005 1005 {
1006 1006 switch (event->key()) {
1007 1007 case Qt::Key_Control:
1008 1008 setCursor(Qt::CrossCursor);
1009 1009 break;
1010 1010 case Qt::Key_Shift:
1011 1011 break;
1012 1012 case Qt::Key_M:
1013 1013 impl->rescaleY();
1014 1014 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
1015 1015 break;
1016 1016 case Qt::Key_Left:
1017 1017 if (event->modifiers() != Qt::ControlModifier) {
1018 1018 move(-0.1, Qt::Horizontal);
1019 1019 }
1020 1020 else {
1021 1021 zoom(2, this->width() / 2, Qt::Horizontal);
1022 1022 }
1023 1023 break;
1024 1024 case Qt::Key_Right:
1025 1025 if (event->modifiers() != Qt::ControlModifier) {
1026 1026 move(0.1, Qt::Horizontal);
1027 1027 }
1028 1028 else {
1029 1029 zoom(0.5, this->width() / 2, Qt::Horizontal);
1030 1030 }
1031 1031 break;
1032 1032 case Qt::Key_Up:
1033 1033 if (event->modifiers() != Qt::ControlModifier) {
1034 1034 move(0.1, Qt::Vertical);
1035 1035 }
1036 1036 else {
1037 1037 zoom(0.5, this->height() / 2, Qt::Vertical);
1038 1038 }
1039 1039 break;
1040 1040 case Qt::Key_Down:
1041 1041 if (event->modifiers() != Qt::ControlModifier) {
1042 1042 move(-0.1, Qt::Vertical);
1043 1043 }
1044 1044 else {
1045 1045 zoom(2, this->height() / 2, Qt::Vertical);
1046 1046 }
1047 1047 break;
1048 1048 default:
1049 1049 QWidget::keyPressEvent(event);
1050 1050 break;
1051 1051 }
1052 1052 }
1053 1053
1054 1054 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
1055 1055 {
1056 1056 return *impl->m_plot;
1057 1057 }
1058 1058
1059 1059 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
1060 1060 {
1061 1061 QMenu graphMenu{};
1062 1062
1063 1063 // Iterates on variables (unique keys)
1064 1064 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
1065 1065 end = impl->m_VariableToPlotMultiMap.cend();
1066 1066 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
1067 1067 // 'Remove variable' action
1068 1068 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
1069 1069 [this, var = it->first]() { removeVariable(var); });
1070 1070 }
1071 1071
1072 1072 if (!impl->m_ZoomStack.isEmpty()) {
1073 1073 if (!graphMenu.isEmpty()) {
1074 1074 graphMenu.addSeparator();
1075 1075 }
1076 1076
1077 1077 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
1078 1078 }
1079 1079
1080 1080 // Selection Zone Actions
1081 1081 auto selectionZoneItem = impl->selectionZoneAt(pos);
1082 1082 if (selectionZoneItem) {
1083 1083 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1084 1084 selectedItems.removeAll(selectionZoneItem);
1085 1085 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
1086 1086
1087 1087 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
1088 1088 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
1089 1089 graphMenu.addSeparator();
1090 1090 }
1091 1091
1092 1092 QHash<QString, QMenu *> subMenus;
1093 1093 QHash<QString, bool> subMenusEnabled;
1094 1094 QHash<QString, FilteringAction *> filteredMenu;
1095 1095
1096 1096 for (auto zoneAction : zoneActions) {
1097 1097
1098 1098 auto isEnabled = zoneAction->isEnabled(selectedItems);
1099 1099
1100 1100 auto menu = &graphMenu;
1101 1101 QString menuPath;
1102 1102 for (auto subMenuName : zoneAction->subMenuList()) {
1103 1103 menuPath += '/';
1104 1104 menuPath += subMenuName;
1105 1105
1106 1106 if (!subMenus.contains(menuPath)) {
1107 1107 menu = menu->addMenu(subMenuName);
1108 1108 subMenus[menuPath] = menu;
1109 1109 subMenusEnabled[menuPath] = isEnabled;
1110 1110 }
1111 1111 else {
1112 1112 menu = subMenus.value(menuPath);
1113 1113 if (isEnabled) {
1114 1114 // The sub menu is enabled if at least one of its actions is enabled
1115 1115 subMenusEnabled[menuPath] = true;
1116 1116 }
1117 1117 }
1118 1118 }
1119 1119
1120 1120 FilteringAction *filterAction = nullptr;
1121 1121 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList())) {
1122 1122 filterAction = filteredMenu.value(menuPath);
1123 1123 if (!filterAction) {
1124 1124 filterAction = new FilteringAction{this};
1125 1125 filteredMenu[menuPath] = filterAction;
1126 1126 menu->addAction(filterAction);
1127 1127 }
1128 1128 }
1129 1129
1130 1130 auto action = menu->addAction(zoneAction->name());
1131 1131 action->setEnabled(isEnabled);
1132 1132 action->setShortcut(zoneAction->displayedShortcut());
1133 1133 QObject::connect(action, &QAction::triggered,
1134 1134 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1135 1135
1136 1136 if (filterAction && zoneAction->isFilteringAllowed()) {
1137 1137 filterAction->addActionToFilter(action);
1138 1138 }
1139 1139 }
1140 1140
1141 1141 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
1142 1142 it.value()->setEnabled(subMenusEnabled[it.key()]);
1143 1143 }
1144 1144 }
1145 1145
1146 1146 if (!graphMenu.isEmpty()) {
1147 1147 graphMenu.exec(QCursor::pos());
1148 1148 }
1149 1149 }
1150 1150
1151 1151 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
1152 1152 {
1153 1153 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1154 1154 }
1155 1155
1156 1156 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
1157 1157 {
1158 1158 // Handles plot rendering when mouse is moving
1159 1159 impl->m_RenderingDelegate->updateTooltip(event);
1160 1160
1161 1161 auto axisPos = impl->posToAxisPos(event->pos());
1162 1162
1163 1163 // Zoom box and zone drawing
1164 1164 if (impl->m_DrawingZoomRect) {
1165 1165 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1166 1166 }
1167 1167 else if (impl->m_DrawingZone) {
1168 1168 impl->m_DrawingZone->setEnd(axisPos.x());
1169 1169 }
1170 1170
1171 1171 // Cursor
1172 1172 if (auto parentZone = parentZoneWidget()) {
1173 1173 if (impl->pointIsInAxisRect(axisPos, plot())) {
1174 1174 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1175 1175 }
1176 1176 else {
1177 1177 parentZone->notifyMouseLeaveGraph(this);
1178 1178 }
1179 1179 }
1180 1180
1181 1181 // Search for the selection zone under the mouse
1182 1182 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1183 1183 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1184 1184 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
1185 1185
1186 1186 // Sets the appropriate cursor shape
1187 1187 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1188 1188 setCursor(cursorShape);
1189 1189
1190 1190 // Manages the hovered zone
1191 1191 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
1192 1192 if (impl->m_HoveredZone) {
1193 1193 impl->m_HoveredZone->setHovered(false);
1194 1194 }
1195 1195 selectionZoneItemUnderCursor->setHovered(true);
1196 1196 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1197 1197 plot().replot(QCustomPlot::rpQueuedReplot);
1198 1198 }
1199 1199 }
1200 1200 else {
1201 1201 // There is no zone under the mouse or the interaction mode is not "selection zones"
1202 1202 if (impl->m_HoveredZone) {
1203 1203 impl->m_HoveredZone->setHovered(false);
1204 1204 impl->m_HoveredZone = nullptr;
1205 1205 }
1206 1206
1207 1207 setCursor(Qt::ArrowCursor);
1208 1208 }
1209 1209
1210 1210 impl->m_HasMovedMouse = true;
1211 1211 VisualizationDragWidget::mouseMoveEvent(event);
1212 1212 }
1213 1213
1214 1214 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
1215 1215 {
1216 1216 // Processes event only if the wheel occurs on axis rect
1217 1217 if (!dynamic_cast<QCPAxisRect *>(impl->m_plot->layoutElementAt(event->posF()))) {
1218 1218 return;
1219 1219 }
1220 1220
1221 1221 auto value = event->angleDelta().x() + event->angleDelta().y();
1222 1222 if (value != 0) {
1223 1223
1224 1224 auto direction = value > 0 ? 1.0 : -1.0;
1225 1225 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1226 1226 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1227 1227 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1228 1228
1229 1229 auto zoomOrientations = QFlags<Qt::Orientation>{};
1230 1230 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1231 1231 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1232 1232
1233 1233 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1234 1234
1235 1235 if (!isZoomX && !isZoomY) {
1236 1236 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1237 1237 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1238 1238
1239 1239 axis->setRange(axis->range() + diff);
1240 1240
1241 1241 if (plot().noAntialiasingOnDrag()) {
1242 1242 plot().setNotAntialiasedElements(QCP::aeAll);
1243 1243 }
1244 1244
1245 1245 // plot().replot(QCustomPlot::rpQueuedReplot);
1246 1246 }
1247 1247 }
1248 1248 }
1249 1249
1250 1250 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
1251 1251 {
1252 1252 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1253 1253 auto isSelectionZoneMode
1254 1254 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1255 1255 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1256 1256
1257 1257 if (!isDragDropClick && isLeftClick) {
1258 1258 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
1259 1259 // Starts a zoom box
1260 1260 impl->startDrawingRect(event->pos());
1261 1261 }
1262 1262 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
1263 1263 // Starts a new selection zone
1264 1264 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1265 1265 if (!zoneAtPos) {
1266 1266 impl->startDrawingZone(event->pos());
1267 1267 }
1268 1268 }
1269 1269 }
1270 1270
1271 1271
1272 1272 // Allows zone edition only in selection zone mode without drag&drop
1273 1273 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1274 1274
1275 1275 // Selection / Deselection
1276 1276 if (isSelectionZoneMode) {
1277 1277 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1278 1278 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1279 1279
1280 1280
1281 1281 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1282 1282 && !isMultiSelectionClick) {
1283 1283 parentVisualizationWidget()->selectionZoneManager().select(
1284 1284 {selectionZoneItemUnderCursor});
1285 1285 }
1286 1286 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
1287 1287 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1288 1288 }
1289 1289 else {
1290 1290 // No selection change
1291 1291 }
1292 1292
1293 1293 if (selectionZoneItemUnderCursor && isLeftClick) {
1294 1294 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1295 1295 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1296 1296 }
1297 1297 }
1298 1298
1299 1299
1300 1300 impl->m_HasMovedMouse = false;
1301 1301 VisualizationDragWidget::mousePressEvent(event);
1302 1302 }
1303 1303
1304 1304 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
1305 1305 {
1306 1306 if (impl->m_DrawingZoomRect) {
1307 1307
1308 1308 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1309 1309 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1310 1310
1311 1311 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
1312 1312 impl->m_DrawingZoomRect->bottomRight->coords().x()};
1313 1313
1314 1314 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
1315 1315 impl->m_DrawingZoomRect->bottomRight->coords().y()};
1316 1316
1317 1317 impl->removeDrawingRect();
1318 1318
1319 1319 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1320 1320 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
1321 1321 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1322 1322 axisX->setRange(newAxisXRange);
1323 1323 axisY->setRange(newAxisYRange);
1324 1324
1325 1325 plot().replot(QCustomPlot::rpQueuedReplot);
1326 1326 }
1327 1327 }
1328 1328
1329 1329 impl->endDrawingZone();
1330 1330
1331 1331 // Selection / Deselection
1332 1332 auto isSelectionZoneMode
1333 1333 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1334 1334 if (isSelectionZoneMode) {
1335 1335 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1336 1336 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1337 1337 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1338 1338 && !impl->m_HasMovedMouse) {
1339 1339
1340 1340 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1341 1341 if (zonesUnderCursor.count() > 1) {
1342 1342 // There are multiple zones under the mouse.
1343 1343 // Performs the selection with a selection dialog.
1344 1344 VisualizationMultiZoneSelectionDialog dialog{this};
1345 1345 dialog.setZones(zonesUnderCursor);
1346 1346 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1347 1347 dialog.activateWindow();
1348 1348 dialog.raise();
1349 1349 if (dialog.exec() == QDialog::Accepted) {
1350 1350 auto selection = dialog.selectedZones();
1351 1351
1352 1352 if (!isMultiSelectionClick) {
1353 1353 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1354 1354 }
1355 1355
1356 1356 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
1357 1357 auto zone = it.key();
1358 1358 auto isSelected = it.value();
1359 1359 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
1360 1360 isSelected);
1361 1361
1362 1362 if (isSelected) {
1363 1363 // Puts the zone on top of the stack so it can be moved or resized
1364 1364 impl->moveSelectionZoneOnTop(zone, plot());
1365 1365 }
1366 1366 }
1367 1367 }
1368 1368 }
1369 1369 else {
1370 1370 if (!isMultiSelectionClick) {
1371 1371 parentVisualizationWidget()->selectionZoneManager().select(
1372 1372 {selectionZoneItemUnderCursor});
1373 1373 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1374 1374 }
1375 1375 else {
1376 1376 parentVisualizationWidget()->selectionZoneManager().setSelected(
1377 1377 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
1378 1378 || event->button() == Qt::RightButton);
1379 1379 }
1380 1380 }
1381 1381 }
1382 1382 else {
1383 1383 // No selection change
1384 1384 }
1385 1385 }
1386 1386 }
1387 1387
1388 1388 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1389 1389 {
1390 1390 auto graphRange = impl->m_plot->xAxis->range();
1391 1391 auto dateTime = DateTimeRange{graphRange.lower, graphRange.upper};
1392 1392
1393 1393 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
1394 1394 auto variable = variableEntry.first;
1395 1395 qCDebug(LOG_VisualizationGraphWidget())
1396 1396 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1397 1397 qCDebug(LOG_VisualizationGraphWidget())
1398 1398 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1399 1399 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
1400 1400 impl->updateData(variableEntry.second, variable, variable->range());
1401 1401 }
1402 1402 }
1403 1403 }
1404 1404
1405 1405 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
1406 1406 const DateTimeRange &range)
1407 1407 {
1408 1408 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1409 1409 if (it != impl->m_VariableToPlotMultiMap.end()) {
1410 1410 impl->updateData(it->second, variable, range);
1411 1411 }
1412 1412 }
1413 1413
1414 1414 void VisualizationGraphWidget::variableUpdated(QUuid id)
1415 1415 {
1416 1416 for (auto &[var, plotables] : impl->m_VariableToPlotMultiMap) {
1417 1417 if (var->ID() == id) {
1418 1418 impl->updateData(plotables, var, this->graphRange());
1419 1419 }
1420 1420 }
1421 this->impl->rescaleY();
1422 1421 }
1423 1422
1424 1423 void VisualizationGraphWidget::variableDeleted(const std::shared_ptr<Variable> & variable)
1425 1424 {
1426 1425 this->removeVariable(variable);
1427 1426 }
General Comments 0
You need to be logged in to leave comments. Login now