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