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