##// END OF EJS Templates
Fix Y range when displaying a uninitialized var in a graph
trabillard -
r1343:3b077ff20668
parent child
Show More
@@ -1,1020 +1,1021
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 <Common/MimeTypesDef.h>
16 16 #include <Data/ArrayData.h>
17 17 #include <Data/IDataSeries.h>
18 18 #include <Data/SpectrogramSeries.h>
19 19 #include <DragAndDrop/DragDropGuiController.h>
20 20 #include <Settings/SqpSettingsDefs.h>
21 21 #include <SqpApplication.h>
22 22 #include <Time/TimeController.h>
23 23 #include <Variable/Variable.h>
24 24 #include <Variable/VariableController.h>
25 25
26 26 #include <unordered_map>
27 27
28 28 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
29 29
30 30 namespace {
31 31
32 32 /// Key pressed to enable drag&drop in all modes
33 33 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
34 34
35 35 /// Key pressed to enable zoom on horizontal axis
36 36 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
37 37
38 38 /// Key pressed to enable zoom on vertical axis
39 39 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
40 40
41 41 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
42 42 const auto PAN_SPEED = 5;
43 43
44 44 /// Key pressed to enable a calibration pan
45 45 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
46 46
47 47 /// Key pressed to enable multi selection of selection zones
48 48 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
49 49
50 50 /// Minimum size for the zoom box, in percentage of the axis range
51 51 const auto ZOOM_BOX_MIN_SIZE = 0.8;
52 52
53 53 /// Format of the dates appearing in the label of a cursor
54 54 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
55 55
56 56 } // namespace
57 57
58 58 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
59 59
60 60 explicit VisualizationGraphWidgetPrivate(const QString &name)
61 61 : m_Name{name},
62 62 m_Flags{GraphFlag::EnableAll},
63 63 m_IsCalibration{false},
64 64 m_RenderingDelegate{nullptr}
65 65 {
66 66 }
67 67
68 68 void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
69 69 const SqpRange &range)
70 70 {
71 71 VisualizationGraphHelper::updateData(plottables, variable, range);
72 72
73 73 // Prevents that data has changed to update rendering
74 74 m_RenderingDelegate->onPlotUpdated();
75 75 }
76 76
77 77 QString m_Name;
78 78 // 1 variable -> n qcpplot
79 79 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
80 80 GraphFlags m_Flags;
81 81 bool m_IsCalibration;
82 82 /// Delegate used to attach rendering features to the plot
83 83 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
84 84
85 85 QCPItemRect *m_DrawingZoomRect = nullptr;
86 86 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
87 87
88 88 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
89 89 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
90 90
91 91 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
92 92 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
93 93 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
94 94
95 95 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
96 96
97 97 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
98 98 {
99 99 removeDrawingRect(plot);
100 100
101 101 auto axisPos = posToAxisPos(pos, plot);
102 102
103 103 m_DrawingZoomRect = new QCPItemRect{&plot};
104 104 QPen p;
105 105 p.setWidth(2);
106 106 m_DrawingZoomRect->setPen(p);
107 107
108 108 m_DrawingZoomRect->topLeft->setCoords(axisPos);
109 109 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
110 110 }
111 111
112 112 void removeDrawingRect(QCustomPlot &plot)
113 113 {
114 114 if (m_DrawingZoomRect) {
115 115 plot.removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
116 116 m_DrawingZoomRect = nullptr;
117 117 plot.replot(QCustomPlot::rpQueuedReplot);
118 118 }
119 119 }
120 120
121 121 void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph)
122 122 {
123 123 endDrawingZone(graph);
124 124
125 125 auto axisPos = posToAxisPos(pos, graph->plot());
126 126
127 127 m_DrawingZone = new VisualizationSelectionZoneItem{&graph->plot()};
128 128 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
129 129 m_DrawingZone->setEditionEnabled(false);
130 130 }
131 131
132 132 void endDrawingZone(VisualizationGraphWidget *graph)
133 133 {
134 134 if (m_DrawingZone) {
135 135 auto drawingZoneRange = m_DrawingZone->range();
136 136 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
137 137 m_DrawingZone->setEditionEnabled(true);
138 138 addSelectionZone(m_DrawingZone);
139 139 }
140 140 else {
141 141 graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot
142 142 }
143 143
144 144 graph->plot().replot(QCustomPlot::rpQueuedReplot);
145 145 m_DrawingZone = nullptr;
146 146 }
147 147 }
148 148
149 149 void setSelectionZonesEditionEnabled(bool value)
150 150 {
151 151 for (auto s : m_SelectionZones) {
152 152 s->setEditionEnabled(value);
153 153 }
154 154 }
155 155
156 156 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
157 157
158 158 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos,
159 159 const QCustomPlot &plot) const
160 160 {
161 161 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
162 162 auto minDistanceToZone = -1;
163 163 for (auto zone : m_SelectionZones) {
164 164 auto distanceToZone = zone->selectTest(pos, false);
165 165 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
166 166 && distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
167 167 selectionZoneItemUnderCursor = zone;
168 168 }
169 169 }
170 170
171 171 return selectionZoneItemUnderCursor;
172 172 }
173 173
174 174 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
175 175 const QCustomPlot &plot) const
176 176 {
177 177 QVector<VisualizationSelectionZoneItem *> zones;
178 178 for (auto zone : m_SelectionZones) {
179 179 auto distanceToZone = zone->selectTest(pos, false);
180 180 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
181 181 zones << zone;
182 182 }
183 183 }
184 184
185 185 return zones;
186 186 }
187 187
188 188 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
189 189 {
190 190 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
191 191 zone->moveToTop();
192 192 m_SelectionZones.removeAll(zone);
193 193 m_SelectionZones.append(zone);
194 194 }
195 195 }
196 196
197 197 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
198 198 {
199 199 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
200 200 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
201 201 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
202 202 }
203 203
204 204 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
205 205 {
206 206 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
207 207 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
208 208 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
209 209 }
210 210 };
211 211
212 212 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
213 213 : VisualizationDragWidget{parent},
214 214 ui{new Ui::VisualizationGraphWidget},
215 215 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
216 216 {
217 217 ui->setupUi(this);
218 218
219 219 // 'Close' options : widget is deleted when closed
220 220 setAttribute(Qt::WA_DeleteOnClose);
221 221
222 222 // Set qcpplot properties :
223 223 // - zoom is enabled
224 224 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
225 225 ui->widget->setInteractions(QCP::iRangeZoom);
226 226 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical);
227 227
228 228 // The delegate must be initialized after the ui as it uses the plot
229 229 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
230 230
231 231 // Init the cursors
232 232 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
233 233 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
234 234 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
235 235 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
236 236
237 237 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
238 238 connect(ui->widget, &QCustomPlot::mouseRelease, this,
239 239 &VisualizationGraphWidget::onMouseRelease);
240 240 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
241 241 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
242 242 connect(ui->widget, &QCustomPlot::mouseDoubleClick, this,
243 243 &VisualizationGraphWidget::onMouseDoubleClick);
244 244 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
245 245 &QCPAxis::rangeChanged),
246 246 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
247 247
248 248 // Activates menu when right clicking on the graph
249 249 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
250 250 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
251 251 &VisualizationGraphWidget::onGraphMenuRequested);
252 252
253 253 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
254 254 &VariableController::onRequestDataLoading);
255 255
256 256 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
257 257 &VisualizationGraphWidget::onUpdateVarDisplaying);
258 258
259 259 #ifdef Q_OS_MAC
260 260 plot().setPlottingHint(QCP::phFastPolylines, true);
261 261 #endif
262 262 }
263 263
264 264
265 265 VisualizationGraphWidget::~VisualizationGraphWidget()
266 266 {
267 267 delete ui;
268 268 }
269 269
270 270 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
271 271 {
272 272 auto parent = parentWidget();
273 273 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
274 274 parent = parent->parentWidget();
275 275 }
276 276
277 277 return qobject_cast<VisualizationZoneWidget *>(parent);
278 278 }
279 279
280 280 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
281 281 {
282 282 auto parent = parentWidget();
283 283 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
284 284 parent = parent->parentWidget();
285 285 }
286 286
287 287 return qobject_cast<VisualizationWidget *>(parent);
288 288 }
289 289
290 290 void VisualizationGraphWidget::setFlags(GraphFlags flags)
291 291 {
292 292 impl->m_Flags = std::move(flags);
293 293 }
294 294
295 295 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
296 296 {
297 297 /// Lambda used to set graph's units and range according to the variable passed in parameter
298 298 auto loadRange = [this](std::shared_ptr<Variable> variable, const SqpRange &range) {
299 299 impl->m_RenderingDelegate->setAxesUnits(*variable);
300 300
301 301 this->setFlags(GraphFlag::DisableAll);
302 302 setGraphRange(range);
303 303 this->setFlags(GraphFlag::EnableAll);
304 304
305 305 emit requestDataLoading({variable}, range, false);
306 306 };
307 307
308 308 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
309 309
310 310 // Calls update of graph's range and units when the data of the variable have been initialized.
311 311 // Note: we use QueuedConnection here as the update event must be called in the UI thread
312 312 connect(variable.get(), &Variable::dataInitialized, this,
313 [ varW = std::weak_ptr<Variable>{variable}, range, loadRange ]() {
313 [ varW = std::weak_ptr<Variable>{variable}, range, loadRange, this ]() {
314 314 if (auto var = varW.lock()) {
315 315 // If the variable is the first added in the graph, we load its range
316 316 auto firstVariableInGraph = range == INVALID_RANGE;
317 317 auto loadedRange = firstVariableInGraph ? var->range() : range;
318 318 loadRange(var, loadedRange);
319 setYRange(var);
319 320 }
320 321 },
321 322 Qt::QueuedConnection);
322 323
323 324 // Uses delegate to create the qcpplot components according to the variable
324 325 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
325 326
326 327 // Sets graph properties
327 328 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
328 329
329 330 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
330 331
331 332 // If the variable already has its data loaded, load its units and its range in the graph
332 333 if (variable->dataSeries() != nullptr) {
333 334 loadRange(variable, range);
334 335 }
335 336
336 337 emit variableAdded(variable);
337 338 }
338 339
339 340 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
340 341 {
341 342 // Each component associated to the variable :
342 343 // - is removed from qcpplot (which deletes it)
343 344 // - is no longer referenced in the map
344 345 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
345 346 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
346 347 emit variableAboutToBeRemoved(variable);
347 348
348 349 auto &plottablesMap = variableIt->second;
349 350
350 351 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
351 352 plottableIt != plottableEnd;) {
352 353 ui->widget->removePlottable(plottableIt->second);
353 354 plottableIt = plottablesMap.erase(plottableIt);
354 355 }
355 356
356 357 impl->m_VariableToPlotMultiMap.erase(variableIt);
357 358 }
358 359
359 360 // Updates graph
360 361 ui->widget->replot();
361 362 }
362 363
363 364 QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
364 365 {
365 366 auto variables = QList<std::shared_ptr<Variable> >{};
366 367 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
367 368 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
368 369 variables << it->first;
369 370 }
370 371
371 372 return variables;
372 373 }
373 374
374 375 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
375 376 {
376 377 if (!variable) {
377 378 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
378 379 return;
379 380 }
380 381
381 382 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
382 383 }
383 384
384 385 SqpRange VisualizationGraphWidget::graphRange() const noexcept
385 386 {
386 387 auto graphRange = ui->widget->xAxis->range();
387 388 return SqpRange{graphRange.lower, graphRange.upper};
388 389 }
389 390
390 391 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
391 392 {
392 393 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
393 394 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
394 395 ui->widget->replot();
395 396 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
396 397 }
397 398
398 399 QVector<SqpRange> VisualizationGraphWidget::selectionZoneRanges() const
399 400 {
400 401 QVector<SqpRange> ranges;
401 402 for (auto zone : impl->m_SelectionZones) {
402 403 ranges << zone->range();
403 404 }
404 405
405 406 return ranges;
406 407 }
407 408
408 409 void VisualizationGraphWidget::addSelectionZones(const QVector<SqpRange> &ranges)
409 410 {
410 411 for (const auto &range : ranges) {
411 412 // note: ownership is transfered to QCustomPlot
412 413 auto zone = new VisualizationSelectionZoneItem(&plot());
413 414 zone->setRange(range.m_TStart, range.m_TEnd);
414 415 impl->addSelectionZone(zone);
415 416 }
416 417
417 418 plot().replot(QCustomPlot::rpQueuedReplot);
418 419 }
419 420
420 421 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
421 422 {
422 423 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
423 424
424 425 if (impl->m_HoveredZone == selectionZone) {
425 426 impl->m_HoveredZone = nullptr;
426 427 setCursor(Qt::ArrowCursor);
427 428 }
428 429
429 430 impl->m_SelectionZones.removeAll(selectionZone);
430 431 plot().removeItem(selectionZone);
431 432 plot().replot(QCustomPlot::rpQueuedReplot);
432 433 }
433 434
434 435 void VisualizationGraphWidget::undoZoom()
435 436 {
436 437 auto zoom = impl->m_ZoomStack.pop();
437 438 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
438 439 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
439 440
440 441 axisX->setRange(zoom.first);
441 442 axisY->setRange(zoom.second);
442 443
443 444 plot().replot(QCustomPlot::rpQueuedReplot);
444 445 }
445 446
446 447 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
447 448 {
448 449 if (visitor) {
449 450 visitor->visit(this);
450 451 }
451 452 else {
452 453 qCCritical(LOG_VisualizationGraphWidget())
453 454 << tr("Can't visit widget : the visitor is null");
454 455 }
455 456 }
456 457
457 458 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
458 459 {
459 460 auto isSpectrogram = [](const auto &variable) {
460 461 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
461 462 };
462 463
463 464 // - A spectrogram series can't be dropped on graph with existing plottables
464 465 // - No data series can be dropped on graph with existing spectrogram series
465 466 return isSpectrogram(variable)
466 467 ? impl->m_VariableToPlotMultiMap.empty()
467 468 : std::none_of(
468 469 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
469 470 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
470 471 }
471 472
472 473 bool VisualizationGraphWidget::contains(const Variable &variable) const
473 474 {
474 475 // Finds the variable among the keys of the map
475 476 auto variablePtr = &variable;
476 477 auto findVariable
477 478 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
478 479
479 480 auto end = impl->m_VariableToPlotMultiMap.cend();
480 481 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
481 482 return it != end;
482 483 }
483 484
484 485 QString VisualizationGraphWidget::name() const
485 486 {
486 487 return impl->m_Name;
487 488 }
488 489
489 490 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
490 491 {
491 492 auto mimeData = new QMimeData;
492 493
493 494 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position, plot());
494 495 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
495 496 && selectionZoneItemUnderCursor) {
496 497 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
497 498 selectionZoneItemUnderCursor->range()));
498 499 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
499 500 selectionZoneItemUnderCursor->range()));
500 501 }
501 502 else {
502 503 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
503 504
504 505 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
505 506 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
506 507 }
507 508
508 509 return mimeData;
509 510 }
510 511
511 512 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
512 513 {
513 514 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition, plot());
514 515 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
515 516 && selectionZoneItemUnderCursor) {
516 517
517 518 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
518 519 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
519 520
520 521 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
521 522 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
522 523 .toSize();
523 524
524 525 auto pixmap = QPixmap(zoneSize);
525 526 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
526 527
527 528 return pixmap;
528 529 }
529 530
530 531 return QPixmap();
531 532 }
532 533
533 534 bool VisualizationGraphWidget::isDragAllowed() const
534 535 {
535 536 return true;
536 537 }
537 538
538 539 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
539 540 {
540 541 if (highlighted) {
541 542 plot().setBackground(QBrush(QColor("#BBD5EE")));
542 543 }
543 544 else {
544 545 plot().setBackground(QBrush(Qt::white));
545 546 }
546 547
547 548 plot().update();
548 549 }
549 550
550 551 void VisualizationGraphWidget::addVerticalCursor(double time)
551 552 {
552 553 impl->m_VerticalCursor->setPosition(time);
553 554 impl->m_VerticalCursor->setVisible(true);
554 555
555 556 auto text
556 557 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
557 558 impl->m_VerticalCursor->setLabelText(text);
558 559 }
559 560
560 561 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
561 562 {
562 563 impl->m_VerticalCursor->setAbsolutePosition(position);
563 564 impl->m_VerticalCursor->setVisible(true);
564 565
565 566 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
566 567 auto text
567 568 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
568 569 impl->m_VerticalCursor->setLabelText(text);
569 570 }
570 571
571 572 void VisualizationGraphWidget::removeVerticalCursor()
572 573 {
573 574 impl->m_VerticalCursor->setVisible(false);
574 575 plot().replot(QCustomPlot::rpQueuedReplot);
575 576 }
576 577
577 578 void VisualizationGraphWidget::addHorizontalCursor(double value)
578 579 {
579 580 impl->m_HorizontalCursor->setPosition(value);
580 581 impl->m_HorizontalCursor->setVisible(true);
581 582 impl->m_HorizontalCursor->setLabelText(QString::number(value));
582 583 }
583 584
584 585 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
585 586 {
586 587 impl->m_HorizontalCursor->setAbsolutePosition(position);
587 588 impl->m_HorizontalCursor->setVisible(true);
588 589
589 590 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
590 591 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
591 592 }
592 593
593 594 void VisualizationGraphWidget::removeHorizontalCursor()
594 595 {
595 596 impl->m_HorizontalCursor->setVisible(false);
596 597 plot().replot(QCustomPlot::rpQueuedReplot);
597 598 }
598 599
599 600 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
600 601 {
601 602 Q_UNUSED(event);
602 603
603 604 // Prevents that all variables will be removed from graph when it will be closed
604 605 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
605 606 emit variableAboutToBeRemoved(variableEntry.first);
606 607 }
607 608 }
608 609
609 610 void VisualizationGraphWidget::enterEvent(QEvent *event)
610 611 {
611 612 Q_UNUSED(event);
612 613 impl->m_RenderingDelegate->showGraphOverlay(true);
613 614 }
614 615
615 616 void VisualizationGraphWidget::leaveEvent(QEvent *event)
616 617 {
617 618 Q_UNUSED(event);
618 619 impl->m_RenderingDelegate->showGraphOverlay(false);
619 620
620 621 if (auto parentZone = parentZoneWidget()) {
621 622 parentZone->notifyMouseLeaveGraph(this);
622 623 }
623 624 else {
624 625 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
625 626 }
626 627
627 628 if (impl->m_HoveredZone) {
628 629 impl->m_HoveredZone->setHovered(false);
629 630 impl->m_HoveredZone = nullptr;
630 631 }
631 632 }
632 633
633 634 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
634 635 {
635 636 return *ui->widget;
636 637 }
637 638
638 639 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
639 640 {
640 641 QMenu graphMenu{};
641 642
642 643 // Iterates on variables (unique keys)
643 644 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
644 645 end = impl->m_VariableToPlotMultiMap.cend();
645 646 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
646 647 // 'Remove variable' action
647 648 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
648 649 [ this, var = it->first ]() { removeVariable(var); });
649 650 }
650 651
651 652 if (!impl->m_ZoomStack.isEmpty()) {
652 653 if (!graphMenu.isEmpty()) {
653 654 graphMenu.addSeparator();
654 655 }
655 656
656 657 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
657 658 }
658 659
659 660 // Selection Zone Actions
660 661 auto selectionZoneItem = impl->selectionZoneAt(pos, plot());
661 662 if (selectionZoneItem) {
662 663 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
663 664 selectedItems.removeAll(selectionZoneItem);
664 665 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
665 666
666 667 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
667 668 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
668 669 graphMenu.addSeparator();
669 670 }
670 671
671 672 QHash<QString, QMenu *> subMenus;
672 673 QHash<QString, bool> subMenusEnabled;
673 674
674 675 for (auto zoneAction : zoneActions) {
675 676
676 677 auto isEnabled = zoneAction->isEnabled(selectedItems);
677 678
678 679 auto menu = &graphMenu;
679 680 for (auto subMenuName : zoneAction->subMenuList()) {
680 681 if (!subMenus.contains(subMenuName)) {
681 682 menu = menu->addMenu(subMenuName);
682 683 subMenus[subMenuName] = menu;
683 684 subMenusEnabled[subMenuName] = isEnabled;
684 685 }
685 686 else {
686 687 menu = subMenus.value(subMenuName);
687 688 if (isEnabled) {
688 689 // The sub menu is enabled if at least one of its actions is enabled
689 690 subMenusEnabled[subMenuName] = true;
690 691 }
691 692 }
692 693 }
693 694
694 695 auto action = menu->addAction(zoneAction->name());
695 696 action->setEnabled(isEnabled);
696 697 action->setShortcut(zoneAction->displayedShortcut());
697 698 QObject::connect(action, &QAction::triggered,
698 699 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
699 700 }
700 701
701 702 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
702 703 it.value()->setEnabled(subMenusEnabled[it.key()]);
703 704 }
704 705 }
705 706
706 707 if (!graphMenu.isEmpty()) {
707 708 graphMenu.exec(QCursor::pos());
708 709 }
709 710 }
710 711
711 712 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
712 713 {
713 714 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
714 715 << QThread::currentThread()->objectName() << "DoAcqui"
715 716 << impl->m_Flags.testFlag(GraphFlag::EnableAcquisition);
716 717
717 718 auto graphRange = SqpRange{t1.lower, t1.upper};
718 719 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
719 720
720 721 if (impl->m_Flags.testFlag(GraphFlag::EnableAcquisition)) {
721 722 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
722 723
723 724 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
724 725 end = impl->m_VariableToPlotMultiMap.end();
725 726 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
726 727 variableUnderGraphVector.push_back(it->first);
727 728 }
728 729 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
729 730 !impl->m_IsCalibration);
730 731 }
731 732
732 733 if (impl->m_Flags.testFlag(GraphFlag::EnableSynchronization) && !impl->m_IsCalibration) {
733 734 qCDebug(LOG_VisualizationGraphWidget())
734 735 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
735 736 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
736 737 emit synchronize(graphRange, oldGraphRange);
737 738 }
738 739
739 740 auto pos = mapFromGlobal(QCursor::pos());
740 741 auto axisPos = impl->posToAxisPos(pos, plot());
741 742 if (auto parentZone = parentZoneWidget()) {
742 743 if (impl->pointIsInAxisRect(axisPos, plot())) {
743 744 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
744 745 }
745 746 else {
746 747 parentZone->notifyMouseLeaveGraph(this);
747 748 }
748 749 }
749 750 else {
750 751 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
751 752 }
752 753
753 754 // Quits calibration
754 755 impl->m_IsCalibration = false;
755 756 }
756 757
757 758 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
758 759 {
759 760 impl->m_RenderingDelegate->onMouseDoubleClick(event);
760 761 }
761 762
762 763 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
763 764 {
764 765 // Handles plot rendering when mouse is moving
765 766 impl->m_RenderingDelegate->onMouseMove(event);
766 767
767 768 auto axisPos = impl->posToAxisPos(event->pos(), plot());
768 769
769 770 // Zoom box and zone drawing
770 771 if (impl->m_DrawingZoomRect) {
771 772 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
772 773 }
773 774 else if (impl->m_DrawingZone) {
774 775 impl->m_DrawingZone->setEnd(axisPos.x());
775 776 }
776 777
777 778 // Cursor
778 779 if (auto parentZone = parentZoneWidget()) {
779 780 if (impl->pointIsInAxisRect(axisPos, plot())) {
780 781 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
781 782 }
782 783 else {
783 784 parentZone->notifyMouseLeaveGraph(this);
784 785 }
785 786 }
786 787 else {
787 788 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
788 789 }
789 790
790 791 // Search for the selection zone under the mouse
791 792 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
792 793 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
793 794 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
794 795
795 796 // Sets the appropriate cursor shape
796 797 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
797 798 setCursor(cursorShape);
798 799
799 800 // Manages the hovered zone
800 801 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
801 802 if (impl->m_HoveredZone) {
802 803 impl->m_HoveredZone->setHovered(false);
803 804 }
804 805 selectionZoneItemUnderCursor->setHovered(true);
805 806 impl->m_HoveredZone = selectionZoneItemUnderCursor;
806 807 plot().replot(QCustomPlot::rpQueuedReplot);
807 808 }
808 809 }
809 810 else {
810 811 // There is no zone under the mouse or the interaction mode is not "selection zones"
811 812 if (impl->m_HoveredZone) {
812 813 impl->m_HoveredZone->setHovered(false);
813 814 impl->m_HoveredZone = nullptr;
814 815 }
815 816
816 817 setCursor(Qt::ArrowCursor);
817 818 }
818 819
819 820 impl->m_HasMovedMouse = true;
820 821 VisualizationDragWidget::mouseMoveEvent(event);
821 822 }
822 823
823 824 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
824 825 {
825 826 auto value = event->angleDelta().x() + event->angleDelta().y();
826 827 if (value != 0) {
827 828
828 829 auto direction = value > 0 ? 1.0 : -1.0;
829 830 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
830 831 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
831 832 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
832 833
833 834 auto zoomOrientations = QFlags<Qt::Orientation>{};
834 835 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
835 836 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
836 837
837 838 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
838 839
839 840 if (!isZoomX && !isZoomY) {
840 841 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
841 842 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
842 843
843 844 axis->setRange(axis->range() + diff);
844 845
845 846 if (plot().noAntialiasingOnDrag()) {
846 847 plot().setNotAntialiasedElements(QCP::aeAll);
847 848 }
848 849
849 850 plot().replot(QCustomPlot::rpQueuedReplot);
850 851 }
851 852 }
852 853 }
853 854
854 855 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
855 856 {
856 857 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
857 858 auto isSelectionZoneMode
858 859 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
859 860 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
860 861
861 862 if (!isDragDropClick && isLeftClick) {
862 863 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
863 864 // Starts a zoom box
864 865 impl->startDrawingRect(event->pos(), plot());
865 866 }
866 867 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
867 868 // Starts a new selection zone
868 869 auto zoneAtPos = impl->selectionZoneAt(event->pos(), plot());
869 870 if (!zoneAtPos) {
870 871 impl->startDrawingZone(event->pos(), this);
871 872 }
872 873 }
873 874 }
874 875
875 876 // Allows mouse panning only in default mode
876 877 plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode()
877 878 == SqpApplication::PlotsInteractionMode::None
878 879 && !isDragDropClick);
879 880
880 881 // Allows zone edition only in selection zone mode without drag&drop
881 882 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
882 883
883 884 // Selection / Deselection
884 885 if (isSelectionZoneMode) {
885 886 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
886 887 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
887 888
888 889
889 890 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
890 891 && !isMultiSelectionClick) {
891 892 parentVisualizationWidget()->selectionZoneManager().select(
892 893 {selectionZoneItemUnderCursor});
893 894 }
894 895 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
895 896 parentVisualizationWidget()->selectionZoneManager().clearSelection();
896 897 }
897 898 else {
898 899 // No selection change
899 900 }
900 901
901 902 if (selectionZoneItemUnderCursor && isLeftClick) {
902 903 selectionZoneItemUnderCursor->setAssociatedEditedZones(
903 904 parentVisualizationWidget()->selectionZoneManager().selectedItems());
904 905 }
905 906 }
906 907
907 908
908 909 impl->m_HasMovedMouse = false;
909 910 VisualizationDragWidget::mousePressEvent(event);
910 911 }
911 912
912 913 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
913 914 {
914 915 if (impl->m_DrawingZoomRect) {
915 916
916 917 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
917 918 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
918 919
919 920 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
920 921 impl->m_DrawingZoomRect->bottomRight->coords().x()};
921 922
922 923 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
923 924 impl->m_DrawingZoomRect->bottomRight->coords().y()};
924 925
925 926 impl->removeDrawingRect(plot());
926 927
927 928 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
928 929 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
929 930 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
930 931 axisX->setRange(newAxisXRange);
931 932 axisY->setRange(newAxisYRange);
932 933
933 934 plot().replot(QCustomPlot::rpQueuedReplot);
934 935 }
935 936 }
936 937
937 938 impl->endDrawingZone(this);
938 939
939 940 // Selection / Deselection
940 941 auto isSelectionZoneMode
941 942 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
942 943 if (isSelectionZoneMode) {
943 944 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
944 945 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
945 946 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
946 947 && !impl->m_HasMovedMouse) {
947 948
948 949 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
949 950 if (zonesUnderCursor.count() > 1) {
950 951 // There are multiple zones under the mouse.
951 952 // Performs the selection with a selection dialog.
952 953 VisualizationMultiZoneSelectionDialog dialog{this};
953 954 dialog.setZones(zonesUnderCursor);
954 955 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
955 956 dialog.activateWindow();
956 957 dialog.raise();
957 958 if (dialog.exec() == QDialog::Accepted) {
958 959 auto selection = dialog.selectedZones();
959 960
960 961 if (!isMultiSelectionClick) {
961 962 parentVisualizationWidget()->selectionZoneManager().clearSelection();
962 963 }
963 964
964 965 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
965 966 auto zone = it.key();
966 967 auto isSelected = it.value();
967 968 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
968 969 isSelected);
969 970
970 971 if (isSelected) {
971 972 // Puts the zone on top of the stack so it can be moved or resized
972 973 impl->moveSelectionZoneOnTop(zone, plot());
973 974 }
974 975 }
975 976 }
976 977 }
977 978 else {
978 979 if (!isMultiSelectionClick) {
979 980 parentVisualizationWidget()->selectionZoneManager().select(
980 981 {selectionZoneItemUnderCursor});
981 982 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
982 983 }
983 984 else {
984 985 parentVisualizationWidget()->selectionZoneManager().setSelected(
985 986 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
986 987 || event->button() == Qt::RightButton);
987 988 }
988 989 }
989 990 }
990 991 else {
991 992 // No selection change
992 993 }
993 994 }
994 995 }
995 996
996 997 void VisualizationGraphWidget::onDataCacheVariableUpdated()
997 998 {
998 999 auto graphRange = ui->widget->xAxis->range();
999 1000 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
1000 1001
1001 1002 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
1002 1003 auto variable = variableEntry.first;
1003 1004 qCDebug(LOG_VisualizationGraphWidget())
1004 1005 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1005 1006 qCDebug(LOG_VisualizationGraphWidget())
1006 1007 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1007 1008 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
1008 1009 impl->updateData(variableEntry.second, variable, variable->range());
1009 1010 }
1010 1011 }
1011 1012 }
1012 1013
1013 1014 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
1014 1015 const SqpRange &range)
1015 1016 {
1016 1017 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1017 1018 if (it != impl->m_VariableToPlotMultiMap.end()) {
1018 1019 impl->updateData(it->second, variable, range);
1019 1020 }
1020 1021 }
General Comments 0
You need to be logged in to leave comments. Login now