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