##// END OF EJS Templates
Restored X axis hiding and graph close GUI interactions...
jeandet -
r1382:c306ac9a29de
parent child
Show More
@@ -1,1418 +1,1425
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 setCursor(Qt::ClosedHandCursor);
966 impl->enterPlotDrag(event->pos());
965 if(auto item = impl->m_plot->itemAt(event->pos()))
966 {
967 emit impl->m_plot->itemClick(item,event);
968 }
969 else
970 {
971 setCursor(Qt::ClosedHandCursor);
972 impl->enterPlotDrag(event->pos());
973 }
967 974 }
968 975 }
969 976 //event->accept();
970 977 QWidget::mousePressEvent(event);
971 978 }
972 979
973 980 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent *event)
974 981 {
975 982 impl->m_RenderingDelegate->onMouseDoubleClick(event);
976 983 }
977 984
978 985 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent *event)
979 986 {
980 987 switch (event->key()) {
981 988 case Qt::Key_Control:
982 989 event->accept();
983 990 break;
984 991 case Qt::Key_Shift:
985 992 event->accept();
986 993 break;
987 994 default:
988 995 QWidget::keyReleaseEvent(event);
989 996 break;
990 997 }
991 998 setCursor(Qt::ArrowCursor);
992 999 //event->accept();
993 1000 }
994 1001
995 1002 void VisualizationGraphWidget::keyPressEvent(QKeyEvent *event)
996 1003 {
997 1004 switch (event->key()) {
998 1005 case Qt::Key_Control:
999 1006 setCursor(Qt::CrossCursor);
1000 1007 break;
1001 1008 case Qt::Key_Shift:
1002 1009 break;
1003 1010 case Qt::Key_M:
1004 1011 impl->rescaleY();
1005 1012 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
1006 1013 break;
1007 1014 case Qt::Key_Left:
1008 1015 if (event->modifiers() != Qt::ControlModifier) {
1009 1016 move(-0.1, Qt::Horizontal);
1010 1017 }
1011 1018 else {
1012 1019 zoom(2, this->width() / 2, Qt::Horizontal);
1013 1020 }
1014 1021 break;
1015 1022 case Qt::Key_Right:
1016 1023 if (event->modifiers() != Qt::ControlModifier) {
1017 1024 move(0.1, Qt::Horizontal);
1018 1025 }
1019 1026 else {
1020 1027 zoom(0.5, this->width() / 2, Qt::Horizontal);
1021 1028 }
1022 1029 break;
1023 1030 case Qt::Key_Up:
1024 1031 if (event->modifiers() != Qt::ControlModifier) {
1025 1032 move(0.1, Qt::Vertical);
1026 1033 }
1027 1034 else {
1028 1035 zoom(0.5, this->height() / 2, Qt::Vertical);
1029 1036 }
1030 1037 break;
1031 1038 case Qt::Key_Down:
1032 1039 if (event->modifiers() != Qt::ControlModifier) {
1033 1040 move(-0.1, Qt::Vertical);
1034 1041 }
1035 1042 else {
1036 1043 zoom(2, this->height() / 2, Qt::Vertical);
1037 1044 }
1038 1045 break;
1039 1046 default:
1040 1047 QWidget::keyPressEvent(event);
1041 1048 break;
1042 1049 }
1043 1050 }
1044 1051
1045 1052 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
1046 1053 {
1047 1054 return *impl->m_plot;
1048 1055 }
1049 1056
1050 1057 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
1051 1058 {
1052 1059 QMenu graphMenu{};
1053 1060
1054 1061 // Iterates on variables (unique keys)
1055 1062 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
1056 1063 end = impl->m_VariableToPlotMultiMap.cend();
1057 1064 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
1058 1065 // 'Remove variable' action
1059 1066 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
1060 1067 [this, var = it->first]() { removeVariable(var); });
1061 1068 }
1062 1069
1063 1070 if (!impl->m_ZoomStack.isEmpty()) {
1064 1071 if (!graphMenu.isEmpty()) {
1065 1072 graphMenu.addSeparator();
1066 1073 }
1067 1074
1068 1075 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
1069 1076 }
1070 1077
1071 1078 // Selection Zone Actions
1072 1079 auto selectionZoneItem = impl->selectionZoneAt(pos);
1073 1080 if (selectionZoneItem) {
1074 1081 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1075 1082 selectedItems.removeAll(selectionZoneItem);
1076 1083 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
1077 1084
1078 1085 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
1079 1086 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
1080 1087 graphMenu.addSeparator();
1081 1088 }
1082 1089
1083 1090 QHash<QString, QMenu *> subMenus;
1084 1091 QHash<QString, bool> subMenusEnabled;
1085 1092 QHash<QString, FilteringAction *> filteredMenu;
1086 1093
1087 1094 for (auto zoneAction : zoneActions) {
1088 1095
1089 1096 auto isEnabled = zoneAction->isEnabled(selectedItems);
1090 1097
1091 1098 auto menu = &graphMenu;
1092 1099 QString menuPath;
1093 1100 for (auto subMenuName : zoneAction->subMenuList()) {
1094 1101 menuPath += '/';
1095 1102 menuPath += subMenuName;
1096 1103
1097 1104 if (!subMenus.contains(menuPath)) {
1098 1105 menu = menu->addMenu(subMenuName);
1099 1106 subMenus[menuPath] = menu;
1100 1107 subMenusEnabled[menuPath] = isEnabled;
1101 1108 }
1102 1109 else {
1103 1110 menu = subMenus.value(menuPath);
1104 1111 if (isEnabled) {
1105 1112 // The sub menu is enabled if at least one of its actions is enabled
1106 1113 subMenusEnabled[menuPath] = true;
1107 1114 }
1108 1115 }
1109 1116 }
1110 1117
1111 1118 FilteringAction *filterAction = nullptr;
1112 1119 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList())) {
1113 1120 filterAction = filteredMenu.value(menuPath);
1114 1121 if (!filterAction) {
1115 1122 filterAction = new FilteringAction{this};
1116 1123 filteredMenu[menuPath] = filterAction;
1117 1124 menu->addAction(filterAction);
1118 1125 }
1119 1126 }
1120 1127
1121 1128 auto action = menu->addAction(zoneAction->name());
1122 1129 action->setEnabled(isEnabled);
1123 1130 action->setShortcut(zoneAction->displayedShortcut());
1124 1131 QObject::connect(action, &QAction::triggered,
1125 1132 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1126 1133
1127 1134 if (filterAction && zoneAction->isFilteringAllowed()) {
1128 1135 filterAction->addActionToFilter(action);
1129 1136 }
1130 1137 }
1131 1138
1132 1139 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
1133 1140 it.value()->setEnabled(subMenusEnabled[it.key()]);
1134 1141 }
1135 1142 }
1136 1143
1137 1144 if (!graphMenu.isEmpty()) {
1138 1145 graphMenu.exec(QCursor::pos());
1139 1146 }
1140 1147 }
1141 1148
1142 1149 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
1143 1150 {
1144 1151 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1145 1152 }
1146 1153
1147 1154 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
1148 1155 {
1149 1156 // Handles plot rendering when mouse is moving
1150 1157 impl->m_RenderingDelegate->updateTooltip(event);
1151 1158
1152 1159 auto axisPos = impl->posToAxisPos(event->pos());
1153 1160
1154 1161 // Zoom box and zone drawing
1155 1162 if (impl->m_DrawingZoomRect) {
1156 1163 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1157 1164 }
1158 1165 else if (impl->m_DrawingZone) {
1159 1166 impl->m_DrawingZone->setEnd(axisPos.x());
1160 1167 }
1161 1168
1162 1169 // Cursor
1163 1170 if (auto parentZone = parentZoneWidget()) {
1164 1171 if (impl->pointIsInAxisRect(axisPos, plot())) {
1165 1172 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1166 1173 }
1167 1174 else {
1168 1175 parentZone->notifyMouseLeaveGraph(this);
1169 1176 }
1170 1177 }
1171 1178
1172 1179 // Search for the selection zone under the mouse
1173 1180 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1174 1181 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1175 1182 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
1176 1183
1177 1184 // Sets the appropriate cursor shape
1178 1185 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1179 1186 setCursor(cursorShape);
1180 1187
1181 1188 // Manages the hovered zone
1182 1189 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
1183 1190 if (impl->m_HoveredZone) {
1184 1191 impl->m_HoveredZone->setHovered(false);
1185 1192 }
1186 1193 selectionZoneItemUnderCursor->setHovered(true);
1187 1194 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1188 1195 plot().replot(QCustomPlot::rpQueuedReplot);
1189 1196 }
1190 1197 }
1191 1198 else {
1192 1199 // There is no zone under the mouse or the interaction mode is not "selection zones"
1193 1200 if (impl->m_HoveredZone) {
1194 1201 impl->m_HoveredZone->setHovered(false);
1195 1202 impl->m_HoveredZone = nullptr;
1196 1203 }
1197 1204
1198 1205 setCursor(Qt::ArrowCursor);
1199 1206 }
1200 1207
1201 1208 impl->m_HasMovedMouse = true;
1202 1209 VisualizationDragWidget::mouseMoveEvent(event);
1203 1210 }
1204 1211
1205 1212 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
1206 1213 {
1207 1214 // Processes event only if the wheel occurs on axis rect
1208 1215 if (!dynamic_cast<QCPAxisRect *>(impl->m_plot->layoutElementAt(event->posF()))) {
1209 1216 return;
1210 1217 }
1211 1218
1212 1219 auto value = event->angleDelta().x() + event->angleDelta().y();
1213 1220 if (value != 0) {
1214 1221
1215 1222 auto direction = value > 0 ? 1.0 : -1.0;
1216 1223 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1217 1224 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1218 1225 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1219 1226
1220 1227 auto zoomOrientations = QFlags<Qt::Orientation>{};
1221 1228 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1222 1229 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1223 1230
1224 1231 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1225 1232
1226 1233 if (!isZoomX && !isZoomY) {
1227 1234 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1228 1235 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1229 1236
1230 1237 axis->setRange(axis->range() + diff);
1231 1238
1232 1239 if (plot().noAntialiasingOnDrag()) {
1233 1240 plot().setNotAntialiasedElements(QCP::aeAll);
1234 1241 }
1235 1242
1236 1243 // plot().replot(QCustomPlot::rpQueuedReplot);
1237 1244 }
1238 1245 }
1239 1246 }
1240 1247
1241 1248 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
1242 1249 {
1243 1250 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1244 1251 auto isSelectionZoneMode
1245 1252 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1246 1253 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1247 1254
1248 1255 if (!isDragDropClick && isLeftClick) {
1249 1256 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
1250 1257 // Starts a zoom box
1251 1258 impl->startDrawingRect(event->pos());
1252 1259 }
1253 1260 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
1254 1261 // Starts a new selection zone
1255 1262 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1256 1263 if (!zoneAtPos) {
1257 1264 impl->startDrawingZone(event->pos());
1258 1265 }
1259 1266 }
1260 1267 }
1261 1268
1262 1269
1263 1270 // Allows zone edition only in selection zone mode without drag&drop
1264 1271 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1265 1272
1266 1273 // Selection / Deselection
1267 1274 if (isSelectionZoneMode) {
1268 1275 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1269 1276 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1270 1277
1271 1278
1272 1279 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1273 1280 && !isMultiSelectionClick) {
1274 1281 parentVisualizationWidget()->selectionZoneManager().select(
1275 1282 {selectionZoneItemUnderCursor});
1276 1283 }
1277 1284 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
1278 1285 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1279 1286 }
1280 1287 else {
1281 1288 // No selection change
1282 1289 }
1283 1290
1284 1291 if (selectionZoneItemUnderCursor && isLeftClick) {
1285 1292 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1286 1293 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1287 1294 }
1288 1295 }
1289 1296
1290 1297
1291 1298 impl->m_HasMovedMouse = false;
1292 1299 VisualizationDragWidget::mousePressEvent(event);
1293 1300 }
1294 1301
1295 1302 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
1296 1303 {
1297 1304 if (impl->m_DrawingZoomRect) {
1298 1305
1299 1306 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1300 1307 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1301 1308
1302 1309 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
1303 1310 impl->m_DrawingZoomRect->bottomRight->coords().x()};
1304 1311
1305 1312 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
1306 1313 impl->m_DrawingZoomRect->bottomRight->coords().y()};
1307 1314
1308 1315 impl->removeDrawingRect();
1309 1316
1310 1317 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1311 1318 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
1312 1319 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1313 1320 axisX->setRange(newAxisXRange);
1314 1321 axisY->setRange(newAxisYRange);
1315 1322
1316 1323 plot().replot(QCustomPlot::rpQueuedReplot);
1317 1324 }
1318 1325 }
1319 1326
1320 1327 impl->endDrawingZone();
1321 1328
1322 1329 // Selection / Deselection
1323 1330 auto isSelectionZoneMode
1324 1331 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1325 1332 if (isSelectionZoneMode) {
1326 1333 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1327 1334 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1328 1335 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1329 1336 && !impl->m_HasMovedMouse) {
1330 1337
1331 1338 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1332 1339 if (zonesUnderCursor.count() > 1) {
1333 1340 // There are multiple zones under the mouse.
1334 1341 // Performs the selection with a selection dialog.
1335 1342 VisualizationMultiZoneSelectionDialog dialog{this};
1336 1343 dialog.setZones(zonesUnderCursor);
1337 1344 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1338 1345 dialog.activateWindow();
1339 1346 dialog.raise();
1340 1347 if (dialog.exec() == QDialog::Accepted) {
1341 1348 auto selection = dialog.selectedZones();
1342 1349
1343 1350 if (!isMultiSelectionClick) {
1344 1351 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1345 1352 }
1346 1353
1347 1354 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
1348 1355 auto zone = it.key();
1349 1356 auto isSelected = it.value();
1350 1357 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
1351 1358 isSelected);
1352 1359
1353 1360 if (isSelected) {
1354 1361 // Puts the zone on top of the stack so it can be moved or resized
1355 1362 impl->moveSelectionZoneOnTop(zone, plot());
1356 1363 }
1357 1364 }
1358 1365 }
1359 1366 }
1360 1367 else {
1361 1368 if (!isMultiSelectionClick) {
1362 1369 parentVisualizationWidget()->selectionZoneManager().select(
1363 1370 {selectionZoneItemUnderCursor});
1364 1371 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1365 1372 }
1366 1373 else {
1367 1374 parentVisualizationWidget()->selectionZoneManager().setSelected(
1368 1375 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
1369 1376 || event->button() == Qt::RightButton);
1370 1377 }
1371 1378 }
1372 1379 }
1373 1380 else {
1374 1381 // No selection change
1375 1382 }
1376 1383 }
1377 1384 }
1378 1385
1379 1386 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1380 1387 {
1381 1388 auto graphRange = impl->m_plot->xAxis->range();
1382 1389 auto dateTime = DateTimeRange{graphRange.lower, graphRange.upper};
1383 1390
1384 1391 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
1385 1392 auto variable = variableEntry.first;
1386 1393 qCDebug(LOG_VisualizationGraphWidget())
1387 1394 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1388 1395 qCDebug(LOG_VisualizationGraphWidget())
1389 1396 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1390 1397 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
1391 1398 impl->updateData(variableEntry.second, variable, variable->range());
1392 1399 }
1393 1400 }
1394 1401 }
1395 1402
1396 1403 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
1397 1404 const DateTimeRange &range)
1398 1405 {
1399 1406 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1400 1407 if (it != impl->m_VariableToPlotMultiMap.end()) {
1401 1408 impl->updateData(it->second, variable, range);
1402 1409 }
1403 1410 }
1404 1411
1405 1412 void VisualizationGraphWidget::variableUpdated(QUuid id)
1406 1413 {
1407 1414 for (auto &[var, plotables] : impl->m_VariableToPlotMultiMap) {
1408 1415 if (var->ID() == id) {
1409 1416 impl->updateData(plotables, var, this->graphRange());
1410 1417 }
1411 1418 }
1412 1419 this->impl->rescaleY();
1413 1420 }
1414 1421
1415 1422 void VisualizationGraphWidget::variableDeleted(const std::shared_ptr<Variable> & variable)
1416 1423 {
1417 1424 this->removeVariable(variable);
1418 1425 }
General Comments 0
You need to be logged in to leave comments. Login now