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