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