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

Auto status change to "Under Review"

Approved

Status change > Approved

Approved

Status change > Approved

You need to be logged in to leave comments. Login now