##// END OF EJS Templates
Quite fixed D&D from tree......
jeandet -
r1378:1667c88c4819
parent child
Show More
@@ -1,205 +1,209
1 1 #include "Visualization/AxisRenderingUtils.h"
2 2
3 3 #include <Data/ScalarSeries.h>
4 4 #include <Data/SpectrogramSeries.h>
5 5 #include <Data/VectorSeries.h>
6 6
7 7 #include <Variable/Variable.h>
8 8
9 9 #include <Visualization/SqpColorScale.h>
10 10 #include <Visualization/qcustomplot.h>
11 11
12 12 Q_LOGGING_CATEGORY(LOG_AxisRenderingUtils, "AxisRenderingUtils")
13 13
14 14 namespace {
15 15
16 16 /// Format for datetimes on a axis
17 17 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
18 18
19 19 const auto NUMBER_FORMAT = 'g';
20 20 const auto NUMBER_PRECISION = 9;
21 21
22 22 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
23 23 /// non-time data
24 24 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis, QCPAxis::ScaleType scaleType)
25 25 {
26 26 if (isTimeAxis) {
27 27 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
28 28 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
29 29 dateTicker->setDateTimeSpec(Qt::UTC);
30 30
31 31 return dateTicker;
32 32 }
33 33 else if (scaleType == QCPAxis::stLogarithmic) {
34 34 return QSharedPointer<QCPAxisTickerLog>::create();
35 35 }
36 36 else {
37 37 // default ticker
38 38 return QSharedPointer<QCPAxisTicker>::create();
39 39 }
40 40 }
41 41
42 42 /**
43 43 * Sets properties of the axis passed as parameter
44 44 * @param axis the axis to set
45 45 * @param unit the unit to set for the axis
46 46 * @param scaleType the scale type to set for the axis
47 47 */
48 48 void setAxisProperties(QCPAxis &axis, const Unit &unit,
49 49 QCPAxis::ScaleType scaleType = QCPAxis::stLinear)
50 50 {
51 51 // label (unit name)
52 52 axis.setLabel(unit.m_Name);
53 53
54 54 // scale type
55 55 axis.setScaleType(scaleType);
56 56 if (scaleType == QCPAxis::stLogarithmic) {
57 57 // Scientific notation
58 58 axis.setNumberPrecision(0);
59 59 axis.setNumberFormat("eb");
60 60 }
61 61
62 62 // ticker (depending on the type of unit)
63 63 axis.setTicker(axisTicker(unit.m_TimeUnit, scaleType));
64 64 }
65 65
66 66 /**
67 67 * Delegate used to set axes properties
68 68 */
69 69 template <typename T, typename Enabled = void>
70 70 struct AxisSetter {
71 71 static void setProperties(QCustomPlot &, SqpColorScale &)
72 72 {
73 73 // Default implementation does nothing
74 74 qCCritical(LOG_AxisRenderingUtils()) << "Can't set axis properties: unmanaged type of data";
75 75 }
76 76
77 77 static void setUnits(T &, QCustomPlot &, SqpColorScale &)
78 78 {
79 79 // Default implementation does nothing
80 80 qCCritical(LOG_AxisRenderingUtils()) << "Can't set axis units: unmanaged type of data";
81 81 }
82 82 };
83 83
84 84 /**
85 85 * Specialization of AxisSetter for scalars and vectors
86 86 * @sa ScalarSeries
87 87 * @sa VectorSeries
88 88 */
89 89 template <typename T>
90 90 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
91 91 or std::is_base_of<VectorSeries, T>::value> > {
92 92 static void setProperties(QCustomPlot &, SqpColorScale &)
93 93 {
94 94 // Nothing to do
95 95 }
96 96
97 97 static void setUnits(T &dataSeries, QCustomPlot &plot, SqpColorScale &)
98 98 {
99 99 dataSeries.lockRead();
100 100 auto xAxisUnit = dataSeries.xAxisUnit();
101 101 auto valuesUnit = dataSeries.valuesUnit();
102 102 dataSeries.unlock();
103 103
104 setAxisProperties(*plot.xAxis, xAxisUnit);
104 // setAxisProperties(*plot.xAxis, xAxisUnit);
105 // This is cheating but it's ok ;)
106 setAxisProperties(*plot.xAxis, Unit{"s", true});
105 107 setAxisProperties(*plot.yAxis, valuesUnit);
106 108 }
107 109 };
108 110
109 111 /**
110 112 * Specialization of AxisSetter for spectrograms
111 113 * @sa SpectrogramSeries
112 114 */
113 115 template <typename T>
114 116 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
115 117 static void setProperties(QCustomPlot &plot, SqpColorScale &colorScale)
116 118 {
117 119 // Displays color scale in plot
118 120 plot.plotLayout()->insertRow(0);
119 121 plot.plotLayout()->addElement(0, 0, colorScale.m_Scale);
120 122 colorScale.m_Scale->setType(QCPAxis::atTop);
121 123 colorScale.m_Scale->setMinimumMargins(QMargins{0, 0, 0, 0});
122 124
123 125 // Aligns color scale with axes
124 126 auto marginGroups = plot.axisRect()->marginGroups();
125 127 for (auto it = marginGroups.begin(), end = marginGroups.end(); it != end; ++it) {
126 128 colorScale.m_Scale->setMarginGroup(it.key(), it.value());
127 129 }
128 130
129 131 // Set color scale properties
130 132 colorScale.m_AutomaticThreshold = true;
131 133 }
132 134
133 135 static void setUnits(T &dataSeries, QCustomPlot &plot, SqpColorScale &colorScale)
134 136 {
135 137 dataSeries.lockRead();
136 138 auto xAxisUnit = dataSeries.xAxisUnit();
137 139 auto yAxisUnit = dataSeries.yAxisUnit();
138 140 auto valuesUnit = dataSeries.valuesUnit();
139 141 dataSeries.unlock();
140 142
141 setAxisProperties(*plot.xAxis, xAxisUnit);
143 //setAxisProperties(*plot.xAxis, xAxisUnit);
144 // This is cheating but it's ok ;)
145 setAxisProperties(*plot.xAxis, Unit{"s", true});
142 146 setAxisProperties(*plot.yAxis, yAxisUnit, QCPAxis::stLogarithmic);
143 147 setAxisProperties(*colorScale.m_Scale->axis(), valuesUnit, QCPAxis::stLogarithmic);
144 148 }
145 149 };
146 150
147 151 /**
148 152 * Default implementation of IAxisHelper, which takes data series to set axes properties
149 153 * @tparam T the data series' type
150 154 */
151 155 template <typename T>
152 156 struct AxisHelper : public IAxisHelper {
153 157 explicit AxisHelper(std::shared_ptr<T> dataSeries) : m_DataSeries{dataSeries} {}
154 158
155 159 void setProperties(QCustomPlot &plot, SqpColorScale &colorScale) override
156 160 {
157 161 AxisSetter<T>::setProperties(plot, colorScale);
158 162 }
159 163
160 164 void setUnits(QCustomPlot &plot, SqpColorScale &colorScale) override
161 165 {
162 166 if (m_DataSeries) {
163 167 AxisSetter<T>::setUnits(*m_DataSeries, plot, colorScale);
164 168 }
165 169 else {
166 170 qCCritical(LOG_AxisRenderingUtils()) << "Can't set units: inconsistency between the "
167 171 "type of data series and the type supposed";
168 172 }
169 173 }
170 174
171 175 std::shared_ptr<T> m_DataSeries;
172 176 };
173 177
174 178 } // namespace
175 179
176 180 QString formatValue(double value, const QCPAxis &axis)
177 181 {
178 182 // If the axis is a time axis, formats the value as a date
179 183 if (auto axisTicker = qSharedPointerDynamicCast<QCPAxisTickerDateTime>(axis.ticker())) {
180 184 return DateUtils::dateTime(value, axisTicker->dateTimeSpec()).toString(DATETIME_FORMAT);
181 185 }
182 186 else {
183 187 return QString::number(value, NUMBER_FORMAT, NUMBER_PRECISION);
184 188 }
185 189 }
186 190
187 191 std::unique_ptr<IAxisHelper> IAxisHelperFactory::create(const Variable &variable) noexcept
188 192 {
189 193 switch (variable.type()) {
190 194 case DataSeriesType::SCALAR:
191 195 return std::make_unique<AxisHelper<ScalarSeries> >(
192 196 std::dynamic_pointer_cast<ScalarSeries>(variable.dataSeries()));
193 197 case DataSeriesType::SPECTROGRAM:
194 198 return std::make_unique<AxisHelper<SpectrogramSeries> >(
195 199 std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()));
196 200 case DataSeriesType::VECTOR:
197 201 return std::make_unique<AxisHelper<VectorSeries> >(
198 202 std::dynamic_pointer_cast<VectorSeries>(variable.dataSeries()));
199 203 default:
200 204 // Creates default helper
201 205 break;
202 206 }
203 207
204 208 return std::make_unique<AxisHelper<IDataSeries> >(nullptr);
205 209 }
@@ -1,1400 +1,1411
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 <Actions/FilteringAction.h>
16 16 #include <Common/MimeTypesDef.h>
17 17 #include <Data/ArrayData.h>
18 18 #include <Data/IDataSeries.h>
19 19 #include <Data/SpectrogramSeries.h>
20 20 #include <DragAndDrop/DragDropGuiController.h>
21 21 #include <Settings/SqpSettingsDefs.h>
22 22 #include <SqpApplication.h>
23 23 #include <Time/TimeController.h>
24 24 #include <Variable/Variable.h>
25 25 #include <Variable/VariableController2.h>
26 26 #include <Data/DateTimeRangeHelper.h>
27 27
28 28 #include <unordered_map>
29 29
30 30 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
31 31
32 32 namespace {
33 33
34 34 /// Key pressed to enable drag&drop in all modes
35 35 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
36 36
37 37 /// Key pressed to enable zoom on horizontal axis
38 38 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
39 39
40 40 /// Key pressed to enable zoom on vertical axis
41 41 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
42 42
43 43 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
44 44 const auto PAN_SPEED = 5;
45 45
46 46 /// Key pressed to enable a calibration pan
47 47 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
48 48
49 49 /// Key pressed to enable multi selection of selection zones
50 50 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
51 51
52 52 /// Minimum size for the zoom box, in percentage of the axis range
53 53 const auto ZOOM_BOX_MIN_SIZE = 0.8;
54 54
55 55 /// Format of the dates appearing in the label of a cursor
56 56 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
57 57
58 58 } // namespace
59 59
60 60 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
61 61
62 62 explicit VisualizationGraphWidgetPrivate(const QString &name)
63 63 : m_Name{name},
64 64 m_Flags{GraphFlag::EnableAll},
65 65 m_IsCalibration{false},
66 66 m_RenderingDelegate{nullptr}
67 67 {
68 68 m_plot = new QCustomPlot();
69 69 // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable.
70 70 m_plot->setPlottingHint(QCP::phFastPolylines, true);
71 71 }
72 72
73 73 void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
74 74 const DateTimeRange &range)
75 75 {
76 76 VisualizationGraphHelper::updateData(plottables, variable, range);
77 77
78 78 // Prevents that data has changed to update rendering
79 79 m_RenderingDelegate->onPlotUpdated();
80 80 }
81 81
82 82 QString m_Name;
83 83 // 1 variable -> n qcpplot
84 84 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
85 85 GraphFlags m_Flags;
86 86 bool m_IsCalibration;
87 87 QCustomPlot* m_plot;
88 88 QPoint m_lastMousePos;
89 89 QCPRange m_lastXRange;
90 90 QCPRange m_lastYRange;
91 91 /// Delegate used to attach rendering features to the plot
92 92 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
93 93
94 94 QCPItemRect* m_DrawingZoomRect = nullptr;
95 95 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
96 96
97 97 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
98 98 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
99 99
100 100 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
101 101 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
102 102 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
103 103
104 104 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
105 105
106 106 bool m_VariableAutoRangeOnInit = true;
107 107
108 108 inline void enterPlotDrag(const QPoint& position)
109 109 {
110 110 m_lastMousePos = m_plot->mapFromParent(position);
111 111 m_lastXRange = m_plot->xAxis->range();
112 112 m_lastYRange = m_plot->yAxis->range();
113 113
114 114 }
115 115
116 116 inline bool isDrawingZoomRect(){return m_DrawingZoomRect!=nullptr;}
117 117 void updateZoomRect(const QPoint& newPos)
118 118 {
119 119 QPointF pos{m_plot->xAxis->pixelToCoord(newPos.x()), m_plot->yAxis->pixelToCoord(newPos.y())};
120 120 m_DrawingZoomRect->bottomRight->setCoords(pos);
121 121 m_plot->replot(QCustomPlot::rpQueuedReplot);
122 122 }
123 123
124 124 void applyZoomRect()
125 125 {
126 126 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
127 127 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
128 128
129 129 auto newAxisXRange = QCPRange{m_DrawingZoomRect->topLeft->coords().x(),
130 130 m_DrawingZoomRect->bottomRight->coords().x()};
131 131
132 132 auto newAxisYRange = QCPRange{m_DrawingZoomRect->topLeft->coords().y(),
133 133 m_DrawingZoomRect->bottomRight->coords().y()};
134 134
135 135 removeDrawingRect();
136 136
137 137 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
138 138 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
139 139 m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
140 140 axisX->setRange(newAxisXRange);
141 141 axisY->setRange(newAxisYRange);
142 142
143 143 m_plot->replot(QCustomPlot::rpQueuedReplot);
144 144 }
145 145 }
146 146
147 147 inline bool isDrawingZoneRect(){return m_DrawingZone!=nullptr;}
148 148 void updateZoneRect(const QPoint& newPos)
149 149 {
150 150 m_DrawingZone->setEnd(m_plot->xAxis->pixelToCoord(newPos.x()));
151 151 m_plot->replot(QCustomPlot::rpQueuedReplot);
152 152 }
153 153
154 154 void startDrawingRect(const QPoint &pos)
155 155 {
156 156 removeDrawingRect();
157 157
158 158 auto axisPos = posToAxisPos(pos);
159 159
160 160 m_DrawingZoomRect = new QCPItemRect{m_plot};
161 161 QPen p;
162 162 p.setWidth(2);
163 163 m_DrawingZoomRect->setPen(p);
164 164
165 165 m_DrawingZoomRect->topLeft->setCoords(axisPos);
166 166 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
167 167 }
168 168
169 169 void removeDrawingRect()
170 170 {
171 171 if (m_DrawingZoomRect) {
172 172 m_plot->removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
173 173 m_DrawingZoomRect = nullptr;
174 174 m_plot->replot(QCustomPlot::rpQueuedReplot);
175 175 }
176 176 }
177 177
178 178 void selectZone(const QPoint &pos)
179 179 {
180 180 auto zoneAtPos = selectionZoneAt(pos);
181 181 setSelectionZonesEditionEnabled(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones);
182 182 }
183 183
184 184 void startDrawingZone(const QPoint &pos)
185 185 {
186 186 endDrawingZone();
187 187
188 188 auto axisPos = posToAxisPos(pos);
189 189
190 190 m_DrawingZone = new VisualizationSelectionZoneItem{m_plot};
191 191 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
192 192 m_DrawingZone->setEditionEnabled(false);
193 193 }
194 194
195 195 void endDrawingZone()
196 196 {
197 197 if (m_DrawingZone) {
198 198 auto drawingZoneRange = m_DrawingZone->range();
199 199 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
200 200 m_DrawingZone->setEditionEnabled(true);
201 201 addSelectionZone(m_DrawingZone);
202 202 }
203 203 else {
204 204 m_plot->removeItem(m_DrawingZone);
205 205 }
206 206
207 207 m_plot->replot(QCustomPlot::rpQueuedReplot);
208 208 m_DrawingZone = nullptr;
209 209 }
210 210 }
211 211
212 212 void moveSelectionZone(const QPoint& destination)
213 213 {
214 214 /*
215 215 * I give up on this for now
216 216 * @TODO implement this, the difficulty is that selection zones have their own
217 217 * event handling code which seems to rely on QCP GUI event handling propagation
218 218 * which was a realy bad design choice.
219 219 */
220 220 }
221 221
222 222 void setSelectionZonesEditionEnabled(bool value)
223 223 {
224 224 for (auto s : m_SelectionZones) {
225 225 s->setEditionEnabled(value);
226 226 }
227 227 }
228 228
229 229 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
230 230
231 231 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos) const
232 232 {
233 233 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
234 234 auto minDistanceToZone = -1;
235 235 for (auto zone : m_SelectionZones) {
236 236 auto distanceToZone = zone->selectTest(pos, false);
237 237 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
238 238 && distanceToZone >= 0 && distanceToZone < m_plot->selectionTolerance()) {
239 239 selectionZoneItemUnderCursor = zone;
240 240 }
241 241 }
242 242
243 243 return selectionZoneItemUnderCursor;
244 244 }
245 245
246 246 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
247 247 const QCustomPlot &plot) const
248 248 {
249 249 QVector<VisualizationSelectionZoneItem *> zones;
250 250 for (auto zone : m_SelectionZones) {
251 251 auto distanceToZone = zone->selectTest(pos, false);
252 252 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
253 253 zones << zone;
254 254 }
255 255 }
256 256
257 257 return zones;
258 258 }
259 259
260 260 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
261 261 {
262 262 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
263 263 zone->moveToTop();
264 264 m_SelectionZones.removeAll(zone);
265 265 m_SelectionZones.append(zone);
266 266 }
267 267 }
268 268
269 269 QPointF posToAxisPos(const QPoint &pos) const
270 270 {
271 271 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
272 272 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
273 273 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
274 274 }
275 275
276 276 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
277 277 {
278 278 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
279 279 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
280 280 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
281 281 }
282 282
283 283 inline QCPRange _pixDistanceToRange(double pos1, double pos2, QCPAxis *axis)
284 284 {
285 285 if (axis->scaleType() == QCPAxis::stLinear)
286 286 {
287 287 auto diff = axis->pixelToCoord(pos1) - axis->pixelToCoord(pos2);
288 288 return QCPRange{axis->range().lower + diff, axis->range().upper + diff};
289 289 }
290 290 else
291 291 {
292 292 auto diff = axis->pixelToCoord(pos1) / axis->pixelToCoord(pos2);
293 293 return QCPRange{axis->range().lower * diff, axis->range().upper * diff};
294 294 }
295 295 }
296 296
297 297 void setRange(const DateTimeRange &newRange, bool updateVar=true)
298 298 {
299 299 this->m_plot->xAxis->setRange(newRange.m_TStart, newRange.m_TEnd);
300 300 if(updateVar)
301 301 {
302 302 for (auto it = m_VariableToPlotMultiMap.begin(),
303 303 end = m_VariableToPlotMultiMap.end();
304 304 it != end; it = m_VariableToPlotMultiMap.upper_bound(it->first))
305 305 {
306 306 sqpApp->variableController().asyncChangeRange(it->first, newRange);
307 307 }
308 308 }
309 309 m_plot->replot(QCustomPlot::rpQueuedReplot);
310 310 }
311 311
312 312 void setRange(const QCPRange &newRange)
313 313 {
314 314 auto graphRange = DateTimeRange{newRange.lower, newRange.upper};
315 315 setRange(graphRange);
316 316 }
317 317
318 318 void rescaleY()
319 319 {
320 320 m_plot->yAxis->rescale(true);
321 321 }
322 322
323 323 std::tuple<double,double> moveGraph(const QPoint& destination)
324 324 {
325 325 auto currentPos = m_plot->mapFromParent(destination);
326 326 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
327 327 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
328 328 auto oldXRange = xAxis->range();
329 329 auto oldYRange = yAxis->range();
330 330 double dx = xAxis->pixelToCoord(m_lastMousePos.x()) - xAxis->pixelToCoord(currentPos.x());
331 331 xAxis->setRange(m_lastXRange.lower+dx, m_lastXRange.upper+dx);
332 332 if(yAxis->scaleType() == QCPAxis::stLinear)
333 333 {
334 334 double dy = yAxis->pixelToCoord(m_lastMousePos.y()) - yAxis->pixelToCoord(currentPos.y());
335 335 yAxis->setRange(m_lastYRange.lower+dy, m_lastYRange.upper+dy);
336 336 }
337 337 else
338 338 {
339 339 double dy = yAxis->pixelToCoord(m_lastMousePos.y()) / yAxis->pixelToCoord(currentPos.y());
340 340 yAxis->setRange(m_lastYRange.lower*dy, m_lastYRange.upper*dy);
341 341 }
342 342 auto newXRange = xAxis->range();
343 343 auto newYRange = yAxis->range();
344 344 setRange(xAxis->range());
345 345 //m_lastMousePos = currentPos;
346 346 return {newXRange.lower - oldXRange.lower, newYRange.lower - oldYRange.lower};
347 347 }
348 348
349 349 void zoom(double factor, int center, Qt::Orientation orientation)
350 350 {
351 351 QCPAxis *axis = m_plot->axisRect()->rangeZoomAxis(orientation);
352 352 axis->scaleRange(factor, axis->pixelToCoord(center));
353 353 if (orientation == Qt::Horizontal)
354 354 setRange(axis->range());
355 355 m_plot->replot(QCustomPlot::rpQueuedReplot);
356 356 }
357 357
358 358 void transform(const DateTimeRangeTransformation &tranformation)
359 359 {
360 360 auto graphRange = m_plot->xAxis->range();
361 361 DateTimeRange range{graphRange.lower, graphRange.upper};
362 362 range = range.transform(tranformation);
363 363 setRange(range);
364 364 m_plot->replot(QCustomPlot::rpQueuedReplot);
365 365 }
366 366
367 367 void move(double dx, double dy)
368 368 {
369 369 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
370 370 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
371 371 xAxis->setRange(QCPRange(xAxis->range().lower+dx, xAxis->range().upper+dx));
372 372 yAxis->setRange(QCPRange(yAxis->range().lower+dy, yAxis->range().upper+dy));
373 373 setRange(xAxis->range());
374 374 m_plot->replot(QCustomPlot::rpQueuedReplot);
375 375 }
376 376
377 377 void move(double factor, Qt::Orientation orientation)
378 378 {
379 379 auto oldRange = m_plot->xAxis->range();
380 380 QCPAxis *axis = m_plot->axisRect()->rangeDragAxis(orientation);
381 381 if (m_plot->xAxis->scaleType() == QCPAxis::stLinear) {
382 382 double rg = (axis->range().upper - axis->range().lower) * (factor / 10);
383 383 axis->setRange(axis->range().lower + (rg), axis->range().upper + (rg));
384 384 }
385 385 else if (m_plot->xAxis->scaleType() == QCPAxis::stLogarithmic) {
386 386 int start = 0, stop = 0;
387 387 double diff = 0.;
388 388 if (factor > 0.0) {
389 389 stop = m_plot->width() * factor / 10;
390 390 start = 2 * m_plot->width() * factor / 10;
391 391 }
392 392 if (factor < 0.0) {
393 393 factor *= -1.0;
394 394 start = m_plot->width() * factor / 10;
395 395 stop = 2 * m_plot->width() * factor / 10;
396 396 }
397 397 diff = axis->pixelToCoord(start) / axis->pixelToCoord(stop);
398 398 axis->setRange(m_plot->axisRect()->rangeDragAxis(orientation)->range().lower * diff,
399 399 m_plot->axisRect()->rangeDragAxis(orientation)->range().upper * diff);
400 400 }
401 401 if (orientation == Qt::Horizontal)
402 402 setRange(axis->range());
403 403 m_plot->replot(QCustomPlot::rpQueuedReplot);
404 404 }
405 405 };
406 406
407 407 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
408 408 : VisualizationDragWidget{parent},
409 409 ui{new Ui::VisualizationGraphWidget},
410 410 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
411 411 {
412 412 ui->setupUi(this);
413 413 this->layout()->addWidget(impl->m_plot);
414 414 // 'Close' options : widget is deleted when closed
415 415 setAttribute(Qt::WA_DeleteOnClose);
416 416
417 417 // The delegate must be initialized after the ui as it uses the plot
418 418 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
419 419
420 420 // Init the cursors
421 421 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
422 422 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
423 423 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
424 424 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
425 425
426 426 this->setFocusPolicy(Qt::WheelFocus);
427 427 this->setMouseTracking(true);
428 428 impl->m_plot->setAttribute(Qt::WA_TransparentForMouseEvents);
429 429 impl->m_plot->setContextMenuPolicy(Qt::CustomContextMenu);
430 430 impl->m_plot->setParent(this);
431 431 }
432 432
433 433
434 434 VisualizationGraphWidget::~VisualizationGraphWidget()
435 435 {
436 436 delete ui;
437 437 }
438 438
439 439 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
440 440 {
441 441 auto parent = parentWidget();
442 442 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
443 443 parent = parent->parentWidget();
444 444 }
445 445
446 446 return qobject_cast<VisualizationZoneWidget *>(parent);
447 447 }
448 448
449 449 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
450 450 {
451 451 auto parent = parentWidget();
452 452 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
453 453 parent = parent->parentWidget();
454 454 }
455 455
456 456 return qobject_cast<VisualizationWidget *>(parent);
457 457 }
458 458
459 459 void VisualizationGraphWidget::setFlags(GraphFlags flags)
460 460 {
461 461 impl->m_Flags = std::move(flags);
462 462 }
463 463
464 464 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, DateTimeRange range)
465 465 {
466 466 // Uses delegate to create the qcpplot components according to the variable
467 467 auto createdPlottables = VisualizationGraphHelper::create(variable, *impl->m_plot);
468 468
469 469 // Sets graph properties
470 470 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
471 471
472 472 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
473 473
474 setGraphRange(range);
474 475 // If the variable already has its data loaded, load its units and its range in the graph
475 476 if (variable->dataSeries() != nullptr) {
476 477 impl->m_RenderingDelegate->setAxesUnits(*variable);
477 this->setFlags(GraphFlag::DisableAll);
478 setGraphRange(range);
479 this->setFlags(GraphFlag::EnableAll);
478 }
479 else
480 {
481 auto context = new QObject{this};
482 connect(variable.get(), &Variable::updated, context,
483 [this, variable, context, range](QUuid)
484 {
485 this->impl->m_RenderingDelegate->setAxesUnits(*variable);
486 this->impl->rescaleY();
487 this->impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
488 delete context;
489 }
490 );
480 491 }
481 492 //@TODO this is bad! when variable is moved to another graph it still fires
482 493 // even if this has been deleted
483 494 connect(variable.get(), &Variable::updated, this, &VisualizationGraphWidget::variableUpdated);
484 495 this->onUpdateVarDisplaying(variable, range); // My bullshit
485 496 emit variableAdded(variable);
486 497 }
487 498
488 499 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
489 500 {
490 501 // Each component associated to the variable :
491 502 // - is removed from qcpplot (which deletes it)
492 503 // - is no longer referenced in the map
493 504 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
494 505 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
495 506 emit variableAboutToBeRemoved(variable);
496 507
497 508 auto &plottablesMap = variableIt->second;
498 509
499 510 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
500 511 plottableIt != plottableEnd;) {
501 512 impl->m_plot->removePlottable(plottableIt->second);
502 513 plottableIt = plottablesMap.erase(plottableIt);
503 514 }
504 515
505 516 impl->m_VariableToPlotMultiMap.erase(variableIt);
506 517 }
507 518
508 519 // Updates graph
509 520 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
510 521 }
511 522
512 523 std::vector<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
513 524 {
514 525 auto variables = std::vector<std::shared_ptr<Variable> >{};
515 526 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
516 527 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
517 528 variables.push_back(it->first);
518 529 }
519 530
520 531 return variables;
521 532 }
522 533
523 534 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
524 535 {
525 536 if (!variable) {
526 537 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
527 538 return;
528 539 }
529 540
530 541 VisualizationGraphHelper::setYAxisRange(variable, *impl->m_plot);
531 542 }
532 543
533 544 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
534 545 {
535 546 auto graphRange = impl->m_plot->xAxis->range();
536 547 return DateTimeRange{graphRange.lower, graphRange.upper};
537 548 }
538 549
539 550 void VisualizationGraphWidget::setGraphRange(const DateTimeRange &range, bool updateVar, bool forward)
540 551 {
541 552 auto oldRange = graphRange();
542 553 impl->setRange(range, updateVar);
543 554 if(forward)
544 555 {
545 556 auto newRange = graphRange();
546 557 if(auto tf = DateTimeRangeHelper::computeTransformation(oldRange,newRange))
547 558 emit this->transform_sig(tf.value(), false);
548 559 }
549 560
550 561 }
551 562
552 563 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
553 564 {
554 565 impl->m_VariableAutoRangeOnInit = value;
555 566 }
556 567
557 568 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
558 569 {
559 570 QVector<DateTimeRange> ranges;
560 571 for (auto zone : impl->m_SelectionZones) {
561 572 ranges << zone->range();
562 573 }
563 574
564 575 return ranges;
565 576 }
566 577
567 578 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange> &ranges)
568 579 {
569 580 for (const auto &range : ranges) {
570 581 // note: ownership is transfered to QCustomPlot
571 582 auto zone = new VisualizationSelectionZoneItem(&plot());
572 583 zone->setRange(range.m_TStart, range.m_TEnd);
573 584 impl->addSelectionZone(zone);
574 585 }
575 586
576 587 plot().replot(QCustomPlot::rpQueuedReplot);
577 588 }
578 589
579 590 VisualizationSelectionZoneItem *
580 591 VisualizationGraphWidget::addSelectionZone(const QString &name, const DateTimeRange &range)
581 592 {
582 593 // note: ownership is transfered to QCustomPlot
583 594 auto zone = new VisualizationSelectionZoneItem(&plot());
584 595 zone->setName(name);
585 596 zone->setRange(range.m_TStart, range.m_TEnd);
586 597 impl->addSelectionZone(zone);
587 598
588 599 plot().replot(QCustomPlot::rpQueuedReplot);
589 600
590 601 return zone;
591 602 }
592 603
593 604 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
594 605 {
595 606 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
596 607
597 608 if (impl->m_HoveredZone == selectionZone) {
598 609 impl->m_HoveredZone = nullptr;
599 610 setCursor(Qt::ArrowCursor);
600 611 }
601 612
602 613 impl->m_SelectionZones.removeAll(selectionZone);
603 614 plot().removeItem(selectionZone);
604 615 plot().replot(QCustomPlot::rpQueuedReplot);
605 616 }
606 617
607 618 void VisualizationGraphWidget::undoZoom()
608 619 {
609 620 auto zoom = impl->m_ZoomStack.pop();
610 621 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
611 622 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
612 623
613 624 axisX->setRange(zoom.first);
614 625 axisY->setRange(zoom.second);
615 626
616 627 plot().replot(QCustomPlot::rpQueuedReplot);
617 628 }
618 629
619 630 void VisualizationGraphWidget::zoom(double factor, int center, Qt::Orientation orientation, bool forward)
620 631 {
621 632 impl->zoom(factor, center, orientation);
622 633 if(forward && orientation==Qt::Horizontal)
623 634 emit this->zoom_sig(factor, center, orientation, false);
624 635 }
625 636
626 637 void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation, bool forward)
627 638 {
628 639 impl->move(factor, orientation);
629 640 if(forward)
630 641 emit this->move_sig(factor, orientation, false);
631 642 }
632 643
633 644 void VisualizationGraphWidget::move(double dx, double dy, bool forward)
634 645 {
635 646 impl->move(dx, dy);
636 647 if(forward)
637 648 emit this->move_sig(dx, dy, false);
638 649 }
639 650
640 651 void VisualizationGraphWidget::transform(const DateTimeRangeTransformation &tranformation, bool forward)
641 652 {
642 653 impl->transform(tranformation);
643 654 if(forward)
644 655 emit this->transform_sig(tranformation, false);
645 656 }
646 657
647 658 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
648 659 {
649 660 if (visitor) {
650 661 visitor->visit(this);
651 662 }
652 663 else {
653 664 qCCritical(LOG_VisualizationGraphWidget())
654 665 << tr("Can't visit widget : the visitor is null");
655 666 }
656 667 }
657 668
658 669 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
659 670 {
660 671 auto isSpectrogram = [](const auto &variable) {
661 672 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
662 673 };
663 674
664 675 // - A spectrogram series can't be dropped on graph with existing plottables
665 676 // - No data series can be dropped on graph with existing spectrogram series
666 677 return isSpectrogram(variable)
667 678 ? impl->m_VariableToPlotMultiMap.empty()
668 679 : std::none_of(
669 680 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
670 681 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
671 682 }
672 683
673 684 bool VisualizationGraphWidget::contains(const Variable &variable) const
674 685 {
675 686 // Finds the variable among the keys of the map
676 687 auto variablePtr = &variable;
677 688 auto findVariable
678 689 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
679 690
680 691 auto end = impl->m_VariableToPlotMultiMap.cend();
681 692 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
682 693 return it != end;
683 694 }
684 695
685 696 QString VisualizationGraphWidget::name() const
686 697 {
687 698 return impl->m_Name;
688 699 }
689 700
690 701 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
691 702 {
692 703 auto mimeData = new QMimeData;
693 704
694 705 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position);
695 706 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
696 707 && selectionZoneItemUnderCursor) {
697 708 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
698 709 selectionZoneItemUnderCursor->range()));
699 710 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
700 711 selectionZoneItemUnderCursor->range()));
701 712 }
702 713 else {
703 714 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
704 715
705 716 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
706 717 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
707 718 }
708 719
709 720 return mimeData;
710 721 }
711 722
712 723 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
713 724 {
714 725 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition);
715 726 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
716 727 && selectionZoneItemUnderCursor) {
717 728
718 729 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
719 730 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
720 731
721 732 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
722 733 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
723 734 .toSize();
724 735
725 736 auto pixmap = QPixmap(zoneSize);
726 737 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
727 738
728 739 return pixmap;
729 740 }
730 741
731 742 return QPixmap();
732 743 }
733 744
734 745 bool VisualizationGraphWidget::isDragAllowed() const
735 746 {
736 747 return true;
737 748 }
738 749
739 750 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
740 751 {
741 752 if (highlighted) {
742 753 plot().setBackground(QBrush(QColor("#BBD5EE")));
743 754 }
744 755 else {
745 756 plot().setBackground(QBrush(Qt::white));
746 757 }
747 758
748 759 plot().update();
749 760 }
750 761
751 762 void VisualizationGraphWidget::addVerticalCursor(double time)
752 763 {
753 764 impl->m_VerticalCursor->setPosition(time);
754 765 impl->m_VerticalCursor->setVisible(true);
755 766
756 767 auto text
757 768 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
758 769 impl->m_VerticalCursor->setLabelText(text);
759 770 }
760 771
761 772 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
762 773 {
763 774 impl->m_VerticalCursor->setAbsolutePosition(position);
764 775 impl->m_VerticalCursor->setVisible(true);
765 776
766 777 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
767 778 auto text
768 779 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
769 780 impl->m_VerticalCursor->setLabelText(text);
770 781 }
771 782
772 783 void VisualizationGraphWidget::removeVerticalCursor()
773 784 {
774 785 impl->m_VerticalCursor->setVisible(false);
775 786 plot().replot(QCustomPlot::rpQueuedReplot);
776 787 }
777 788
778 789 void VisualizationGraphWidget::addHorizontalCursor(double value)
779 790 {
780 791 impl->m_HorizontalCursor->setPosition(value);
781 792 impl->m_HorizontalCursor->setVisible(true);
782 793 impl->m_HorizontalCursor->setLabelText(QString::number(value));
783 794 }
784 795
785 796 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
786 797 {
787 798 impl->m_HorizontalCursor->setAbsolutePosition(position);
788 799 impl->m_HorizontalCursor->setVisible(true);
789 800
790 801 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
791 802 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
792 803 }
793 804
794 805 void VisualizationGraphWidget::removeHorizontalCursor()
795 806 {
796 807 impl->m_HorizontalCursor->setVisible(false);
797 808 plot().replot(QCustomPlot::rpQueuedReplot);
798 809 }
799 810
800 811 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
801 812 {
802 813 Q_UNUSED(event);
803 814
804 815 for (auto i : impl->m_SelectionZones) {
805 816 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
806 817 }
807 818
808 819 // Prevents that all variables will be removed from graph when it will be closed
809 820 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
810 821 emit variableAboutToBeRemoved(variableEntry.first);
811 822 }
812 823 }
813 824
814 825 void VisualizationGraphWidget::enterEvent(QEvent *event)
815 826 {
816 827 Q_UNUSED(event);
817 828 impl->m_RenderingDelegate->showGraphOverlay(true);
818 829 }
819 830
820 831 void VisualizationGraphWidget::leaveEvent(QEvent *event)
821 832 {
822 833 Q_UNUSED(event);
823 834 impl->m_RenderingDelegate->showGraphOverlay(false);
824 835
825 836 if (auto parentZone = parentZoneWidget()) {
826 837 parentZone->notifyMouseLeaveGraph(this);
827 838 }
828 839 else {
829 840 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
830 841 }
831 842
832 843 if (impl->m_HoveredZone) {
833 844 impl->m_HoveredZone->setHovered(false);
834 845 impl->m_HoveredZone = nullptr;
835 846 }
836 847 }
837 848
838 849 void VisualizationGraphWidget::wheelEvent(QWheelEvent *event)
839 850 {
840 851 double factor;
841 852 double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually
842 853 if (event->modifiers() == Qt::ControlModifier) {
843 854 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
844 855 {
845 856 setCursor(Qt::SizeVerCursor);
846 857 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps);
847 858 zoom(factor, event->pos().y(), Qt::Vertical);
848 859 }
849 860 }
850 861 else if (event->modifiers() == Qt::ShiftModifier) {
851 862 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
852 863 {
853 864 setCursor(Qt::SizeHorCursor);
854 865 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps);
855 866 zoom(factor, event->pos().x(), Qt::Horizontal);
856 867 }
857 868 }
858 869 else {
859 870 move(wheelSteps, Qt::Horizontal);
860 871 }
861 872 event->accept();
862 873 }
863 874
864 875
865 876
866 877 void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent *event)
867 878 {
868 879 if(impl->isDrawingZoomRect())
869 880 {
870 881 impl->updateZoomRect(event->pos());
871 882 }
872 883 else if (impl->isDrawingZoneRect())
873 884 {
874 885 impl->updateZoneRect(event->pos());
875 886 }
876 887 else if (event->buttons() == Qt::LeftButton)
877 888 {
878 889 if(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::None)
879 890 {
880 891 auto [dx,dy] = impl->moveGraph(event->pos());
881 892 emit this->move_sig(dx,0., false); // don't sync Y transformations
882 893 }
883 894 else if(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones)
884 895 {
885 896
886 897 }
887 898 }
888 899 else
889 900 {
890 901 impl->m_RenderingDelegate->updateTooltip(event);
891 902 }
892 903 //event->accept();
893 904 QWidget::mouseMoveEvent(event);
894 905 }
895 906
896 907 void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent *event)
897 908 {
898 909 if(impl->isDrawingZoomRect())
899 910 {
900 911 auto oldRange = this->graphRange();
901 912 impl->applyZoomRect();
902 913 auto newRange = this->graphRange();
903 914 if(auto tf = DateTimeRangeHelper::computeTransformation(oldRange,newRange))
904 915 emit this->transform_sig(tf.value(), false);
905 916 }
906 917 else if(impl->isDrawingZoneRect())
907 918 {
908 919 impl->endDrawingZone();
909 920 }
910 921 else
911 922 {
912 923 setCursor(Qt::ArrowCursor);
913 924 }
914 925 event->accept();
915 926 }
916 927
917 928 void VisualizationGraphWidget::mousePressEvent(QMouseEvent *event)
918 929 {
919 930 if (event->button()==Qt::RightButton)
920 931 {
921 932 onGraphMenuRequested(event->pos());
922 933 }
923 934 else
924 935 {
925 936 auto selectedZone = impl->selectionZoneAt(event->pos());
926 937 switch (sqpApp->plotsInteractionMode())
927 938 {
928 939 case SqpApplication::PlotsInteractionMode::DragAndDrop :
929 940 break;
930 941 case SqpApplication::PlotsInteractionMode::SelectionZones :
931 942 impl->setSelectionZonesEditionEnabled(true);
932 943 if ((event->modifiers() == Qt::ControlModifier) && (selectedZone != nullptr))
933 944 {
934 945 selectedZone->setAssociatedEditedZones(parentVisualizationWidget()->selectionZoneManager().selectedItems());
935 946 }
936 947 else
937 948 {
938 949 if (!selectedZone)
939 950 {
940 951 parentVisualizationWidget()->selectionZoneManager().clearSelection();
941 952 impl->startDrawingZone(event->pos());
942 953 }
943 954 else
944 955 {
945 956 parentVisualizationWidget()->selectionZoneManager().select({ selectedZone });
946 957 }
947 958 }
948 959 break;
949 960 case SqpApplication::PlotsInteractionMode::ZoomBox :
950 961 impl->startDrawingRect(event->pos());
951 962 break;
952 963 default:
953 964 setCursor(Qt::ClosedHandCursor);
954 965 impl->enterPlotDrag(event->pos());
955 966 }
956 967 }
957 968 //event->accept();
958 969 QWidget::mousePressEvent(event);
959 970 }
960 971
961 972 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent *event)
962 973 {
963 974 impl->m_RenderingDelegate->onMouseDoubleClick(event);
964 975 }
965 976
966 977 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent *event)
967 978 {
968 979 switch (event->key()) {
969 980 case Qt::Key_Control:
970 981 event->accept();
971 982 break;
972 983 case Qt::Key_Shift:
973 984 event->accept();
974 985 break;
975 986 default:
976 987 QWidget::keyReleaseEvent(event);
977 988 break;
978 989 }
979 990 setCursor(Qt::ArrowCursor);
980 991 //event->accept();
981 992 }
982 993
983 994 void VisualizationGraphWidget::keyPressEvent(QKeyEvent *event)
984 995 {
985 996 switch (event->key()) {
986 997 case Qt::Key_Control:
987 998 setCursor(Qt::CrossCursor);
988 999 break;
989 1000 case Qt::Key_Shift:
990 1001 break;
991 1002 case Qt::Key_M:
992 1003 impl->rescaleY();
993 1004 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
994 1005 break;
995 1006 case Qt::Key_Left:
996 1007 if (event->modifiers() != Qt::ControlModifier) {
997 1008 move(-0.1, Qt::Horizontal);
998 1009 }
999 1010 else {
1000 1011 zoom(2, this->width() / 2, Qt::Horizontal);
1001 1012 }
1002 1013 break;
1003 1014 case Qt::Key_Right:
1004 1015 if (event->modifiers() != Qt::ControlModifier) {
1005 1016 move(0.1, Qt::Horizontal);
1006 1017 }
1007 1018 else {
1008 1019 zoom(0.5, this->width() / 2, Qt::Horizontal);
1009 1020 }
1010 1021 break;
1011 1022 case Qt::Key_Up:
1012 1023 if (event->modifiers() != Qt::ControlModifier) {
1013 1024 move(0.1, Qt::Vertical);
1014 1025 }
1015 1026 else {
1016 1027 zoom(0.5, this->height() / 2, Qt::Vertical);
1017 1028 }
1018 1029 break;
1019 1030 case Qt::Key_Down:
1020 1031 if (event->modifiers() != Qt::ControlModifier) {
1021 1032 move(-0.1, Qt::Vertical);
1022 1033 }
1023 1034 else {
1024 1035 zoom(2, this->height() / 2, Qt::Vertical);
1025 1036 }
1026 1037 break;
1027 1038 default:
1028 1039 QWidget::keyPressEvent(event);
1029 1040 break;
1030 1041 }
1031 1042 }
1032 1043
1033 1044 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
1034 1045 {
1035 1046 return *impl->m_plot;
1036 1047 }
1037 1048
1038 1049 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
1039 1050 {
1040 1051 QMenu graphMenu{};
1041 1052
1042 1053 // Iterates on variables (unique keys)
1043 1054 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
1044 1055 end = impl->m_VariableToPlotMultiMap.cend();
1045 1056 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
1046 1057 // 'Remove variable' action
1047 1058 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
1048 1059 [this, var = it->first]() { removeVariable(var); });
1049 1060 }
1050 1061
1051 1062 if (!impl->m_ZoomStack.isEmpty()) {
1052 1063 if (!graphMenu.isEmpty()) {
1053 1064 graphMenu.addSeparator();
1054 1065 }
1055 1066
1056 1067 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
1057 1068 }
1058 1069
1059 1070 // Selection Zone Actions
1060 1071 auto selectionZoneItem = impl->selectionZoneAt(pos);
1061 1072 if (selectionZoneItem) {
1062 1073 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1063 1074 selectedItems.removeAll(selectionZoneItem);
1064 1075 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
1065 1076
1066 1077 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
1067 1078 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
1068 1079 graphMenu.addSeparator();
1069 1080 }
1070 1081
1071 1082 QHash<QString, QMenu *> subMenus;
1072 1083 QHash<QString, bool> subMenusEnabled;
1073 1084 QHash<QString, FilteringAction *> filteredMenu;
1074 1085
1075 1086 for (auto zoneAction : zoneActions) {
1076 1087
1077 1088 auto isEnabled = zoneAction->isEnabled(selectedItems);
1078 1089
1079 1090 auto menu = &graphMenu;
1080 1091 QString menuPath;
1081 1092 for (auto subMenuName : zoneAction->subMenuList()) {
1082 1093 menuPath += '/';
1083 1094 menuPath += subMenuName;
1084 1095
1085 1096 if (!subMenus.contains(menuPath)) {
1086 1097 menu = menu->addMenu(subMenuName);
1087 1098 subMenus[menuPath] = menu;
1088 1099 subMenusEnabled[menuPath] = isEnabled;
1089 1100 }
1090 1101 else {
1091 1102 menu = subMenus.value(menuPath);
1092 1103 if (isEnabled) {
1093 1104 // The sub menu is enabled if at least one of its actions is enabled
1094 1105 subMenusEnabled[menuPath] = true;
1095 1106 }
1096 1107 }
1097 1108 }
1098 1109
1099 1110 FilteringAction *filterAction = nullptr;
1100 1111 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList())) {
1101 1112 filterAction = filteredMenu.value(menuPath);
1102 1113 if (!filterAction) {
1103 1114 filterAction = new FilteringAction{this};
1104 1115 filteredMenu[menuPath] = filterAction;
1105 1116 menu->addAction(filterAction);
1106 1117 }
1107 1118 }
1108 1119
1109 1120 auto action = menu->addAction(zoneAction->name());
1110 1121 action->setEnabled(isEnabled);
1111 1122 action->setShortcut(zoneAction->displayedShortcut());
1112 1123 QObject::connect(action, &QAction::triggered,
1113 1124 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1114 1125
1115 1126 if (filterAction && zoneAction->isFilteringAllowed()) {
1116 1127 filterAction->addActionToFilter(action);
1117 1128 }
1118 1129 }
1119 1130
1120 1131 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
1121 1132 it.value()->setEnabled(subMenusEnabled[it.key()]);
1122 1133 }
1123 1134 }
1124 1135
1125 1136 if (!graphMenu.isEmpty()) {
1126 1137 graphMenu.exec(QCursor::pos());
1127 1138 }
1128 1139 }
1129 1140
1130 1141 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
1131 1142 {
1132 1143 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1133 1144 }
1134 1145
1135 1146 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
1136 1147 {
1137 1148 // Handles plot rendering when mouse is moving
1138 1149 impl->m_RenderingDelegate->updateTooltip(event);
1139 1150
1140 1151 auto axisPos = impl->posToAxisPos(event->pos());
1141 1152
1142 1153 // Zoom box and zone drawing
1143 1154 if (impl->m_DrawingZoomRect) {
1144 1155 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1145 1156 }
1146 1157 else if (impl->m_DrawingZone) {
1147 1158 impl->m_DrawingZone->setEnd(axisPos.x());
1148 1159 }
1149 1160
1150 1161 // Cursor
1151 1162 if (auto parentZone = parentZoneWidget()) {
1152 1163 if (impl->pointIsInAxisRect(axisPos, plot())) {
1153 1164 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1154 1165 }
1155 1166 else {
1156 1167 parentZone->notifyMouseLeaveGraph(this);
1157 1168 }
1158 1169 }
1159 1170
1160 1171 // Search for the selection zone under the mouse
1161 1172 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1162 1173 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1163 1174 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
1164 1175
1165 1176 // Sets the appropriate cursor shape
1166 1177 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1167 1178 setCursor(cursorShape);
1168 1179
1169 1180 // Manages the hovered zone
1170 1181 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
1171 1182 if (impl->m_HoveredZone) {
1172 1183 impl->m_HoveredZone->setHovered(false);
1173 1184 }
1174 1185 selectionZoneItemUnderCursor->setHovered(true);
1175 1186 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1176 1187 plot().replot(QCustomPlot::rpQueuedReplot);
1177 1188 }
1178 1189 }
1179 1190 else {
1180 1191 // There is no zone under the mouse or the interaction mode is not "selection zones"
1181 1192 if (impl->m_HoveredZone) {
1182 1193 impl->m_HoveredZone->setHovered(false);
1183 1194 impl->m_HoveredZone = nullptr;
1184 1195 }
1185 1196
1186 1197 setCursor(Qt::ArrowCursor);
1187 1198 }
1188 1199
1189 1200 impl->m_HasMovedMouse = true;
1190 1201 VisualizationDragWidget::mouseMoveEvent(event);
1191 1202 }
1192 1203
1193 1204 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
1194 1205 {
1195 1206 // Processes event only if the wheel occurs on axis rect
1196 1207 if (!dynamic_cast<QCPAxisRect *>(impl->m_plot->layoutElementAt(event->posF()))) {
1197 1208 return;
1198 1209 }
1199 1210
1200 1211 auto value = event->angleDelta().x() + event->angleDelta().y();
1201 1212 if (value != 0) {
1202 1213
1203 1214 auto direction = value > 0 ? 1.0 : -1.0;
1204 1215 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1205 1216 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1206 1217 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1207 1218
1208 1219 auto zoomOrientations = QFlags<Qt::Orientation>{};
1209 1220 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1210 1221 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1211 1222
1212 1223 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1213 1224
1214 1225 if (!isZoomX && !isZoomY) {
1215 1226 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1216 1227 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1217 1228
1218 1229 axis->setRange(axis->range() + diff);
1219 1230
1220 1231 if (plot().noAntialiasingOnDrag()) {
1221 1232 plot().setNotAntialiasedElements(QCP::aeAll);
1222 1233 }
1223 1234
1224 1235 // plot().replot(QCustomPlot::rpQueuedReplot);
1225 1236 }
1226 1237 }
1227 1238 }
1228 1239
1229 1240 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
1230 1241 {
1231 1242 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1232 1243 auto isSelectionZoneMode
1233 1244 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1234 1245 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1235 1246
1236 1247 if (!isDragDropClick && isLeftClick) {
1237 1248 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
1238 1249 // Starts a zoom box
1239 1250 impl->startDrawingRect(event->pos());
1240 1251 }
1241 1252 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
1242 1253 // Starts a new selection zone
1243 1254 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1244 1255 if (!zoneAtPos) {
1245 1256 impl->startDrawingZone(event->pos());
1246 1257 }
1247 1258 }
1248 1259 }
1249 1260
1250 1261
1251 1262 // Allows zone edition only in selection zone mode without drag&drop
1252 1263 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1253 1264
1254 1265 // Selection / Deselection
1255 1266 if (isSelectionZoneMode) {
1256 1267 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1257 1268 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1258 1269
1259 1270
1260 1271 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1261 1272 && !isMultiSelectionClick) {
1262 1273 parentVisualizationWidget()->selectionZoneManager().select(
1263 1274 {selectionZoneItemUnderCursor});
1264 1275 }
1265 1276 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
1266 1277 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1267 1278 }
1268 1279 else {
1269 1280 // No selection change
1270 1281 }
1271 1282
1272 1283 if (selectionZoneItemUnderCursor && isLeftClick) {
1273 1284 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1274 1285 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1275 1286 }
1276 1287 }
1277 1288
1278 1289
1279 1290 impl->m_HasMovedMouse = false;
1280 1291 VisualizationDragWidget::mousePressEvent(event);
1281 1292 }
1282 1293
1283 1294 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
1284 1295 {
1285 1296 if (impl->m_DrawingZoomRect) {
1286 1297
1287 1298 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1288 1299 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1289 1300
1290 1301 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
1291 1302 impl->m_DrawingZoomRect->bottomRight->coords().x()};
1292 1303
1293 1304 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
1294 1305 impl->m_DrawingZoomRect->bottomRight->coords().y()};
1295 1306
1296 1307 impl->removeDrawingRect();
1297 1308
1298 1309 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1299 1310 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
1300 1311 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1301 1312 axisX->setRange(newAxisXRange);
1302 1313 axisY->setRange(newAxisYRange);
1303 1314
1304 1315 plot().replot(QCustomPlot::rpQueuedReplot);
1305 1316 }
1306 1317 }
1307 1318
1308 1319 impl->endDrawingZone();
1309 1320
1310 1321 // Selection / Deselection
1311 1322 auto isSelectionZoneMode
1312 1323 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1313 1324 if (isSelectionZoneMode) {
1314 1325 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1315 1326 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1316 1327 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1317 1328 && !impl->m_HasMovedMouse) {
1318 1329
1319 1330 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1320 1331 if (zonesUnderCursor.count() > 1) {
1321 1332 // There are multiple zones under the mouse.
1322 1333 // Performs the selection with a selection dialog.
1323 1334 VisualizationMultiZoneSelectionDialog dialog{this};
1324 1335 dialog.setZones(zonesUnderCursor);
1325 1336 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1326 1337 dialog.activateWindow();
1327 1338 dialog.raise();
1328 1339 if (dialog.exec() == QDialog::Accepted) {
1329 1340 auto selection = dialog.selectedZones();
1330 1341
1331 1342 if (!isMultiSelectionClick) {
1332 1343 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1333 1344 }
1334 1345
1335 1346 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
1336 1347 auto zone = it.key();
1337 1348 auto isSelected = it.value();
1338 1349 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
1339 1350 isSelected);
1340 1351
1341 1352 if (isSelected) {
1342 1353 // Puts the zone on top of the stack so it can be moved or resized
1343 1354 impl->moveSelectionZoneOnTop(zone, plot());
1344 1355 }
1345 1356 }
1346 1357 }
1347 1358 }
1348 1359 else {
1349 1360 if (!isMultiSelectionClick) {
1350 1361 parentVisualizationWidget()->selectionZoneManager().select(
1351 1362 {selectionZoneItemUnderCursor});
1352 1363 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1353 1364 }
1354 1365 else {
1355 1366 parentVisualizationWidget()->selectionZoneManager().setSelected(
1356 1367 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
1357 1368 || event->button() == Qt::RightButton);
1358 1369 }
1359 1370 }
1360 1371 }
1361 1372 else {
1362 1373 // No selection change
1363 1374 }
1364 1375 }
1365 1376 }
1366 1377
1367 1378 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1368 1379 {
1369 1380 auto graphRange = impl->m_plot->xAxis->range();
1370 1381 auto dateTime = DateTimeRange{graphRange.lower, graphRange.upper};
1371 1382
1372 1383 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
1373 1384 auto variable = variableEntry.first;
1374 1385 qCDebug(LOG_VisualizationGraphWidget())
1375 1386 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1376 1387 qCDebug(LOG_VisualizationGraphWidget())
1377 1388 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1378 1389 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
1379 1390 impl->updateData(variableEntry.second, variable, variable->range());
1380 1391 }
1381 1392 }
1382 1393 }
1383 1394
1384 1395 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
1385 1396 const DateTimeRange &range)
1386 1397 {
1387 1398 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1388 1399 if (it != impl->m_VariableToPlotMultiMap.end()) {
1389 1400 impl->updateData(it->second, variable, range);
1390 1401 }
1391 1402 }
1392 1403
1393 1404 void VisualizationGraphWidget::variableUpdated(QUuid id)
1394 1405 {
1395 1406 for (auto &[var, plotables] : impl->m_VariableToPlotMultiMap) {
1396 1407 if (var->ID() == id) {
1397 1408 impl->updateData(plotables, var, this->graphRange());
1398 1409 }
1399 1410 }
1400 1411 }
@@ -1,659 +1,675
1 1 #include "Visualization/VisualizationZoneWidget.h"
2 2
3 3 #include "Visualization/IVisualizationWidgetVisitor.h"
4 4 #include "Visualization/QCustomPlotSynchronizer.h"
5 5 #include "Visualization/VisualizationGraphWidget.h"
6 6 #include "Visualization/VisualizationWidget.h"
7 7 #include "ui_VisualizationZoneWidget.h"
8 8
9 9 #include "Common/MimeTypesDef.h"
10 10 #include "Common/VisualizationDef.h"
11 11
12 12 #include <Data/DateTimeRange.h>
13 13 #include <Data/DateTimeRangeHelper.h>
14 14 #include <DataSource/DataSourceController.h>
15 15 #include <Time/TimeController.h>
16 16 #include <Variable/Variable.h>
17 17 #include <Variable/VariableController2.h>
18 18
19 19 #include <Visualization/operations/FindVariableOperation.h>
20 20
21 21 #include <DragAndDrop/DragDropGuiController.h>
22 22 #include <QUuid>
23 23 #include <SqpApplication.h>
24 24 #include <cmath>
25 25
26 26 #include <QLayout>
27 27 #include <QStyle>
28 28
29 29 Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget")
30 30
31 31 namespace {
32 32
33 33 /**
34 34 * Applies a function to all graphs of the zone represented by its layout
35 35 * @param layout the layout that contains graphs
36 36 * @param fun the function to apply to each graph
37 37 */
38 38 template <typename Fun>
39 39 void processGraphs(QLayout &layout, Fun fun)
40 40 {
41 41 for (auto i = 0; i < layout.count(); ++i) {
42 42 if (auto item = layout.itemAt(i)) {
43 43 if (auto visualizationGraphWidget
44 44 = qobject_cast<VisualizationGraphWidget *>(item->widget())) {
45 45 fun(*visualizationGraphWidget);
46 46 }
47 47 }
48 48 }
49 49 }
50 50
51 51 /// Generates a default name for a new graph, according to the number of graphs already displayed in
52 52 /// the zone
53 53 QString defaultGraphName(QLayout &layout)
54 54 {
55 55 QSet<QString> existingNames;
56 56 processGraphs(
57 57 layout, [&existingNames](auto &graphWidget) { existingNames.insert(graphWidget.name()); });
58 58
59 59 int zoneNum = 1;
60 60 QString name;
61 61 do {
62 62 name = QObject::tr("Graph ").append(QString::number(zoneNum));
63 63 ++zoneNum;
64 64 } while (existingNames.contains(name));
65 65
66 66 return name;
67 67 }
68 68
69 69 } // namespace
70 70
71 71 struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate {
72 72
73 73 explicit VisualizationZoneWidgetPrivate()
74 74 : m_SynchronisationGroupId{QUuid::createUuid()},
75 75 m_Synchronizer{std::make_unique<QCustomPlotSynchronizer>()}
76 76 {
77 77 }
78 78 QUuid m_SynchronisationGroupId;
79 79 std::unique_ptr<IGraphSynchronizer> m_Synchronizer;
80 80
81 81 void dropGraph(int index, VisualizationZoneWidget *zoneWidget);
82 82 void dropVariables(const std::vector<std::shared_ptr<Variable> > &variables, int index,
83 83 VisualizationZoneWidget *zoneWidget);
84 84 void dropProducts(const QVariantList &productsData, int index,
85 85 VisualizationZoneWidget *zoneWidget);
86 86 };
87 87
88 88 VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent)
89 89 : VisualizationDragWidget{parent},
90 90 ui{new Ui::VisualizationZoneWidget},
91 91 impl{spimpl::make_unique_impl<VisualizationZoneWidgetPrivate>()}
92 92 {
93 93 ui->setupUi(this);
94 94
95 95 ui->zoneNameLabel->setText(name);
96 96
97 97 ui->dragDropContainer->setPlaceHolderType(DragDropGuiController::PlaceHolderType::Graph);
98 98 ui->dragDropContainer->setMimeType(MIME_TYPE_GRAPH,
99 99 VisualizationDragDropContainer::DropBehavior::Inserted);
100 100 ui->dragDropContainer->setMimeType(
101 101 MIME_TYPE_VARIABLE_LIST, VisualizationDragDropContainer::DropBehavior::InsertedAndMerged);
102 102 ui->dragDropContainer->setMimeType(
103 103 MIME_TYPE_PRODUCT_LIST, VisualizationDragDropContainer::DropBehavior::InsertedAndMerged);
104 104 ui->dragDropContainer->setMimeType(MIME_TYPE_TIME_RANGE,
105 105 VisualizationDragDropContainer::DropBehavior::Merged);
106 106 ui->dragDropContainer->setMimeType(MIME_TYPE_ZONE,
107 107 VisualizationDragDropContainer::DropBehavior::Forbidden);
108 108 ui->dragDropContainer->setMimeType(MIME_TYPE_SELECTION_ZONE,
109 109 VisualizationDragDropContainer::DropBehavior::Forbidden);
110 110 ui->dragDropContainer->setAcceptMimeDataFunction([this](auto mimeData) {
111 111 return sqpApp->dragDropGuiController().checkMimeDataForVisualization(mimeData,
112 112 ui->dragDropContainer);
113 113 });
114 114
115 115 auto acceptDragWidgetFun = [](auto dragWidget, auto mimeData) {
116 116 if (!mimeData) {
117 117 return false;
118 118 }
119 119
120 120 if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
121 121 auto variables = sqpApp->variableController().variables(
122 122 Variable::variablesIDs(mimeData->data(MIME_TYPE_VARIABLE_LIST)));
123 123
124 124 if (variables.size() != 1) {
125 125 return false;
126 126 }
127 127 auto variable = variables.front();
128 128
129 129 if (auto graphWidget = dynamic_cast<const VisualizationGraphWidget *>(dragWidget)) {
130 130 return graphWidget->canDrop(*variable);
131 131 }
132 132 }
133 133
134 134 return true;
135 135 };
136 136 ui->dragDropContainer->setAcceptDragWidgetFunction(acceptDragWidgetFun);
137 137
138 138 connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccuredInContainer, this,
139 139 &VisualizationZoneWidget::dropMimeData);
140 140 connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccuredOnWidget, this,
141 141 &VisualizationZoneWidget::dropMimeDataOnGraph);
142 142
143 143 // 'Close' options : widget is deleted when closed
144 144 setAttribute(Qt::WA_DeleteOnClose);
145 145 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationZoneWidget::close);
146 146 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
147 147
148 148 // Synchronisation id
149 149 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronizationGroupId",
150 150 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
151 151 }
152 152
153 153 VisualizationZoneWidget::~VisualizationZoneWidget()
154 154 {
155 155 delete ui;
156 156 }
157 157
158 158 void VisualizationZoneWidget::setZoneRange(const DateTimeRange &range)
159 159 {
160 160 if (auto graph = firstGraph()) {
161 161 graph->setGraphRange(range);
162 162 }
163 163 else {
164 164 qCWarning(LOG_VisualizationZoneWidget())
165 165 << tr("setZoneRange:Cannot set the range of an empty zone.");
166 166 }
167 167 }
168 168
169 169 void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget)
170 170 {
171 171 // Synchronize new graph with others in the zone
172 172 // impl->m_Synchronizer->addGraph(*graphWidget);
173 173
174 174 // ui->dragDropContainer->addDragWidget(graphWidget);
175 175 insertGraph(0,graphWidget);
176 176
177 177 }
178 178
179 179 void VisualizationZoneWidget::insertGraph(int index, VisualizationGraphWidget *graphWidget)
180 180 {
181 181 DEPRECATE(
182 182 auto layout = ui->dragDropContainer->layout();
183 183 for(int i=0;i<layout->count();i++)
184 184 {
185 185 auto graph = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(i)->widget());
186 186 connect(graphWidget, &VisualizationGraphWidget::zoom_sig, graph, &VisualizationGraphWidget::zoom);
187 187 connect(graphWidget, &VisualizationGraphWidget::transform_sig, graph, &VisualizationGraphWidget::transform);
188 188
189 189 connect(graphWidget, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move_sig),
190 190 graph, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move));
191 191 connect(graphWidget, qOverload<double,double,bool>(&VisualizationGraphWidget::move_sig),
192 192 graph, qOverload<double,double,bool>(&VisualizationGraphWidget::move));
193 193
194 194 connect(graph, &VisualizationGraphWidget::zoom_sig, graphWidget, &VisualizationGraphWidget::zoom);
195 195 connect(graph, &VisualizationGraphWidget::transform_sig, graphWidget, &VisualizationGraphWidget::transform);
196 196
197 197 connect(graph, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move_sig),
198 198 graphWidget, qOverload<double,Qt::Orientation,bool>(&VisualizationGraphWidget::move));
199 199 connect(graph, qOverload<double,double,bool>(&VisualizationGraphWidget::move_sig),
200 200 graphWidget, qOverload<double,double,bool>(&VisualizationGraphWidget::move));
201 201 }
202 202 if(auto graph = firstGraph())
203 203 {
204 204 graphWidget->setGraphRange(graph->graphRange(), true);
205 205 }
206 206 )
207 207
208 208 // Synchronize new graph with others in the zone
209 209 impl->m_Synchronizer->addGraph(*graphWidget);
210 210
211 211 ui->dragDropContainer->insertDragWidget(index, graphWidget);
212 212
213 213 }
214 214
215 215 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable)
216 216 {
217 217 return createGraph(variable, -1);
218 218 }
219 219
220 220 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable,
221 221 int index)
222 222 {
223 223 auto graphWidget
224 224 = new VisualizationGraphWidget{defaultGraphName(*ui->dragDropContainer->layout()), this};
225 225
226 226
227 227 // Set graph properties
228 228 graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
229 229 graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT);
230 230
231 231
232 232 // Lambda to synchronize zone widget
233 233 auto synchronizeZoneWidget = [this, graphWidget](const DateTimeRange &graphRange,
234 234 const DateTimeRange &oldGraphRange) {
235 235
236 236 auto zoomType = DateTimeRangeHelper::getTransformationType(oldGraphRange, graphRange);
237 237 auto frameLayout = ui->dragDropContainer->layout();
238 238 for (auto i = 0; i < frameLayout->count(); ++i) {
239 239 auto graphChild
240 240 = dynamic_cast<VisualizationGraphWidget *>(frameLayout->itemAt(i)->widget());
241 241 if (graphChild && (graphChild != graphWidget)) {
242 242
243 243 auto graphChildRange = graphChild->graphRange();
244 244 switch (zoomType) {
245 245 case TransformationType::ZoomIn: {
246 246 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
247 247 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
248 248 graphChildRange.m_TStart += deltaLeft;
249 249 graphChildRange.m_TEnd -= deltaRight;
250 250 break;
251 251 }
252 252
253 253 case TransformationType::ZoomOut: {
254 254 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
255 255 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
256 256 graphChildRange.m_TStart -= deltaLeft;
257 257 graphChildRange.m_TEnd += deltaRight;
258 258 break;
259 259 }
260 260 case TransformationType::PanRight: {
261 261 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
262 262 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
263 263 graphChildRange.m_TStart += deltaLeft;
264 264 graphChildRange.m_TEnd += deltaRight;
265 265 break;
266 266 }
267 267 case TransformationType::PanLeft: {
268 268 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
269 269 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
270 270 graphChildRange.m_TStart -= deltaLeft;
271 271 graphChildRange.m_TEnd -= deltaRight;
272 272 break;
273 273 }
274 274 case TransformationType::Unknown: {
275 275 break;
276 276 }
277 277 default:
278 278 qCCritical(LOG_VisualizationZoneWidget())
279 279 << tr("Impossible to synchronize: zoom type not take into account");
280 280 // No action
281 281 break;
282 282 }
283 283 graphChild->setFlags(GraphFlag::DisableAll);
284 284 graphChild->setGraphRange(graphChildRange);
285 285 graphChild->setFlags(GraphFlag::EnableAll);
286 286 }
287 287 }
288 288 };
289 289
290 290 // connection for synchronization
291 291 connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget);
292 292 connect(graphWidget, &VisualizationGraphWidget::variableAdded, this,
293 293 &VisualizationZoneWidget::onVariableAdded);
294 294 connect(graphWidget, &VisualizationGraphWidget::variableAboutToBeRemoved, this,
295 295 &VisualizationZoneWidget::onVariableAboutToBeRemoved);
296 296
297 297 auto range = DateTimeRange{};
298 298 if (auto firstGraph = this->firstGraph()) {
299 299 // Case of a new graph in a existant zone
300 300 range = firstGraph->graphRange();
301 301 }
302 302 else {
303 303 // Case of a new graph as the first of the zone
304 304 range = variable->range();
305 305 }
306 306
307 307 this->insertGraph(index, graphWidget);
308 308
309 309 graphWidget->addVariable(variable, range);
310 310 graphWidget->setYRange(variable);
311 311
312 312 return graphWidget;
313 313 }
314 314
315 315 VisualizationGraphWidget *
316 316 VisualizationZoneWidget::createGraph(const std::vector<std::shared_ptr<Variable> > variables, int index)
317 317 {
318 318 if (variables.empty()) {
319 319 return nullptr;
320 320 }
321 321
322 322 auto graphWidget = createGraph(variables.front(), index);
323 323 for (auto variableIt = variables.cbegin() + 1; variableIt != variables.cend(); ++variableIt) {
324 324 graphWidget->addVariable(*variableIt, graphWidget->graphRange());
325 325 }
326 326
327 327 return graphWidget;
328 328 }
329 329
330 330 VisualizationGraphWidget *VisualizationZoneWidget::firstGraph() const
331 331 {
332 332 VisualizationGraphWidget *firstGraph = nullptr;
333 333 auto layout = ui->dragDropContainer->layout();
334 334 if (layout->count() > 0) {
335 335 if (auto visualizationGraphWidget
336 336 = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
337 337 firstGraph = visualizationGraphWidget;
338 338 }
339 339 }
340 340
341 341 return firstGraph;
342 342 }
343 343
344 344 void VisualizationZoneWidget::closeAllGraphs()
345 345 {
346 346 processGraphs(*ui->dragDropContainer->layout(),
347 347 [](VisualizationGraphWidget &graphWidget) { graphWidget.close(); });
348 348 }
349 349
350 350 void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor)
351 351 {
352 352 if (visitor) {
353 353 visitor->visitEnter(this);
354 354
355 355 // Apply visitor to graph children: widgets different from graphs are not visited (no
356 356 // action)
357 357 processGraphs(
358 358 *ui->dragDropContainer->layout(),
359 359 [visitor](VisualizationGraphWidget &graphWidget) { graphWidget.accept(visitor); });
360 360
361 361 visitor->visitLeave(this);
362 362 }
363 363 else {
364 364 qCCritical(LOG_VisualizationZoneWidget()) << tr("Can't visit widget : the visitor is null");
365 365 }
366 366 }
367 367
368 368 bool VisualizationZoneWidget::canDrop(const Variable &variable) const
369 369 {
370 370 // A tab can always accomodate a variable
371 371 Q_UNUSED(variable);
372 372 return true;
373 373 }
374 374
375 375 bool VisualizationZoneWidget::contains(const Variable &variable) const
376 376 {
377 377 Q_UNUSED(variable);
378 378 return false;
379 379 }
380 380
381 381 QString VisualizationZoneWidget::name() const
382 382 {
383 383 return ui->zoneNameLabel->text();
384 384 }
385 385
386 386 QMimeData *VisualizationZoneWidget::mimeData(const QPoint &position) const
387 387 {
388 388 Q_UNUSED(position);
389 389
390 390 auto mimeData = new QMimeData;
391 391 mimeData->setData(MIME_TYPE_ZONE, QByteArray{});
392 392
393 393 if (auto firstGraph = this->firstGraph()) {
394 394 auto timeRangeData = TimeController::mimeDataForTimeRange(firstGraph->graphRange());
395 395 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
396 396 }
397 397
398 398 return mimeData;
399 399 }
400 400
401 401 bool VisualizationZoneWidget::isDragAllowed() const
402 402 {
403 403 return true;
404 404 }
405 405
406 406 void VisualizationZoneWidget::notifyMouseMoveInGraph(const QPointF &graphPosition,
407 407 const QPointF &plotPosition,
408 408 VisualizationGraphWidget *graphWidget)
409 409 {
410 410 processGraphs(*ui->dragDropContainer->layout(), [&graphPosition, &plotPosition, &graphWidget](
411 411 VisualizationGraphWidget &processedGraph) {
412 412
413 413 switch (sqpApp->plotsCursorMode()) {
414 414 case SqpApplication::PlotsCursorMode::Vertical:
415 415 processedGraph.removeHorizontalCursor();
416 416 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
417 417 break;
418 418 case SqpApplication::PlotsCursorMode::Temporal:
419 419 processedGraph.addVerticalCursor(plotPosition.x());
420 420 processedGraph.removeHorizontalCursor();
421 421 break;
422 422 case SqpApplication::PlotsCursorMode::Horizontal:
423 423 processedGraph.removeVerticalCursor();
424 424 if (&processedGraph == graphWidget) {
425 425 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
426 426 }
427 427 else {
428 428 processedGraph.removeHorizontalCursor();
429 429 }
430 430 break;
431 431 case SqpApplication::PlotsCursorMode::Cross:
432 432 if (&processedGraph == graphWidget) {
433 433 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
434 434 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
435 435 }
436 436 else {
437 437 processedGraph.removeHorizontalCursor();
438 438 processedGraph.removeVerticalCursor();
439 439 }
440 440 break;
441 441 case SqpApplication::PlotsCursorMode::NoCursor:
442 442 processedGraph.removeHorizontalCursor();
443 443 processedGraph.removeVerticalCursor();
444 444 break;
445 445 }
446 446
447 447
448 448 });
449 449 }
450 450
451 451 void VisualizationZoneWidget::notifyMouseLeaveGraph(VisualizationGraphWidget *graphWidget)
452 452 {
453 453 processGraphs(*ui->dragDropContainer->layout(), [](VisualizationGraphWidget &processedGraph) {
454 454 processedGraph.removeHorizontalCursor();
455 455 processedGraph.removeVerticalCursor();
456 456 });
457 457 }
458 458
459 459 void VisualizationZoneWidget::closeEvent(QCloseEvent *event)
460 460 {
461 461 // Closes graphs in the zone
462 462 processGraphs(*ui->dragDropContainer->layout(),
463 463 [](VisualizationGraphWidget &graphWidget) { graphWidget.close(); });
464 464
465 465 // Delete synchronization group from variable controller
466 466 QMetaObject::invokeMethod(&sqpApp->variableController(), "onRemoveSynchronizationGroupId",
467 467 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
468 468
469 469 QWidget::closeEvent(event);
470 470 }
471 471
472 472 void VisualizationZoneWidget::onVariableAdded(std::shared_ptr<Variable> variable)
473 473 {
474 474 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronized",
475 475 Qt::QueuedConnection, Q_ARG(std::shared_ptr<Variable>, variable),
476 476 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
477 477 }
478 478
479 479 void VisualizationZoneWidget::onVariableAboutToBeRemoved(std::shared_ptr<Variable> variable)
480 480 {
481 481 QMetaObject::invokeMethod(&sqpApp->variableController(), "desynchronize", Qt::QueuedConnection,
482 482 Q_ARG(std::shared_ptr<Variable>, variable),
483 483 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
484 484 }
485 485
486 486 void VisualizationZoneWidget::dropMimeData(int index, const QMimeData *mimeData)
487 487 {
488 488 if (mimeData->hasFormat(MIME_TYPE_GRAPH)) {
489 489 impl->dropGraph(index, this);
490 490 }
491 491 else if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
492 492 auto variables = sqpApp->variableController().variables(
493 493 Variable::variablesIDs(mimeData->data(MIME_TYPE_VARIABLE_LIST)));
494 494 impl->dropVariables(variables, index, this);
495 495 }
496 496 else if (mimeData->hasFormat(MIME_TYPE_PRODUCT_LIST)) {
497 497 auto products = sqpApp->dataSourceController().productsDataForMimeData(
498 498 mimeData->data(MIME_TYPE_PRODUCT_LIST));
499 499 impl->dropProducts(products, index, this);
500 500 }
501 501 else {
502 502 qCWarning(LOG_VisualizationZoneWidget())
503 503 << tr("VisualizationZoneWidget::dropMimeData, unknown MIME data received.");
504 504 }
505 505 }
506 506
507 507 void VisualizationZoneWidget::dropMimeDataOnGraph(VisualizationDragWidget *dragWidget,
508 508 const QMimeData *mimeData)
509 509 {
510 510 auto graphWidget = qobject_cast<VisualizationGraphWidget *>(dragWidget);
511 511 if (!graphWidget) {
512 512 qCWarning(LOG_VisualizationZoneWidget())
513 513 << tr("VisualizationZoneWidget::dropMimeDataOnGraph, dropping in an unknown widget, "
514 514 "drop aborted");
515 515 Q_ASSERT(false);
516 516 return;
517 517 }
518 518
519 519 if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
520 520 auto variables = sqpApp->variableController().variables(
521 521 Variable::variablesIDs(mimeData->data(MIME_TYPE_VARIABLE_LIST)));
522 522 for (const auto &var : variables) {
523 523 graphWidget->addVariable(var, graphWidget->graphRange());
524 524 }
525 525 }
526 526 else if (mimeData->hasFormat(MIME_TYPE_PRODUCT_LIST)) {
527 527 auto products = sqpApp->dataSourceController().productsDataForMimeData(
528 528 mimeData->data(MIME_TYPE_PRODUCT_LIST));
529 529
530 530 auto context = new QObject{this};
531 auto range = TimeController::timeRangeForMimeData(mimeData->data(MIME_TYPE_TIME_RANGE));
532 // BTW this is really dangerous, this assumes the next created variable will be this one...
531 533 connect(&sqpApp->variableController(), &VariableController2::variableAdded, context,
532 [this, graphWidget, context](auto variable) {
533 graphWidget->addVariable(variable, graphWidget->graphRange());
534 delete context; // removes the connection
534 [this, graphWidget, context, range](auto variable) {
535 if(sqpApp->variableController().isReady(variable))
536 {
537 graphWidget->addVariable(variable, range);
538 delete context;
539 }
540 else
541 {
542 // -> this is pure insanity! this is a workaround to make a bad design work
543 QObject::connect(variable.get(), &Variable::updated,context,
544 [graphWidget, context, range, variable]()
545 {
546 graphWidget->addVariable(variable, range);
547 delete context;
548 });
549 }
550
535 551 },
536 552 Qt::QueuedConnection);
537 553
538 554 auto productData = products.first().toHash();
539 555 QMetaObject::invokeMethod(&sqpApp->dataSourceController(), "requestVariable",
540 556 Qt::QueuedConnection, Q_ARG(QVariantHash, productData));
541 557 }
542 558 else if (mimeData->hasFormat(MIME_TYPE_TIME_RANGE)) {
543 559 auto range = TimeController::timeRangeForMimeData(mimeData->data(MIME_TYPE_TIME_RANGE));
544 560 graphWidget->setGraphRange(range, true, true);
545 561 }
546 562 else {
547 563 qCWarning(LOG_VisualizationZoneWidget())
548 564 << tr("VisualizationZoneWidget::dropMimeDataOnGraph, unknown MIME data received.");
549 565 }
550 566 }
551 567
552 568 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropGraph(
553 569 int index, VisualizationZoneWidget *zoneWidget)
554 570 {
555 571 auto &helper = sqpApp->dragDropGuiController();
556 572
557 573 auto graphWidget = qobject_cast<VisualizationGraphWidget *>(helper.getCurrentDragWidget());
558 574 if (!graphWidget) {
559 575 qCWarning(LOG_VisualizationZoneWidget())
560 576 << tr("VisualizationZoneWidget::dropGraph, drop aborted, the dropped graph is not "
561 577 "found or invalid.");
562 578 Q_ASSERT(false);
563 579 return;
564 580 }
565 581
566 582 auto parentDragDropContainer
567 583 = qobject_cast<VisualizationDragDropContainer *>(graphWidget->parentWidget());
568 584 if (!parentDragDropContainer) {
569 585 qCWarning(LOG_VisualizationZoneWidget())
570 586 << tr("VisualizationZoneWidget::dropGraph, drop aborted, the parent container of "
571 587 "the dropped graph is not found.");
572 588 Q_ASSERT(false);
573 589 return;
574 590 }
575 591
576 592 const auto &variables = graphWidget->variables();
577 593
578 594 if (parentDragDropContainer != zoneWidget->ui->dragDropContainer && !variables.empty()) {
579 595 // The drop didn't occur in the same zone
580 596
581 597 // Abort the requests for the variables (if any)
582 598 // Commented, because it's not sure if it's needed or not
583 599 // for (const auto& var : variables)
584 600 //{
585 601 // sqpApp->variableController().onAbortProgressRequested(var);
586 602 //}
587 603
588 604 auto previousParentZoneWidget = graphWidget->parentZoneWidget();
589 605 auto nbGraph = parentDragDropContainer->countDragWidget();
590 606 if (nbGraph == 1) {
591 607 // This is the only graph in the previous zone, close the zone
592 608 helper.delayedCloseWidget(previousParentZoneWidget);
593 609 }
594 610 else {
595 611 // Close the graph
596 612 helper.delayedCloseWidget(graphWidget);
597 613 }
598 614
599 615 // Creates the new graph in the zone
600 616 auto newGraphWidget = zoneWidget->createGraph(variables, index);
601 617 newGraphWidget->addSelectionZones(graphWidget->selectionZoneRanges());
602 618 }
603 619 else {
604 620 // The drop occurred in the same zone or the graph is empty
605 621 // Simple move of the graph, no variable operation associated
606 622 parentDragDropContainer->layout()->removeWidget(graphWidget);
607 623
608 624 if (variables.empty() && parentDragDropContainer != zoneWidget->ui->dragDropContainer) {
609 625 // The graph is empty and dropped in a different zone.
610 626 // Take the range of the first graph in the zone (if existing).
611 627 auto layout = zoneWidget->ui->dragDropContainer->layout();
612 628 if (layout->count() > 0) {
613 629 if (auto visualizationGraphWidget
614 630 = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
615 631 graphWidget->setGraphRange(visualizationGraphWidget->graphRange());
616 632 }
617 633 }
618 634 }
619 635
620 636 zoneWidget->ui->dragDropContainer->insertDragWidget(index, graphWidget);
621 637 }
622 638 }
623 639
624 640 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropVariables(
625 641 const std::vector<std::shared_ptr<Variable> > &variables, int index,
626 642 VisualizationZoneWidget *zoneWidget)
627 643 {
628 644 // Note: the AcceptMimeDataFunction (set on the drop container) ensure there is a single and
629 645 // compatible variable here
630 646 if (variables.size() > 1) {
631 647 return;
632 648 }
633 649 zoneWidget->createGraph(variables, index);
634 650 }
635 651
636 652 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropProducts(
637 653 const QVariantList &productsData, int index, VisualizationZoneWidget *zoneWidget)
638 654 {
639 655 // Note: the AcceptMimeDataFunction (set on the drop container) ensure there is a single and
640 656 // compatible variable here
641 657 if (productsData.count() != 1) {
642 658 qCWarning(LOG_VisualizationZoneWidget())
643 659 << tr("VisualizationTabWidget::dropProducts, dropping multiple products, operation "
644 660 "aborted.");
645 661 return;
646 662 }
647 663
648 664 auto context = new QObject{zoneWidget};
649 665 connect(&sqpApp->variableController(), &VariableController2::variableAdded, context,
650 666 [this, index, zoneWidget, context](auto variable) {
651 667 zoneWidget->createGraph(variable, index);
652 668 delete context; // removes the connection
653 669 },
654 670 Qt::QueuedConnection);
655 671
656 672 auto productData = productsData.first().toHash();
657 673 QMetaObject::invokeMethod(&sqpApp->dataSourceController(), "requestVariable",
658 674 Qt::QueuedConnection, Q_ARG(QVariantHash, productData));
659 675 }
General Comments 0
You need to be logged in to leave comments. Login now