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