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