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