##// END OF EJS Templates
Refac + clang-format...
jeandet -
r1365:19075a214d07
parent child
Show More
@@ -1,26 +1,26
1 1 ---
2 2 # See http://clang.llvm.org/docs/ClangFormatStyleOptions.html for a definition
3 3 # of the options
4 4 Language: Cpp
5 BasedOnStyle: LLVM
5 BasedOnStyle: WebKit
6 6
7 7 # Line length
8 8 ColumnLimit: 100
9 9
10 10 # Indent with 4 spaces
11 11 IndentWidth: 4
12 12 AccessModifierOffset: -4 # -IndentWidth
13 13 ConstructorInitializerIndentWidth: 8 # 2 * IndentWidth
14 14
15 # Break only before function braces
16 BreakBeforeBraces: Stroustrup
15 BreakBeforeBraces: Allman
17 16
18 17 AllowShortFunctionsOnASingleLine: Inline
19 18 AlwaysBreakTemplateDeclarations: true
20 19 AlwaysBreakBeforeMultilineStrings: true
21 20 BreakBeforeBinaryOperators: true
22 21 ConstructorInitializerAllOnOneLineOrOnePerLine: true
23 22 IndentCaseLabels: true
24 23 MaxEmptyLinesToKeep: 2
25 Standard: Cpp03
24 Standard: Cpp11
25 UseTab: Never
26 26
@@ -1,84 +1,84
1 1 cmake_minimum_required(VERSION 3.6)
2 2 project(SciQLOP CXX)
3 3
4 4 include(GNUInstallDirs)
5 5
6 6 SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_LIST_DIR}/cmake")
7 7
8 8 OPTION (CPPCHECK "Analyzes the source code with cppcheck" OFF)
9 9 OPTION (CLANG_TIDY "Analyzes the source code with Clang Tidy" OFF)
10 10 OPTION (IWYU "Analyzes the source code with Include What You Use" OFF)
11 11 OPTION (Coverage "Enables code coverage" OFF)
12 12
13 13 set(CMAKE_CXX_STANDARD 17)
14 14
15 15 set(CMAKE_AUTOMOC ON)
16 16 #https://gitlab.kitware.com/cmake/cmake/issues/15227
17 17 #set(CMAKE_AUTOUIC ON)
18 18 if(POLICY CMP0071)
19 19 cmake_policy(SET CMP0071 OLD)
20 20 endif()
21 21 set(CMAKE_AUTORCC ON)
22 22 set(CMAKE_INCLUDE_CURRENT_DIR ON)
23 23
24 24 if(NOT DEFINED CMAKE_INSTALL_RPATH_USE_LINK_PATH)
25 25 set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
26 26 endif()
27 27 if(NOT DEFINED CMAKE_MACOSX_RPATH)
28 28 set(CMAKE_MACOSX_RPATH TRUE)
29 29 endif()
30 30
31 31 if(NOT CMAKE_BUILD_TYPE)
32 32 set(CMAKE_BUILD_TYPE "Release" CACHE STRING "" FORCE)
33 33 endif()
34 set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0")
34 set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -g3")
35 35
36 36 find_package(Qt5 COMPONENTS Core Widgets Network PrintSupport Svg Test REQUIRED)
37 37
38 38 IF(CPPCHECK)
39 39 set(CMAKE_CXX_CPPCHECK "cppcheck;--enable=warning,style")
40 40 ENDIF(CPPCHECK)
41 41
42 42 IF(CLANG_TIDY)
43 43 set(CMAKE_CXX_CLANG_TIDY "clang-tidy;-style=file;-checks=*")
44 44 ENDIF(CLANG_TIDY)
45 45
46 46 IF(IWYU)
47 47 set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "include-what-you-use")
48 48 ENDIF(IWYU)
49 49
50 50 IF(Coverage)
51 51 set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -g -O0 -Wall -W -Wshadow -Wunused-variable -Wunused-parameter -Wunused-function -Wunused -Wno-system-headers -Wno-deprecated -Woverloaded-virtual -Wwrite-strings -fprofile-arcs -ftest-coverage")
52 52 set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -g -O0 -Wall -W -Wshadow -Wunused-variable \
53 53 -Wunused-parameter -Wunused-function -Wunused -Wno-system-headers \
54 54 -Wno-deprecated -Woverloaded-virtual -Wwrite-strings -fprofile-arcs -ftest-coverage")
55 55 set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fprofile-arcs -ftest-coverage")
56 56
57 57 add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/gcov.html
58 58 COMMAND gcovr --exclude='.*Test.*' --exclude='.*external.*' --object-directory ${CMAKE_BINARY_DIR} -r ${CMAKE_SOURCE_DIR} --html --html-details -o ${CMAKE_CURRENT_BINARY_DIR}/gcov.html
59 59 )
60 60 add_custom_target(gcovr
61 61 DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gcov.html gcovr
62 62 )
63 63 add_custom_target(show_coverage
64 64 COMMAND xdg-open ${CMAKE_CURRENT_BINARY_DIR}/gcov.html
65 65 DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/gcov.html gcovr
66 66 )
67 67 ENDIF(Coverage)
68 68
69 69 enable_testing()
70 70
71 71 find_package(SciQLOPCore CONFIG QUIET)
72 72 if (NOT SciQLOPCore_FOUND)
73 73 if(NOT IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/core)
74 74 message("Init submodule Core")
75 75 execute_process(COMMAND git submodule init core WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
76 76 execute_process(COMMAND git submodule update core WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
77 77 endif()
78 78 add_subdirectory(core)
79 79 endif()
80 80
81 81 add_subdirectory(gui)
82 82 add_subdirectory(app)
83 83 add_subdirectory(plugins)
84 84 #add_subdirectory(docs)
@@ -1,1286 +1,1319
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
27 27 #include <unordered_map>
28 28
29 29 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
30 30
31 31 namespace {
32 32
33 33 /// Key pressed to enable drag&drop in all modes
34 34 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
35 35
36 36 /// Key pressed to enable zoom on horizontal axis
37 37 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
38 38
39 39 /// Key pressed to enable zoom on vertical axis
40 40 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
41 41
42 42 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
43 43 const auto PAN_SPEED = 5;
44 44
45 45 /// Key pressed to enable a calibration pan
46 46 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
47 47
48 48 /// Key pressed to enable multi selection of selection zones
49 49 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
50 50
51 51 /// Minimum size for the zoom box, in percentage of the axis range
52 52 const auto ZOOM_BOX_MIN_SIZE = 0.8;
53 53
54 54 /// Format of the dates appearing in the label of a cursor
55 55 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
56 56
57 57 } // namespace
58 58
59 59 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
60 60
61 61 explicit VisualizationGraphWidgetPrivate(const QString &name)
62 62 : m_Name{name},
63 63 m_Flags{GraphFlag::EnableAll},
64 64 m_IsCalibration{false},
65 65 m_RenderingDelegate{nullptr}
66 66 {
67 67 m_plot = new QCustomPlot();
68 68 // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable.
69 69 m_plot->setPlottingHint(QCP::phFastPolylines, true);
70 70 }
71 71
72 72 void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
73 73 const DateTimeRange &range)
74 74 {
75 75 VisualizationGraphHelper::updateData(plottables, variable, range);
76 76
77 77 // Prevents that data has changed to update rendering
78 78 m_RenderingDelegate->onPlotUpdated();
79 79 }
80 80
81 81 QString m_Name;
82 82 // 1 variable -> n qcpplot
83 83 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
84 84 GraphFlags m_Flags;
85 85 bool m_IsCalibration;
86 86 QCustomPlot* m_plot;
87 87 QPoint m_lastMousePos;
88 88 /// Delegate used to attach rendering features to the plot
89 89 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
90 90
91 91 QCPItemRect* m_DrawingZoomRect = nullptr;
92 92 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
93 93
94 94 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
95 95 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
96 96
97 97 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
98 98 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
99 99 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
100 100
101 101 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
102 102
103 103 bool m_VariableAutoRangeOnInit = true;
104 104
105 105 inline void updateMousePosition(const QPoint& position)
106 106 {
107 107 m_lastMousePos = position;
108 108 }
109 109
110 110 inline bool isDrawingZoomRect(){return m_DrawingZoomRect!=nullptr;}
111 111 void updateZoomRect(const QPoint& newPos)
112 112 {
113 113 QPointF pos{m_plot->xAxis->pixelToCoord(newPos.x()), m_plot->yAxis->pixelToCoord(newPos.y())};
114 114 m_DrawingZoomRect->bottomRight->setCoords(pos);
115 115 m_plot->replot(QCustomPlot::rpQueuedReplot);
116 116 }
117 117
118 118 void applyZoomRect()
119 119 {
120 120 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
121 121 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
122 122
123 123 auto newAxisXRange = QCPRange{m_DrawingZoomRect->topLeft->coords().x(),
124 124 m_DrawingZoomRect->bottomRight->coords().x()};
125 125
126 126 auto newAxisYRange = QCPRange{m_DrawingZoomRect->topLeft->coords().y(),
127 127 m_DrawingZoomRect->bottomRight->coords().y()};
128 128
129 129 removeDrawingRect();
130 130
131 131 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
132 132 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
133 133 m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
134 134 axisX->setRange(newAxisXRange);
135 135 axisY->setRange(newAxisYRange);
136 136
137 137 m_plot->replot(QCustomPlot::rpQueuedReplot);
138 138 }
139 139 }
140 140
141 141 inline bool isDrawingZoneRect(){return m_DrawingZone!=nullptr;}
142 142 void updateZoneRect(const QPoint& newPos)
143 143 {
144 144 m_DrawingZone->setEnd(m_plot->xAxis->pixelToCoord(newPos.x()));
145 145 m_plot->replot(QCustomPlot::rpQueuedReplot);
146 146 }
147 147
148 148 void startDrawingRect(const QPoint &pos)
149 149 {
150 150 removeDrawingRect();
151 151
152 152 auto axisPos = posToAxisPos(pos);
153 153
154 154 m_DrawingZoomRect = new QCPItemRect{m_plot};
155 155 QPen p;
156 156 p.setWidth(2);
157 157 m_DrawingZoomRect->setPen(p);
158 158
159 159 m_DrawingZoomRect->topLeft->setCoords(axisPos);
160 160 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
161 161 }
162 162
163 163 void removeDrawingRect()
164 164 {
165 165 if (m_DrawingZoomRect) {
166 166 m_plot->removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
167 167 m_DrawingZoomRect = nullptr;
168 168 m_plot->replot(QCustomPlot::rpQueuedReplot);
169 169 }
170 170 }
171 171
172 void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph)
172 void selectZone(const QPoint &pos)
173 173 {
174 endDrawingZone(graph);
174 auto zoneAtPos = selectionZoneAt(pos);
175 setSelectionZonesEditionEnabled(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones);
176 }
177
178 void startDrawingZone(const QPoint &pos)
179 {
180 endDrawingZone();
175 181
176 182 auto axisPos = posToAxisPos(pos);
177 183
178 184 m_DrawingZone = new VisualizationSelectionZoneItem{m_plot};
179 185 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
180 186 m_DrawingZone->setEditionEnabled(false);
181 187 }
182 188
183 void endDrawingZone(VisualizationGraphWidget *graph)
189 void endDrawingZone()
184 190 {
185 191 if (m_DrawingZone) {
186 192 auto drawingZoneRange = m_DrawingZone->range();
187 193 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
188 194 m_DrawingZone->setEditionEnabled(true);
189 195 addSelectionZone(m_DrawingZone);
190 196 }
191 197 else {
192 graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot
198 m_plot->removeItem(m_DrawingZone);
193 199 }
194 200
195 graph->plot().replot(QCustomPlot::rpQueuedReplot);
201 m_plot->replot(QCustomPlot::rpQueuedReplot);
196 202 m_DrawingZone = nullptr;
197 203 }
198 204 }
199 205
206 void moveSelectionZone(const QPoint& destination)
207 {
208 /*
209 * I give up on this for now
210 * @TODO implement this, the difficulty is that selection zones have their own
211 * event handling code which seems to rely on QCP GUI event handling propagation
212 * which was a realy bad design choice.
213 */
214 }
215
200 216 void setSelectionZonesEditionEnabled(bool value)
201 217 {
202 218 for (auto s : m_SelectionZones) {
203 219 s->setEditionEnabled(value);
204 220 }
205 221 }
206 222
207 223 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
208 224
209 225 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos) const
210 226 {
211 227 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
212 228 auto minDistanceToZone = -1;
213 229 for (auto zone : m_SelectionZones) {
214 230 auto distanceToZone = zone->selectTest(pos, false);
215 231 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
216 232 && distanceToZone >= 0 && distanceToZone < m_plot->selectionTolerance()) {
217 233 selectionZoneItemUnderCursor = zone;
218 234 }
219 235 }
220 236
221 237 return selectionZoneItemUnderCursor;
222 238 }
223 239
224 240 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
225 241 const QCustomPlot &plot) const
226 242 {
227 243 QVector<VisualizationSelectionZoneItem *> zones;
228 244 for (auto zone : m_SelectionZones) {
229 245 auto distanceToZone = zone->selectTest(pos, false);
230 246 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
231 247 zones << zone;
232 248 }
233 249 }
234 250
235 251 return zones;
236 252 }
237 253
238 254 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
239 255 {
240 256 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
241 257 zone->moveToTop();
242 258 m_SelectionZones.removeAll(zone);
243 259 m_SelectionZones.append(zone);
244 260 }
245 261 }
246 262
247 263 QPointF posToAxisPos(const QPoint &pos) const
248 264 {
249 265 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
250 266 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
251 267 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
252 268 }
253 269
254 270 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
255 271 {
256 272 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
257 273 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
258 274 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
259 275 }
260 276
261 277 inline QCPRange _pixDistanceToRange(double pos1, double pos2, QCPAxis *axis)
262 278 {
263 279 if (axis->scaleType() == QCPAxis::stLinear)
264 280 {
265 281 auto diff = axis->pixelToCoord(pos1) - axis->pixelToCoord(pos2);
266 282 return QCPRange{axis->range().lower + diff, axis->range().upper + diff};
267 283 }
268 284 else
269 285 {
270 286 auto diff = axis->pixelToCoord(pos1) / axis->pixelToCoord(pos2);
271 287 return QCPRange{axis->range().lower * diff, axis->range().upper * diff};
272 288 }
273 289 }
274 290
275 291 void setRange(const QCPRange &newRange)
276 292 {
277 293 auto graphRange = DateTimeRange{newRange.lower, newRange.upper};
278 294
279 295 if (m_Flags.testFlag(GraphFlag::EnableAcquisition))
280 296 {
281 297 for (auto it = m_VariableToPlotMultiMap.begin(),
282 298 end = m_VariableToPlotMultiMap.end();
283 299 it != end; it = m_VariableToPlotMultiMap.upper_bound(it->first))
284 300 {
285 301 sqpApp->variableController().asyncChangeRange(it->first, graphRange);
286 302 }
287 303 }
288 304 }
289 305
290 306 void moveGraph(const QPoint& destination)
291 307 {
292 308 auto currentPos = destination;
293 309 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
294 310 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
295 311 xAxis->setRange(_pixDistanceToRange(m_lastMousePos.x(), currentPos.x(), xAxis));
296 312 yAxis->setRange(_pixDistanceToRange(m_lastMousePos.y(), currentPos.y(), yAxis));
297 313 setRange(xAxis->range());
298 314 m_plot->replot(QCustomPlot::rpQueuedReplot);
299 315 m_lastMousePos = destination;
300 316 }
301 317 };
302 318
303 319 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
304 320 : VisualizationDragWidget{parent},
305 321 ui{new Ui::VisualizationGraphWidget},
306 322 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
307 323 {
308 324 ui->setupUi(this);
309 325 this->layout()->addWidget(impl->m_plot);
310 326 // 'Close' options : widget is deleted when closed
311 327 setAttribute(Qt::WA_DeleteOnClose);
312 328
313 329 // The delegate must be initialized after the ui as it uses the plot
314 330 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
315 331
316 332 // Init the cursors
317 333 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
318 334 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
319 335 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
320 336 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
321 337
322 338 this->setFocusPolicy(Qt::WheelFocus);
323 339 this->setMouseTracking(true);
324 340 impl->m_plot->setAttribute(Qt::WA_TransparentForMouseEvents);
325 341 impl->m_plot->setContextMenuPolicy(Qt::CustomContextMenu);
326 342 impl->m_plot->setParent(this);
327 343 }
328 344
329 345
330 346 VisualizationGraphWidget::~VisualizationGraphWidget()
331 347 {
332 348 delete ui;
333 349 }
334 350
335 351 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
336 352 {
337 353 auto parent = parentWidget();
338 354 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
339 355 parent = parent->parentWidget();
340 356 }
341 357
342 358 return qobject_cast<VisualizationZoneWidget *>(parent);
343 359 }
344 360
345 361 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
346 362 {
347 363 auto parent = parentWidget();
348 364 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
349 365 parent = parent->parentWidget();
350 366 }
351 367
352 368 return qobject_cast<VisualizationWidget *>(parent);
353 369 }
354 370
355 371 void VisualizationGraphWidget::setFlags(GraphFlags flags)
356 372 {
357 373 impl->m_Flags = std::move(flags);
358 374 }
359 375
360 376 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, DateTimeRange range)
361 377 {
362 378 // Uses delegate to create the qcpplot components according to the variable
363 379 auto createdPlottables = VisualizationGraphHelper::create(variable, *impl->m_plot);
364 380
365 381 // Sets graph properties
366 382 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
367 383
368 384 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
369 385
370 386 // If the variable already has its data loaded, load its units and its range in the graph
371 387 if (variable->dataSeries() != nullptr) {
372 388 impl->m_RenderingDelegate->setAxesUnits(*variable);
373 389 this->setFlags(GraphFlag::DisableAll);
374 390 setGraphRange(range);
375 391 this->setFlags(GraphFlag::EnableAll);
376 392 }
377 393 //@TODO this is bad! when variable is moved to another graph it still fires
378 394 // even if this has been deleted
379 395 connect(variable.get(), &Variable::updated, this, &VisualizationGraphWidget::variableUpdated);
380 396 this->onUpdateVarDisplaying(variable, range); // My bullshit
381 397 emit variableAdded(variable);
382 398 }
383 399
384 400 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
385 401 {
386 402 // Each component associated to the variable :
387 403 // - is removed from qcpplot (which deletes it)
388 404 // - is no longer referenced in the map
389 405 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
390 406 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
391 407 emit variableAboutToBeRemoved(variable);
392 408
393 409 auto &plottablesMap = variableIt->second;
394 410
395 411 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
396 412 plottableIt != plottableEnd;) {
397 413 impl->m_plot->removePlottable(plottableIt->second);
398 414 plottableIt = plottablesMap.erase(plottableIt);
399 415 }
400 416
401 417 impl->m_VariableToPlotMultiMap.erase(variableIt);
402 418 }
403 419
404 420 // Updates graph
405 421 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
406 422 }
407 423
408 424 std::vector<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
409 425 {
410 426 auto variables = std::vector<std::shared_ptr<Variable> >{};
411 427 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
412 428 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
413 429 variables.push_back(it->first);
414 430 }
415 431
416 432 return variables;
417 433 }
418 434
419 435 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
420 436 {
421 437 if (!variable) {
422 438 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
423 439 return;
424 440 }
425 441
426 442 VisualizationGraphHelper::setYAxisRange(variable, *impl->m_plot);
427 443 }
428 444
429 445 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
430 446 {
431 447 auto graphRange = impl->m_plot->xAxis->range();
432 448 return DateTimeRange{graphRange.lower, graphRange.upper};
433 449 }
434 450
435 451 void VisualizationGraphWidget::setGraphRange(const DateTimeRange &range, bool calibration)
436 452 {
437 453 if (calibration) {
438 454 impl->m_IsCalibration = true;
439 455 }
440 456
441 457 impl->m_plot->xAxis->setRange(range.m_TStart, range.m_TEnd);
442 458 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
443 459
444 460 if (calibration) {
445 461 impl->m_IsCalibration = false;
446 462 }
447 463 }
448 464
449 465 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
450 466 {
451 467 impl->m_VariableAutoRangeOnInit = value;
452 468 }
453 469
454 470 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
455 471 {
456 472 QVector<DateTimeRange> ranges;
457 473 for (auto zone : impl->m_SelectionZones) {
458 474 ranges << zone->range();
459 475 }
460 476
461 477 return ranges;
462 478 }
463 479
464 480 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange> &ranges)
465 481 {
466 482 for (const auto &range : ranges) {
467 483 // note: ownership is transfered to QCustomPlot
468 484 auto zone = new VisualizationSelectionZoneItem(&plot());
469 485 zone->setRange(range.m_TStart, range.m_TEnd);
470 486 impl->addSelectionZone(zone);
471 487 }
472 488
473 489 plot().replot(QCustomPlot::rpQueuedReplot);
474 490 }
475 491
476 492 VisualizationSelectionZoneItem *
477 493 VisualizationGraphWidget::addSelectionZone(const QString &name, const DateTimeRange &range)
478 494 {
479 495 // note: ownership is transfered to QCustomPlot
480 496 auto zone = new VisualizationSelectionZoneItem(&plot());
481 497 zone->setName(name);
482 498 zone->setRange(range.m_TStart, range.m_TEnd);
483 499 impl->addSelectionZone(zone);
484 500
485 501 plot().replot(QCustomPlot::rpQueuedReplot);
486 502
487 503 return zone;
488 504 }
489 505
490 506 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
491 507 {
492 508 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
493 509
494 510 if (impl->m_HoveredZone == selectionZone) {
495 511 impl->m_HoveredZone = nullptr;
496 512 setCursor(Qt::ArrowCursor);
497 513 }
498 514
499 515 impl->m_SelectionZones.removeAll(selectionZone);
500 516 plot().removeItem(selectionZone);
501 517 plot().replot(QCustomPlot::rpQueuedReplot);
502 518 }
503 519
504 520 void VisualizationGraphWidget::undoZoom()
505 521 {
506 522 auto zoom = impl->m_ZoomStack.pop();
507 523 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
508 524 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
509 525
510 526 axisX->setRange(zoom.first);
511 527 axisY->setRange(zoom.second);
512 528
513 529 plot().replot(QCustomPlot::rpQueuedReplot);
514 530 }
515 531
516 532 void VisualizationGraphWidget::zoom(double factor, int center, Qt::Orientation orientation)
517 533
518 534 {
519 535 QCPAxis *axis = impl->m_plot->axisRect()->rangeZoomAxis(orientation);
520 536 axis->scaleRange(factor, axis->pixelToCoord(center));
521 537 if (orientation == Qt::Horizontal)
522 538 impl->setRange(axis->range());
523 539 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
524 540 }
525 541
526 542 void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation)
527 543 {
528 544 auto oldRange = impl->m_plot->xAxis->range();
529 545 QCPAxis *axis = impl->m_plot->axisRect()->rangeDragAxis(orientation);
530 546 if (impl->m_plot->xAxis->scaleType() == QCPAxis::stLinear) {
531 547 double rg = (axis->range().upper - axis->range().lower) * (factor / 10);
532 548 axis->setRange(axis->range().lower + (rg), axis->range().upper + (rg));
533 549 }
534 550 else if (impl->m_plot->xAxis->scaleType() == QCPAxis::stLogarithmic) {
535 551 int start = 0, stop = 0;
536 552 double diff = 0.;
537 553 if (factor > 0.0) {
538 554 stop = this->width() * factor / 10;
539 555 start = 2 * this->width() * factor / 10;
540 556 }
541 557 if (factor < 0.0) {
542 558 factor *= -1.0;
543 559 start = this->width() * factor / 10;
544 560 stop = 2 * this->width() * factor / 10;
545 561 }
546 562 diff = axis->pixelToCoord(start) / axis->pixelToCoord(stop);
547 563 axis->setRange(impl->m_plot->axisRect()->rangeDragAxis(orientation)->range().lower * diff,
548 564 impl->m_plot->axisRect()->rangeDragAxis(orientation)->range().upper * diff);
549 565 }
550 566 if (orientation == Qt::Horizontal)
551 567 impl->setRange(axis->range());
552 568 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
553 569 }
554 570
555 571 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
556 572 {
557 573 if (visitor) {
558 574 visitor->visit(this);
559 575 }
560 576 else {
561 577 qCCritical(LOG_VisualizationGraphWidget())
562 578 << tr("Can't visit widget : the visitor is null");
563 579 }
564 580 }
565 581
566 582 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
567 583 {
568 584 auto isSpectrogram = [](const auto &variable) {
569 585 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
570 586 };
571 587
572 588 // - A spectrogram series can't be dropped on graph with existing plottables
573 589 // - No data series can be dropped on graph with existing spectrogram series
574 590 return isSpectrogram(variable)
575 591 ? impl->m_VariableToPlotMultiMap.empty()
576 592 : std::none_of(
577 593 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
578 594 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
579 595 }
580 596
581 597 bool VisualizationGraphWidget::contains(const Variable &variable) const
582 598 {
583 599 // Finds the variable among the keys of the map
584 600 auto variablePtr = &variable;
585 601 auto findVariable
586 602 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
587 603
588 604 auto end = impl->m_VariableToPlotMultiMap.cend();
589 605 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
590 606 return it != end;
591 607 }
592 608
593 609 QString VisualizationGraphWidget::name() const
594 610 {
595 611 return impl->m_Name;
596 612 }
597 613
598 614 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
599 615 {
600 616 auto mimeData = new QMimeData;
601 617
602 618 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position);
603 619 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
604 620 && selectionZoneItemUnderCursor) {
605 621 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
606 622 selectionZoneItemUnderCursor->range()));
607 623 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
608 624 selectionZoneItemUnderCursor->range()));
609 625 }
610 626 else {
611 627 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
612 628
613 629 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
614 630 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
615 631 }
616 632
617 633 return mimeData;
618 634 }
619 635
620 636 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
621 637 {
622 638 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition);
623 639 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
624 640 && selectionZoneItemUnderCursor) {
625 641
626 642 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
627 643 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
628 644
629 645 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
630 646 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
631 647 .toSize();
632 648
633 649 auto pixmap = QPixmap(zoneSize);
634 650 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
635 651
636 652 return pixmap;
637 653 }
638 654
639 655 return QPixmap();
640 656 }
641 657
642 658 bool VisualizationGraphWidget::isDragAllowed() const
643 659 {
644 660 return true;
645 661 }
646 662
647 663 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
648 664 {
649 665 if (highlighted) {
650 666 plot().setBackground(QBrush(QColor("#BBD5EE")));
651 667 }
652 668 else {
653 669 plot().setBackground(QBrush(Qt::white));
654 670 }
655 671
656 672 plot().update();
657 673 }
658 674
659 675 void VisualizationGraphWidget::addVerticalCursor(double time)
660 676 {
661 677 impl->m_VerticalCursor->setPosition(time);
662 678 impl->m_VerticalCursor->setVisible(true);
663 679
664 680 auto text
665 681 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
666 682 impl->m_VerticalCursor->setLabelText(text);
667 683 }
668 684
669 685 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
670 686 {
671 687 impl->m_VerticalCursor->setAbsolutePosition(position);
672 688 impl->m_VerticalCursor->setVisible(true);
673 689
674 690 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
675 691 auto text
676 692 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
677 693 impl->m_VerticalCursor->setLabelText(text);
678 694 }
679 695
680 696 void VisualizationGraphWidget::removeVerticalCursor()
681 697 {
682 698 impl->m_VerticalCursor->setVisible(false);
683 699 plot().replot(QCustomPlot::rpQueuedReplot);
684 700 }
685 701
686 702 void VisualizationGraphWidget::addHorizontalCursor(double value)
687 703 {
688 704 impl->m_HorizontalCursor->setPosition(value);
689 705 impl->m_HorizontalCursor->setVisible(true);
690 706 impl->m_HorizontalCursor->setLabelText(QString::number(value));
691 707 }
692 708
693 709 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
694 710 {
695 711 impl->m_HorizontalCursor->setAbsolutePosition(position);
696 712 impl->m_HorizontalCursor->setVisible(true);
697 713
698 714 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
699 715 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
700 716 }
701 717
702 718 void VisualizationGraphWidget::removeHorizontalCursor()
703 719 {
704 720 impl->m_HorizontalCursor->setVisible(false);
705 721 plot().replot(QCustomPlot::rpQueuedReplot);
706 722 }
707 723
708 724 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
709 725 {
710 726 Q_UNUSED(event);
711 727
712 728 for (auto i : impl->m_SelectionZones) {
713 729 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
714 730 }
715 731
716 732 // Prevents that all variables will be removed from graph when it will be closed
717 733 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
718 734 emit variableAboutToBeRemoved(variableEntry.first);
719 735 }
720 736 }
721 737
722 738 void VisualizationGraphWidget::enterEvent(QEvent *event)
723 739 {
724 740 Q_UNUSED(event);
725 741 impl->m_RenderingDelegate->showGraphOverlay(true);
726 742 }
727 743
728 744 void VisualizationGraphWidget::leaveEvent(QEvent *event)
729 745 {
730 746 Q_UNUSED(event);
731 747 impl->m_RenderingDelegate->showGraphOverlay(false);
732 748
733 749 if (auto parentZone = parentZoneWidget()) {
734 750 parentZone->notifyMouseLeaveGraph(this);
735 751 }
736 752 else {
737 753 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
738 754 }
739 755
740 756 if (impl->m_HoveredZone) {
741 757 impl->m_HoveredZone->setHovered(false);
742 758 impl->m_HoveredZone = nullptr;
743 759 }
744 760 }
745 761
746 762 void VisualizationGraphWidget::wheelEvent(QWheelEvent *event)
747 763 {
748 764 double factor;
749 765 double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually
750 766 if (event->modifiers() == Qt::ControlModifier) {
751 767 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
752 768 {
753 769 setCursor(Qt::SizeVerCursor);
754 770 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps);
755 771 zoom(factor, event->pos().y(), Qt::Vertical);
756 772 }
757 773 }
758 774 else if (event->modifiers() == Qt::ShiftModifier) {
759 775 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
760 776 {
761 777 setCursor(Qt::SizeHorCursor);
762 778 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps);
763 779 zoom(factor, event->pos().x(), Qt::Horizontal);
764 780 }
765 781 }
766 782 else {
767 783 move(wheelSteps, Qt::Horizontal);
768 784 }
769 785 QWidget::wheelEvent(event);
770 786 }
771 787
772 788
773 789
774 790 void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent *event)
775 791 {
776 792 if(impl->isDrawingZoomRect())
777 793 {
778 794 impl->updateZoomRect(event->pos());
779 795 }
780 796 else if (impl->isDrawingZoneRect())
781 797 {
782 798 impl->updateZoneRect(event->pos());
783 799 }
784 800 else if (event->buttons() == Qt::LeftButton)
785 801 {
786 impl->moveGraph(event->pos());
802 switch (sqpApp->plotsInteractionMode())
803 {
804 case SqpApplication::PlotsInteractionMode::None:
805 impl->moveGraph(event->pos());
806 break;
807 case SqpApplication::PlotsInteractionMode::SelectionZones:
808
809 break;
810 default:
811 break;
812 }
787 813 }
788 814 else
789 815 {
790 816 impl->m_RenderingDelegate->updateTooltip(event);
791 817 }
792 event->accept();
793 818 QWidget::mouseMoveEvent(event);
794 819 }
795 820
796 821 void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent *event)
797 822 {
798 823 if(impl->isDrawingZoomRect())
799 824 {
800 825 impl->applyZoomRect();
801 826 }
802 827 else if(impl->isDrawingZoneRect())
803 828 {
804 impl->endDrawingZone(this);
829 impl->endDrawingZone();
805 830 }
806 831 else
807 832 {
808 833 setCursor(Qt::ArrowCursor);
809 834 }
810 835 QWidget::mouseReleaseEvent(event);
811 836 }
812 837
813 838 void VisualizationGraphWidget::mousePressEvent(QMouseEvent *event)
814 839 {
815 if (event->button() == Qt::LeftButton) {
816 if (event->modifiers() == Qt::ControlModifier) {
817 }
818 else if (event->modifiers() == Qt::AltModifier) {
819
820 }
821 else
840 if (event->button()==Qt::RightButton)
841 {
842 onGraphMenuRequested(event->pos());
843 }
844 else
845 {
846 auto selectedZone = impl->selectionZoneAt(event->pos());
847 switch (sqpApp->plotsInteractionMode())
822 848 {
823 switch (sqpApp->plotsInteractionMode())
849 case SqpApplication::PlotsInteractionMode::DragAndDrop :
850 break;
851 case SqpApplication::PlotsInteractionMode::SelectionZones :
852 impl->setSelectionZonesEditionEnabled(true);
853 if ((event->modifiers() == Qt::ControlModifier) && (selectedZone != nullptr))
824 854 {
825 case SqpApplication::PlotsInteractionMode::DragAndDrop :
826 break;
827 case SqpApplication::PlotsInteractionMode::SelectionZones :
828 if (!impl->selectionZoneAt(event->pos())) {
829 impl->startDrawingZone(event->pos(), this);
855 selectedZone->setAssociatedEditedZones(parentVisualizationWidget()->selectionZoneManager().selectedItems());
856 }
857 else
858 {
859 if (!selectedZone)
860 {
861 parentVisualizationWidget()->selectionZoneManager().clearSelection();
862 impl->startDrawingZone(event->pos());
863 }
864 else
865 {
866 parentVisualizationWidget()->selectionZoneManager().select({ selectedZone });
830 867 }
831 break;
832 case SqpApplication::PlotsInteractionMode::ZoomBox :
833 impl->startDrawingRect(event->pos());
834 break;
835 default:
836 setCursor(Qt::ClosedHandCursor);
837 impl->updateMousePosition(event->pos());
838 868 }
869 break;
870 case SqpApplication::PlotsInteractionMode::ZoomBox :
871 impl->startDrawingRect(event->pos());
872 break;
873 default:
874 setCursor(Qt::ClosedHandCursor);
875 impl->updateMousePosition(event->pos());
839 876 }
840 877 }
841 else if (event->button()==Qt::RightButton)
842 {
843 onGraphMenuRequested(event->pos());
844 }
845 878 QWidget::mousePressEvent(event);
846 879 }
847 880
848 881 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent *event)
849 882 {
850 883 impl->m_RenderingDelegate->onMouseDoubleClick(event);
851 884 }
852 885
853 886 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent *event)
854 887 {
855 888 switch (event->key()) {
856 889 case Qt::Key_Control:
857 890 event->accept();
858 891 break;
859 892 case Qt::Key_Shift:
860 893 event->accept();
861 894 break;
862 895 default:
863 896 QWidget::keyReleaseEvent(event);
864 897 break;
865 898 }
866 899 setCursor(Qt::ArrowCursor);
867 900 }
868 901
869 902 void VisualizationGraphWidget::keyPressEvent(QKeyEvent *event)
870 903 {
871 904 switch (event->key()) {
872 905 case Qt::Key_Control:
873 906 setCursor(Qt::CrossCursor);
874 907 break;
875 908 case Qt::Key_Shift:
876 909 break;
877 910 case Qt::Key_M:
878 911 impl->m_plot->rescaleAxes();
879 912 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
880 913 break;
881 914 case Qt::Key_Left:
882 915 if (event->modifiers() != Qt::ControlModifier) {
883 916 move(-0.1, Qt::Horizontal);
884 917 }
885 918 else {
886 919 zoom(2, this->width() / 2, Qt::Horizontal);
887 920 }
888 921 break;
889 922 case Qt::Key_Right:
890 923 if (event->modifiers() != Qt::ControlModifier) {
891 924 move(0.1, Qt::Horizontal);
892 925 }
893 926 else {
894 927 zoom(0.5, this->width() / 2, Qt::Horizontal);
895 928 }
896 929 break;
897 930 case Qt::Key_Up:
898 931 if (event->modifiers() != Qt::ControlModifier) {
899 932 move(0.1, Qt::Vertical);
900 933 }
901 934 else {
902 935 zoom(0.5, this->height() / 2, Qt::Vertical);
903 936 }
904 937 break;
905 938 case Qt::Key_Down:
906 939 if (event->modifiers() != Qt::ControlModifier) {
907 940 move(-0.1, Qt::Vertical);
908 941 }
909 942 else {
910 943 zoom(2, this->height() / 2, Qt::Vertical);
911 944 }
912 945 break;
913 946 default:
914 947 QWidget::keyPressEvent(event);
915 948 break;
916 949 }
917 950 }
918 951
919 952 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
920 953 {
921 954 return *impl->m_plot;
922 955 }
923 956
924 957 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
925 958 {
926 959 QMenu graphMenu{};
927 960
928 961 // Iterates on variables (unique keys)
929 962 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
930 963 end = impl->m_VariableToPlotMultiMap.cend();
931 964 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
932 965 // 'Remove variable' action
933 966 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
934 967 [this, var = it->first]() { removeVariable(var); });
935 968 }
936 969
937 970 if (!impl->m_ZoomStack.isEmpty()) {
938 971 if (!graphMenu.isEmpty()) {
939 972 graphMenu.addSeparator();
940 973 }
941 974
942 975 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
943 976 }
944 977
945 978 // Selection Zone Actions
946 979 auto selectionZoneItem = impl->selectionZoneAt(pos);
947 980 if (selectionZoneItem) {
948 981 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
949 982 selectedItems.removeAll(selectionZoneItem);
950 983 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
951 984
952 985 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
953 986 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
954 987 graphMenu.addSeparator();
955 988 }
956 989
957 990 QHash<QString, QMenu *> subMenus;
958 991 QHash<QString, bool> subMenusEnabled;
959 992 QHash<QString, FilteringAction *> filteredMenu;
960 993
961 994 for (auto zoneAction : zoneActions) {
962 995
963 996 auto isEnabled = zoneAction->isEnabled(selectedItems);
964 997
965 998 auto menu = &graphMenu;
966 999 QString menuPath;
967 1000 for (auto subMenuName : zoneAction->subMenuList()) {
968 1001 menuPath += '/';
969 1002 menuPath += subMenuName;
970 1003
971 1004 if (!subMenus.contains(menuPath)) {
972 1005 menu = menu->addMenu(subMenuName);
973 1006 subMenus[menuPath] = menu;
974 1007 subMenusEnabled[menuPath] = isEnabled;
975 1008 }
976 1009 else {
977 1010 menu = subMenus.value(menuPath);
978 1011 if (isEnabled) {
979 1012 // The sub menu is enabled if at least one of its actions is enabled
980 1013 subMenusEnabled[menuPath] = true;
981 1014 }
982 1015 }
983 1016 }
984 1017
985 1018 FilteringAction *filterAction = nullptr;
986 1019 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList())) {
987 1020 filterAction = filteredMenu.value(menuPath);
988 1021 if (!filterAction) {
989 1022 filterAction = new FilteringAction{this};
990 1023 filteredMenu[menuPath] = filterAction;
991 1024 menu->addAction(filterAction);
992 1025 }
993 1026 }
994 1027
995 1028 auto action = menu->addAction(zoneAction->name());
996 1029 action->setEnabled(isEnabled);
997 1030 action->setShortcut(zoneAction->displayedShortcut());
998 1031 QObject::connect(action, &QAction::triggered,
999 1032 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1000 1033
1001 1034 if (filterAction && zoneAction->isFilteringAllowed()) {
1002 1035 filterAction->addActionToFilter(action);
1003 1036 }
1004 1037 }
1005 1038
1006 1039 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
1007 1040 it.value()->setEnabled(subMenusEnabled[it.key()]);
1008 1041 }
1009 1042 }
1010 1043
1011 1044 if (!graphMenu.isEmpty()) {
1012 1045 graphMenu.exec(QCursor::pos());
1013 1046 }
1014 1047 }
1015 1048
1016 1049 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
1017 1050 {
1018 1051 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1019 1052 }
1020 1053
1021 1054 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
1022 1055 {
1023 1056 // Handles plot rendering when mouse is moving
1024 1057 impl->m_RenderingDelegate->updateTooltip(event);
1025 1058
1026 1059 auto axisPos = impl->posToAxisPos(event->pos());
1027 1060
1028 1061 // Zoom box and zone drawing
1029 1062 if (impl->m_DrawingZoomRect) {
1030 1063 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1031 1064 }
1032 1065 else if (impl->m_DrawingZone) {
1033 1066 impl->m_DrawingZone->setEnd(axisPos.x());
1034 1067 }
1035 1068
1036 1069 // Cursor
1037 1070 if (auto parentZone = parentZoneWidget()) {
1038 1071 if (impl->pointIsInAxisRect(axisPos, plot())) {
1039 1072 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1040 1073 }
1041 1074 else {
1042 1075 parentZone->notifyMouseLeaveGraph(this);
1043 1076 }
1044 1077 }
1045 1078
1046 1079 // Search for the selection zone under the mouse
1047 1080 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1048 1081 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1049 1082 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
1050 1083
1051 1084 // Sets the appropriate cursor shape
1052 1085 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1053 1086 setCursor(cursorShape);
1054 1087
1055 1088 // Manages the hovered zone
1056 1089 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
1057 1090 if (impl->m_HoveredZone) {
1058 1091 impl->m_HoveredZone->setHovered(false);
1059 1092 }
1060 1093 selectionZoneItemUnderCursor->setHovered(true);
1061 1094 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1062 1095 plot().replot(QCustomPlot::rpQueuedReplot);
1063 1096 }
1064 1097 }
1065 1098 else {
1066 1099 // There is no zone under the mouse or the interaction mode is not "selection zones"
1067 1100 if (impl->m_HoveredZone) {
1068 1101 impl->m_HoveredZone->setHovered(false);
1069 1102 impl->m_HoveredZone = nullptr;
1070 1103 }
1071 1104
1072 1105 setCursor(Qt::ArrowCursor);
1073 1106 }
1074 1107
1075 1108 impl->m_HasMovedMouse = true;
1076 1109 VisualizationDragWidget::mouseMoveEvent(event);
1077 1110 }
1078 1111
1079 1112 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
1080 1113 {
1081 1114 // Processes event only if the wheel occurs on axis rect
1082 1115 if (!dynamic_cast<QCPAxisRect *>(impl->m_plot->layoutElementAt(event->posF()))) {
1083 1116 return;
1084 1117 }
1085 1118
1086 1119 auto value = event->angleDelta().x() + event->angleDelta().y();
1087 1120 if (value != 0) {
1088 1121
1089 1122 auto direction = value > 0 ? 1.0 : -1.0;
1090 1123 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1091 1124 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1092 1125 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1093 1126
1094 1127 auto zoomOrientations = QFlags<Qt::Orientation>{};
1095 1128 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1096 1129 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1097 1130
1098 1131 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1099 1132
1100 1133 if (!isZoomX && !isZoomY) {
1101 1134 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1102 1135 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1103 1136
1104 1137 axis->setRange(axis->range() + diff);
1105 1138
1106 1139 if (plot().noAntialiasingOnDrag()) {
1107 1140 plot().setNotAntialiasedElements(QCP::aeAll);
1108 1141 }
1109 1142
1110 1143 // plot().replot(QCustomPlot::rpQueuedReplot);
1111 1144 }
1112 1145 }
1113 1146 }
1114 1147
1115 1148 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
1116 1149 {
1117 1150 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1118 1151 auto isSelectionZoneMode
1119 1152 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1120 1153 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1121 1154
1122 1155 if (!isDragDropClick && isLeftClick) {
1123 1156 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
1124 1157 // Starts a zoom box
1125 1158 impl->startDrawingRect(event->pos());
1126 1159 }
1127 1160 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
1128 1161 // Starts a new selection zone
1129 1162 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1130 1163 if (!zoneAtPos) {
1131 impl->startDrawingZone(event->pos(), this);
1164 impl->startDrawingZone(event->pos());
1132 1165 }
1133 1166 }
1134 1167 }
1135 1168
1136 1169
1137 1170 // Allows zone edition only in selection zone mode without drag&drop
1138 1171 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1139 1172
1140 1173 // Selection / Deselection
1141 1174 if (isSelectionZoneMode) {
1142 1175 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1143 1176 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1144 1177
1145 1178
1146 1179 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1147 1180 && !isMultiSelectionClick) {
1148 1181 parentVisualizationWidget()->selectionZoneManager().select(
1149 1182 {selectionZoneItemUnderCursor});
1150 1183 }
1151 1184 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
1152 1185 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1153 1186 }
1154 1187 else {
1155 1188 // No selection change
1156 1189 }
1157 1190
1158 1191 if (selectionZoneItemUnderCursor && isLeftClick) {
1159 1192 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1160 1193 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1161 1194 }
1162 1195 }
1163 1196
1164 1197
1165 1198 impl->m_HasMovedMouse = false;
1166 1199 VisualizationDragWidget::mousePressEvent(event);
1167 1200 }
1168 1201
1169 1202 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
1170 1203 {
1171 1204 if (impl->m_DrawingZoomRect) {
1172 1205
1173 1206 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1174 1207 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1175 1208
1176 1209 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
1177 1210 impl->m_DrawingZoomRect->bottomRight->coords().x()};
1178 1211
1179 1212 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
1180 1213 impl->m_DrawingZoomRect->bottomRight->coords().y()};
1181 1214
1182 1215 impl->removeDrawingRect();
1183 1216
1184 1217 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1185 1218 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
1186 1219 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1187 1220 axisX->setRange(newAxisXRange);
1188 1221 axisY->setRange(newAxisYRange);
1189 1222
1190 1223 plot().replot(QCustomPlot::rpQueuedReplot);
1191 1224 }
1192 1225 }
1193 1226
1194 impl->endDrawingZone(this);
1227 impl->endDrawingZone();
1195 1228
1196 1229 // Selection / Deselection
1197 1230 auto isSelectionZoneMode
1198 1231 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1199 1232 if (isSelectionZoneMode) {
1200 1233 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1201 1234 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1202 1235 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1203 1236 && !impl->m_HasMovedMouse) {
1204 1237
1205 1238 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1206 1239 if (zonesUnderCursor.count() > 1) {
1207 1240 // There are multiple zones under the mouse.
1208 1241 // Performs the selection with a selection dialog.
1209 1242 VisualizationMultiZoneSelectionDialog dialog{this};
1210 1243 dialog.setZones(zonesUnderCursor);
1211 1244 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1212 1245 dialog.activateWindow();
1213 1246 dialog.raise();
1214 1247 if (dialog.exec() == QDialog::Accepted) {
1215 1248 auto selection = dialog.selectedZones();
1216 1249
1217 1250 if (!isMultiSelectionClick) {
1218 1251 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1219 1252 }
1220 1253
1221 1254 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
1222 1255 auto zone = it.key();
1223 1256 auto isSelected = it.value();
1224 1257 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
1225 1258 isSelected);
1226 1259
1227 1260 if (isSelected) {
1228 1261 // Puts the zone on top of the stack so it can be moved or resized
1229 1262 impl->moveSelectionZoneOnTop(zone, plot());
1230 1263 }
1231 1264 }
1232 1265 }
1233 1266 }
1234 1267 else {
1235 1268 if (!isMultiSelectionClick) {
1236 1269 parentVisualizationWidget()->selectionZoneManager().select(
1237 1270 {selectionZoneItemUnderCursor});
1238 1271 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1239 1272 }
1240 1273 else {
1241 1274 parentVisualizationWidget()->selectionZoneManager().setSelected(
1242 1275 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
1243 1276 || event->button() == Qt::RightButton);
1244 1277 }
1245 1278 }
1246 1279 }
1247 1280 else {
1248 1281 // No selection change
1249 1282 }
1250 1283 }
1251 1284 }
1252 1285
1253 1286 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1254 1287 {
1255 1288 auto graphRange = impl->m_plot->xAxis->range();
1256 1289 auto dateTime = DateTimeRange{graphRange.lower, graphRange.upper};
1257 1290
1258 1291 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
1259 1292 auto variable = variableEntry.first;
1260 1293 qCDebug(LOG_VisualizationGraphWidget())
1261 1294 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1262 1295 qCDebug(LOG_VisualizationGraphWidget())
1263 1296 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1264 1297 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
1265 1298 impl->updateData(variableEntry.second, variable, variable->range());
1266 1299 }
1267 1300 }
1268 1301 }
1269 1302
1270 1303 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
1271 1304 const DateTimeRange &range)
1272 1305 {
1273 1306 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1274 1307 if (it != impl->m_VariableToPlotMultiMap.end()) {
1275 1308 impl->updateData(it->second, variable, range);
1276 1309 }
1277 1310 }
1278 1311
1279 1312 void VisualizationGraphWidget::variableUpdated(QUuid id)
1280 1313 {
1281 1314 for (auto &[var, plotables] : impl->m_VariableToPlotMultiMap) {
1282 1315 if (var->ID() == id) {
1283 1316 impl->updateData(plotables, var, this->graphRange());
1284 1317 }
1285 1318 }
1286 1319 }
@@ -1,102 +1,101
1 1 #include <QtTest>
2 2 #include <QObject>
3 3 #include <QString>
4 4 #include <QScreen>
5 5 #include <QMainWindow>
6 6 #include <QWheelEvent>
7 7
8 8 #include <qcustomplot.h>
9 9
10 10 #include <SqpApplication.h>
11 11 #include <Variable/VariableController2.h>
12 12 #include <Common/cpp_utils.h>
13 13
14 14 #include <Visualization/VisualizationGraphWidget.h>
15 15 #include <TestProviders.h>
16 16 #include <GUITestUtils.h>
17 17
18 18
19 19 ALIAS_TEMPLATE_FUNCTION(isReady, static_cast<SqpApplication *>(qApp)->variableController().isReady)
20 20
21 21 #define A_SIMPLE_GRAPH_FIXTURE \
22 22 VisualizationGraphWidget w;\
23 23 PREPARE_GUI_TEST(w);\
24 24 auto provider = std::make_shared<SimpleRange<10> >();\
25 25 auto range = DateTimeRange::fromDateTime(QDate(2018, 8, 7), QTime(14, 00), QDate(2018, 8, 7),\
26 26 QTime(16, 00));\
27 27 auto var = static_cast<SqpApplication *>(qApp)->variableController().createVariable(\
28 28 "V1", {{"", "scalar"}}, provider, range);\
29 29 while (!isReady(var))\
30 30 QCoreApplication::processEvents();\
31 31 w.addVariable(var, range);\
32 GET_CHILD_WIDGET_FOR_GUI_TESTS(w, plot, QCustomPlot, "widget");\
33 32 auto cent = center(&w);
34 33
35 34
36 35
37 36 class A_SimpleGraph : public QObject {
38 37 Q_OBJECT
39 38 public:
40 39 explicit A_SimpleGraph(QObject *parent = Q_NULLPTR) : QObject(parent) {}
41 40
42 41 private slots:
43 42 void scrolls_left_with_mouse()
44 43 {
45 44 A_SIMPLE_GRAPH_FIXTURE;
46 45
47 46 for (auto i = 0; i < 100; i++) {
48 47 QTest::mousePress(&w, Qt::LeftButton, Qt::NoModifier, cent, 1);
49 48 mouseMove(&w, {cent.x() + 200, cent.y()}, Qt::LeftButton);
50 49 QTest::mouseRelease(&w, Qt::LeftButton);
51 50 while (!isReady(var))
52 51 QCoreApplication::processEvents();
53 52 }
54 53 while (!isReady(var))
55 54 QCoreApplication::processEvents();
56 55 auto r = var->range();
57 56 /*
58 57 * Scrolling to the left implies going back in time
59 58 * Scroll only implies keeping the same delta T -> shit only transformation
60 59 */
61 60 QVERIFY(r.m_TEnd < range.m_TEnd);
62 61 QVERIFY(SciQLop::numeric::almost_equal<double>(r.delta(),range.delta(),1));
63 62 }
64 63
65 64 void scrolls_right_with_mouse()
66 65 {
67 66 A_SIMPLE_GRAPH_FIXTURE;
68 67
69 68 for (auto i = 0; i < 100; i++) {
70 69 QTest::mousePress(&w, Qt::LeftButton, Qt::NoModifier, cent, 1);
71 70 mouseMove(&w, {cent.x() - 200, cent.y()}, Qt::LeftButton);
72 71 QTest::mouseRelease(&w, Qt::LeftButton);
73 72 while (!isReady(var))
74 73 QCoreApplication::processEvents();
75 74 }
76 75 while (!isReady(var))
77 76 QCoreApplication::processEvents();
78 77 auto r = var->range();
79 78 /*
80 79 * Scrolling to the right implies going forward in time
81 80 * Scroll only implies keeping the same delta T -> shit only transformation
82 81 */
83 82 QVERIFY(r.m_TEnd > range.m_TEnd);
84 83 QVERIFY(SciQLop::numeric::almost_equal<double>(r.delta(),range.delta(),1));
85 84 }
86 85 };
87 86
88 87 QT_BEGIN_NAMESPACE
89 88 QTEST_ADD_GPU_BLACKLIST_SUPPORT_DEFS
90 89 QT_END_NAMESPACE
91 90 int main(int argc, char *argv[])
92 91 {
93 92 SqpApplication app{argc, argv};
94 93 app.setAttribute(Qt::AA_Use96Dpi, true);
95 94 QTEST_DISABLE_KEYPAD_NAVIGATION;
96 95 QTEST_ADD_GPU_BLACKLIST_SUPPORT;
97 96 A_SimpleGraph tc;
98 97 QTEST_SET_MAIN_SOURCE_PATH;
99 98 return QTest::qExec(&tc, argc, argv);
100 99 }
101 100
102 101 #include "main.moc"
General Comments 0
You need to be logged in to leave comments. Login now