##// END OF EJS Templates
Restored graph D&D and added full screen mode (F11)...
jeandet -
r1376:b8327d173f37
parent child
Show More
@@ -1,66 +1,68
1 1 /*------------------------------------------------------------------------------
2 2 -- This file is a part of the SciQLop Software
3 3 -- Copyright (C) 2017, Plasma Physics Laboratory - CNRS
4 4 --
5 5 -- This program is free software; you can redistribute it and/or modify
6 6 -- it under the terms of the GNU General Public License as published by
7 7 -- the Free Software Foundation; either version 2 of the License, or
8 8 -- (at your option) any later version.
9 9 --
10 10 -- This program is distributed in the hope that it will be useful,
11 11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 -- GNU General Public License for more details.
14 14 --
15 15 -- You should have received a copy of the GNU General Public License
16 16 -- along with this program; if not, write to the Free Software
17 17 -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 18 -------------------------------------------------------------------------------*/
19 19 /*-- Author : Alexis Jeandet
20 20 -- Mail : alexis.jeandet@member.fsf.org
21 21 ----------------------------------------------------------------------------*/
22 22 #ifndef SCIQLOP_MAINWINDOW_H
23 23 #define SCIQLOP_MAINWINDOW_H
24 24
25 25 #include <QListWidgetItem>
26 26 #include <QLoggingCategory>
27 27 #include <QMainWindow>
28 28 #include <QProgressBar>
29 29 #include <QProgressDialog>
30 30 #include <QThread>
31 31 #include <QVBoxLayout>
32 32 #include <QWidget>
33 33
34 34 #include <Common/spimpl.h>
35 35
36 36 #include <memory>
37 37
38 38 Q_DECLARE_LOGGING_CATEGORY(LOG_MainWindow)
39 39
40 40 namespace Ui {
41 41 class MainWindow;
42 42 } // namespace Ui
43 43
44 44
45 45 class MainWindow : public QMainWindow {
46 46 Q_OBJECT
47 47
48 48 public:
49 explicit MainWindow(QWidget *parent = 0);
50 virtual ~MainWindow();
49 explicit MainWindow(QWidget *parent = nullptr);
50 virtual ~MainWindow() override;
51 51 public slots:
52 52
53 53 protected:
54 void changeEvent(QEvent *e);
55 void closeEvent(QCloseEvent *event);
54 void changeEvent(QEvent *e) override;
55 void closeEvent(QCloseEvent *event) override;
56
57 void keyPressEvent(QKeyEvent *event) override;
56 58
57 59 private:
58 60 std::unique_ptr<Ui::MainWindow> m_Ui;
59 61 // QWidget *m_progressWidget;
60 62 // QVBoxLayout *m_progressLayout;
61 63 // QList<QLopService*> m_qlopServices;
62 64 class MainWindowPrivate;
63 65 spimpl::unique_impl_ptr<MainWindowPrivate> impl;
64 66 };
65 67
66 68 #endif // SCIQLOP_MAINWINDOW_H
@@ -1,375 +1,394
1 1 /*------------------------------------------------------------------------------
2 2 -- This file is a part of the SciQLop Software
3 3 -- Copyright (C) 2017, Plasma Physics Laboratory - CNRS
4 4 --
5 5 -- This program is free software; you can redistribute it and/or modify
6 6 -- it under the terms of the GNU General Public License as published by
7 7 -- the Free Software Foundation; either version 2 of the License, or
8 8 -- (at your option) any later version.
9 9 --
10 10 -- This program is distributed in the hope that it will be useful,
11 11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 -- GNU General Public License for more details.
14 14 --
15 15 -- You should have received a copy of the GNU General Public License
16 16 -- along with this program; if not, write to the Free Software
17 17 -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 18 -------------------------------------------------------------------------------*/
19 19 /*-- Author : Alexis Jeandet
20 20 -- Mail : alexis.jeandet@member.fsf.org
21 21 ----------------------------------------------------------------------------*/
22 22 #include "MainWindow.h"
23 23 #include "ui_MainWindow.h"
24 24
25 25 #include <Catalogue/CatalogueController.h>
26 26 #include <Catalogue/CatalogueExplorer.h>
27 27 #include <DataSource/DataSourceController.h>
28 28 #include <DataSource/DataSourceWidget.h>
29 29 #include <Settings/SqpSettingsDialog.h>
30 30 #include <Settings/SqpSettingsGeneralWidget.h>
31 31 #include <SidePane/SqpSidePane.h>
32 32 #include <SqpApplication.h>
33 33 #include <Time/TimeController.h>
34 34 #include <TimeWidget/TimeWidget.h>
35 35 #include <Variable/Variable.h>
36 36 #include <Visualization/VisualizationController.h>
37 37
38 38 #include <QAction>
39 39 #include <QCloseEvent>
40 40 #include <QDate>
41 41 #include <QDir>
42 42 #include <QFileDialog>
43 43 #include <QMessageBox>
44 44 #include <QToolBar>
45 45 #include <QToolButton>
46 46 #include <memory.h>
47 47
48 48 #include "iostream"
49 49
50 50 Q_LOGGING_CATEGORY(LOG_MainWindow, "MainWindow")
51 51
52 52 namespace {
53 53 const auto LEFTMAININSPECTORWIDGETSPLITTERINDEX = 0;
54 54 const auto LEFTINSPECTORSIDEPANESPLITTERINDEX = 1;
55 55 const auto VIEWPLITTERINDEX = 2;
56 56 const auto RIGHTINSPECTORSIDEPANESPLITTERINDEX = 3;
57 57 const auto RIGHTMAININSPECTORWIDGETSPLITTERINDEX = 4;
58 58 }
59 59
60 60 class MainWindow::MainWindowPrivate {
61 61 public:
62 62 explicit MainWindowPrivate(MainWindow *mainWindow)
63 63 : m_LastOpenLeftInspectorSize{},
64 64 m_LastOpenRightInspectorSize{},
65 65 m_GeneralSettingsWidget{new SqpSettingsGeneralWidget{mainWindow}},
66 66 m_SettingsDialog{new SqpSettingsDialog{mainWindow}},
67 67 m_CatalogExplorer{new CatalogueExplorer{mainWindow}}
68 68 {
69 69 }
70 70
71 71 QSize m_LastOpenLeftInspectorSize;
72 72 QSize m_LastOpenRightInspectorSize;
73 73 /// General settings widget. MainWindow has the ownership
74 74 SqpSettingsGeneralWidget *m_GeneralSettingsWidget;
75 75 /// Settings dialog. MainWindow has the ownership
76 76 SqpSettingsDialog *m_SettingsDialog;
77 77 /// Catalogue dialog. MainWindow has the ownership
78 78 CatalogueExplorer *m_CatalogExplorer;
79 79
80 80 bool checkDataToSave(QWidget *parentWidget);
81 81 };
82 82
83 83 MainWindow::MainWindow(QWidget *parent)
84 84 : QMainWindow{parent},
85 85 m_Ui{new Ui::MainWindow},
86 86 impl{spimpl::make_unique_impl<MainWindowPrivate>(this)}
87 87 {
88 88 m_Ui->setupUi(this);
89 89
90 90 m_Ui->splitter->setCollapsible(LEFTINSPECTORSIDEPANESPLITTERINDEX, false);
91 91 m_Ui->splitter->setCollapsible(RIGHTINSPECTORSIDEPANESPLITTERINDEX, false);
92 92
93 93 impl->m_CatalogExplorer->setVisualizationWidget(m_Ui->view);
94 94
95 95
96 96
97 97
98 98
99 99 auto spacerLeftTop = new QWidget{};
100 100 spacerLeftTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
101 101
102 102 auto spacerLeftBottom = new QWidget{};
103 103 spacerLeftBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
104 104
105 105
106 106 auto spacerRightTop = new QWidget{};
107 107 spacerRightTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
108 108
109 109 auto spacerRightBottom = new QWidget{};
110 110 spacerRightBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
111 111
112 112
113 113 auto openInspector = [this](bool checked, bool right, auto action) {
114 114
115 115 action->setIcon(QIcon{(checked ^ right) ? ":/icones/next.png" : ":/icones/previous.png"});
116 116
117 117 auto &lastInspectorSize
118 118 = right ? impl->m_LastOpenRightInspectorSize : impl->m_LastOpenLeftInspectorSize;
119 119
120 120 auto nextInspectorSize = right ? m_Ui->rightMainInspectorWidget->size()
121 121 : m_Ui->leftMainInspectorWidget->size();
122 122
123 123 // Update of the last opened geometry
124 124 if (checked) {
125 125 lastInspectorSize = nextInspectorSize;
126 126 }
127 127
128 128 auto startSize = lastInspectorSize;
129 129 auto endSize = startSize;
130 130 endSize.setWidth(0);
131 131
132 132 auto splitterInspectorIndex
133 133 = right ? RIGHTMAININSPECTORWIDGETSPLITTERINDEX : LEFTMAININSPECTORWIDGETSPLITTERINDEX;
134 134
135 135 auto currentSizes = m_Ui->splitter->sizes();
136 136 if (checked) {
137 137 // adjust sizes individually here, e.g.
138 138 currentSizes[splitterInspectorIndex] -= lastInspectorSize.width();
139 139 currentSizes[VIEWPLITTERINDEX] += lastInspectorSize.width();
140 140 m_Ui->splitter->setSizes(currentSizes);
141 141 }
142 142 else {
143 143 // adjust sizes individually here, e.g.
144 144 currentSizes[splitterInspectorIndex] += lastInspectorSize.width();
145 145 currentSizes[VIEWPLITTERINDEX] -= lastInspectorSize.width();
146 146 m_Ui->splitter->setSizes(currentSizes);
147 147 }
148 148
149 149 };
150 150
151 151
152 152 // //////////////// //
153 153 // Menu and Toolbar //
154 154 // //////////////// //
155 155 this->menuBar()->addAction(tr("File"));
156 156 auto toolsMenu = this->menuBar()->addMenu(tr("Tools"));
157 157 toolsMenu->addAction(tr("Settings..."), [this]() {
158 158 // Loads settings
159 159 impl->m_SettingsDialog->loadSettings();
160 160
161 161 // Open settings dialog and save settings if the dialog is accepted
162 162 if (impl->m_SettingsDialog->exec() == QDialog::Accepted) {
163 163 impl->m_SettingsDialog->saveSettings();
164 164 }
165 165
166 166 });
167 167
168 168 auto mainToolBar = this->addToolBar(QStringLiteral("MainToolBar"));
169 169
170 170 auto timeWidget = new TimeWidget{};
171 171 mainToolBar->addWidget(timeWidget);
172 172
173 173 // Interaction modes
174 174 auto actionPointerMode = new QAction{QIcon(":/icones/pointer.png"), "Move", this};
175 175 actionPointerMode->setCheckable(true);
176 176 actionPointerMode->setChecked(sqpApp->plotsInteractionMode()
177 177 == SqpApplication::PlotsInteractionMode::None);
178 178 connect(actionPointerMode, &QAction::triggered,
179 179 []() { sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::None); });
180 180
181 181 auto actionZoomMode = new QAction{QIcon(":/icones/zoom.png"), "Zoom", this};
182 182 actionZoomMode->setCheckable(true);
183 183 actionZoomMode->setChecked(sqpApp->plotsInteractionMode()
184 184 == SqpApplication::PlotsInteractionMode::ZoomBox);
185 185 connect(actionZoomMode, &QAction::triggered, []() {
186 186 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::ZoomBox);
187 187 });
188 188
189 189 auto actionOrganisationMode = new QAction{QIcon(":/icones/drag.png"), "Organize", this};
190 190 actionOrganisationMode->setCheckable(true);
191 191 actionOrganisationMode->setChecked(sqpApp->plotsInteractionMode()
192 192 == SqpApplication::PlotsInteractionMode::DragAndDrop);
193 193 connect(actionOrganisationMode, &QAction::triggered, []() {
194 194 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::DragAndDrop);
195 195 });
196 196
197 197 auto actionZonesMode = new QAction{QIcon(":/icones/rectangle.png"), "Zones", this};
198 198 actionZonesMode->setCheckable(true);
199 199 actionZonesMode->setChecked(sqpApp->plotsInteractionMode()
200 200 == SqpApplication::PlotsInteractionMode::SelectionZones);
201 201 connect(actionZonesMode, &QAction::triggered, []() {
202 202 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::SelectionZones);
203 203 });
204 204
205 205 auto modeActionGroup = new QActionGroup{this};
206 206 modeActionGroup->addAction(actionZoomMode);
207 207 modeActionGroup->addAction(actionZonesMode);
208 208 modeActionGroup->addAction(actionOrganisationMode);
209 209 modeActionGroup->addAction(actionPointerMode);
210 210 modeActionGroup->setExclusive(true);
211 211
212 212 mainToolBar->addSeparator();
213 213 mainToolBar->addAction(actionPointerMode);
214 214 mainToolBar->addAction(actionZoomMode);
215 215 mainToolBar->addAction(actionOrganisationMode);
216 216 mainToolBar->addAction(actionZonesMode);
217 217 mainToolBar->addSeparator();
218 218
219 219 // Cursors
220 220 auto btnCursor = new QToolButton{this};
221 221 btnCursor->setIcon(QIcon(":/icones/cursor.png"));
222 222 btnCursor->setText("Cursor");
223 223 btnCursor->setToolTip("Cursor");
224 224 btnCursor->setPopupMode(QToolButton::InstantPopup);
225 225 auto cursorMenu = new QMenu("CursorMenu", this);
226 226 btnCursor->setMenu(cursorMenu);
227 227
228 228 auto noCursorAction = cursorMenu->addAction("No Cursor");
229 229 noCursorAction->setCheckable(true);
230 230 noCursorAction->setChecked(sqpApp->plotsCursorMode()
231 231 == SqpApplication::PlotsCursorMode::NoCursor);
232 232 connect(noCursorAction, &QAction::triggered,
233 233 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::NoCursor); });
234 234
235 235 cursorMenu->addSeparator();
236 236 auto verticalCursorAction = cursorMenu->addAction("Vertical Cursor");
237 237 verticalCursorAction->setCheckable(true);
238 238 verticalCursorAction->setChecked(sqpApp->plotsCursorMode()
239 239 == SqpApplication::PlotsCursorMode::Vertical);
240 240 connect(verticalCursorAction, &QAction::triggered,
241 241 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Vertical); });
242 242
243 243 auto temporalCursorAction = cursorMenu->addAction("Temporal Cursor");
244 244 temporalCursorAction->setCheckable(true);
245 245 temporalCursorAction->setChecked(sqpApp->plotsCursorMode()
246 246 == SqpApplication::PlotsCursorMode::Temporal);
247 247 connect(temporalCursorAction, &QAction::triggered,
248 248 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Temporal); });
249 249
250 250 auto horizontalCursorAction = cursorMenu->addAction("Horizontal Cursor");
251 251 horizontalCursorAction->setCheckable(true);
252 252 horizontalCursorAction->setChecked(sqpApp->plotsCursorMode()
253 253 == SqpApplication::PlotsCursorMode::Horizontal);
254 254 connect(horizontalCursorAction, &QAction::triggered,
255 255 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Horizontal); });
256 256
257 257 auto crossCursorAction = cursorMenu->addAction("Cross Cursor");
258 258 crossCursorAction->setCheckable(true);
259 259 crossCursorAction->setChecked(sqpApp->plotsCursorMode()
260 260 == SqpApplication::PlotsCursorMode::Cross);
261 261 connect(crossCursorAction, &QAction::triggered,
262 262 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Cross); });
263 263
264 264 mainToolBar->addWidget(btnCursor);
265 265
266 266 auto cursorModeActionGroup = new QActionGroup{this};
267 267 cursorModeActionGroup->setExclusive(true);
268 268 cursorModeActionGroup->addAction(noCursorAction);
269 269 cursorModeActionGroup->addAction(verticalCursorAction);
270 270 cursorModeActionGroup->addAction(temporalCursorAction);
271 271 cursorModeActionGroup->addAction(horizontalCursorAction);
272 272 cursorModeActionGroup->addAction(crossCursorAction);
273 273
274 274 // Catalog
275 275 mainToolBar->addSeparator();
276 276 mainToolBar->addAction(QIcon(":/icones/catalogue.png"), "Catalogues",
277 277 [this]() { impl->m_CatalogExplorer->show(); });
278 278
279 279 // //////// //
280 280 // Settings //
281 281 // //////// //
282 282
283 283 // Registers "general settings" widget to the settings dialog
284 284 impl->m_SettingsDialog->registerWidget(QStringLiteral("General"),
285 285 impl->m_GeneralSettingsWidget);
286 286
287 287 // /////////// //
288 288 // Connections //
289 289 // /////////// //
290 290
291 291 // Controllers / controllers connections
292 292 // connect(&sqpApp->timeController(), SIGNAL(timeUpdated(DateTimeRange)), &sqpApp->variableController(),
293 293 // SLOT(onDateTimeOnSelection(DateTimeRange)));
294 294
295 295 // Widgets / controllers connections
296 296
297 297 // DataSource
298 298 connect(&sqpApp->dataSourceController(), SIGNAL(dataSourceItemSet(DataSourceItem *)),
299 299 m_Ui->dataSourceWidget, SLOT(addDataSource(DataSourceItem *)));
300 300
301 301 // Time
302 302 connect(timeWidget, SIGNAL(timeUpdated(DateTimeRange)), &sqpApp->timeController(),
303 303 SLOT(onTimeToUpdate(DateTimeRange)));
304 304
305 305 // Visualization
306 306 connect(&sqpApp->visualizationController(),
307 307 SIGNAL(variableAboutToBeDeleted(std::shared_ptr<Variable>)), m_Ui->view,
308 308 SLOT(onVariableAboutToBeDeleted(std::shared_ptr<Variable>)));
309 309
310 310 connect(&sqpApp->visualizationController(),
311 311 SIGNAL(rangeChanged(std::shared_ptr<Variable>, const DateTimeRange &)), m_Ui->view,
312 312 SLOT(onRangeChanged(std::shared_ptr<Variable>, const DateTimeRange &)));
313 313
314 314 // Widgets / widgets connections
315 315
316 316 // For the following connections, we use DirectConnection to allow each widget that can
317 317 // potentially attach a menu to the variable's menu to do so before this menu is displayed.
318 318 // The order of connections is also important, since it determines the order in which each
319 319 // widget will attach its menu
320 320 connect(
321 321 m_Ui->variableInspectorWidget,
322 322 SIGNAL(tableMenuAboutToBeDisplayed(QMenu *, const QVector<std::shared_ptr<Variable> > &)),
323 323 m_Ui->view, SLOT(attachVariableMenu(QMenu *, const QVector<std::shared_ptr<Variable> > &)),
324 324 Qt::DirectConnection);
325 325 }
326 326
327 327 MainWindow::~MainWindow()
328 328 {
329 329 }
330 330
331 331 void MainWindow::changeEvent(QEvent *e)
332 332 {
333 333 QMainWindow::changeEvent(e);
334 334 switch (e->type()) {
335 335 case QEvent::LanguageChange:
336 336 m_Ui->retranslateUi(this);
337 337 break;
338 338 default:
339 339 break;
340 340 }
341 341 }
342 342
343 343 void MainWindow::closeEvent(QCloseEvent *event)
344 344 {
345 345 if (!impl->checkDataToSave(this)) {
346 346 event->ignore();
347 347 }
348 348 else {
349 349 event->accept();
350 350 }
351 351 }
352 352
353 void MainWindow::keyPressEvent(QKeyEvent *event)
354 {
355 switch (event->key())
356 {
357 case Qt::Key_F11:
358 if(this->isFullScreen())
359 {
360 this->showNormal();
361 }
362 else
363 {
364 this->showFullScreen();
365 }
366 break;
367 default:
368 break;
369 }
370 }
371
353 372 bool MainWindow::MainWindowPrivate::checkDataToSave(QWidget *parentWidget)
354 373 {
355 374 auto hasChanges = sqpApp->catalogueController().hasChanges();
356 375 if (hasChanges) {
357 376 // There are some unsaved changes
358 377 switch (QMessageBox::question(
359 378 parentWidget, tr("Save changes"),
360 379 tr("The catalogue controller has unsaved changes.\nDo you want to save them ?"),
361 380 QMessageBox::SaveAll | QMessageBox::Discard | QMessageBox::Cancel,
362 381 QMessageBox::SaveAll)) {
363 382 case QMessageBox::SaveAll:
364 383 sqpApp->catalogueController().saveAll();
365 384 break;
366 385 case QMessageBox::Discard:
367 386 break;
368 387 case QMessageBox::Cancel:
369 388 default:
370 389 return false;
371 390 }
372 391 }
373 392
374 393 return true;
375 394 }
@@ -1,1368 +1,1370
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 QCPRange m_lastXRange;
89 89 QCPRange m_lastYRange;
90 90 /// Delegate used to attach rendering features to the plot
91 91 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
92 92
93 93 QCPItemRect* m_DrawingZoomRect = nullptr;
94 94 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
95 95
96 96 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
97 97 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
98 98
99 99 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
100 100 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
101 101 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
102 102
103 103 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
104 104
105 105 bool m_VariableAutoRangeOnInit = true;
106 106
107 107 inline void enterPlotDrag(const QPoint& position)
108 108 {
109 109 m_lastMousePos = m_plot->mapFromParent(position);
110 110 m_lastXRange = m_plot->xAxis->range();
111 111 m_lastYRange = m_plot->yAxis->range();
112 112
113 113 }
114 114
115 115 inline bool isDrawingZoomRect(){return m_DrawingZoomRect!=nullptr;}
116 116 void updateZoomRect(const QPoint& newPos)
117 117 {
118 118 QPointF pos{m_plot->xAxis->pixelToCoord(newPos.x()), m_plot->yAxis->pixelToCoord(newPos.y())};
119 119 m_DrawingZoomRect->bottomRight->setCoords(pos);
120 120 m_plot->replot(QCustomPlot::rpQueuedReplot);
121 121 }
122 122
123 123 void applyZoomRect()
124 124 {
125 125 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
126 126 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
127 127
128 128 auto newAxisXRange = QCPRange{m_DrawingZoomRect->topLeft->coords().x(),
129 129 m_DrawingZoomRect->bottomRight->coords().x()};
130 130
131 131 auto newAxisYRange = QCPRange{m_DrawingZoomRect->topLeft->coords().y(),
132 132 m_DrawingZoomRect->bottomRight->coords().y()};
133 133
134 134 removeDrawingRect();
135 135
136 136 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
137 137 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
138 138 m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
139 139 axisX->setRange(newAxisXRange);
140 140 axisY->setRange(newAxisYRange);
141 141
142 142 m_plot->replot(QCustomPlot::rpQueuedReplot);
143 143 }
144 144 }
145 145
146 146 inline bool isDrawingZoneRect(){return m_DrawingZone!=nullptr;}
147 147 void updateZoneRect(const QPoint& newPos)
148 148 {
149 149 m_DrawingZone->setEnd(m_plot->xAxis->pixelToCoord(newPos.x()));
150 150 m_plot->replot(QCustomPlot::rpQueuedReplot);
151 151 }
152 152
153 153 void startDrawingRect(const QPoint &pos)
154 154 {
155 155 removeDrawingRect();
156 156
157 157 auto axisPos = posToAxisPos(pos);
158 158
159 159 m_DrawingZoomRect = new QCPItemRect{m_plot};
160 160 QPen p;
161 161 p.setWidth(2);
162 162 m_DrawingZoomRect->setPen(p);
163 163
164 164 m_DrawingZoomRect->topLeft->setCoords(axisPos);
165 165 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
166 166 }
167 167
168 168 void removeDrawingRect()
169 169 {
170 170 if (m_DrawingZoomRect) {
171 171 m_plot->removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
172 172 m_DrawingZoomRect = nullptr;
173 173 m_plot->replot(QCustomPlot::rpQueuedReplot);
174 174 }
175 175 }
176 176
177 177 void selectZone(const QPoint &pos)
178 178 {
179 179 auto zoneAtPos = selectionZoneAt(pos);
180 180 setSelectionZonesEditionEnabled(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones);
181 181 }
182 182
183 183 void startDrawingZone(const QPoint &pos)
184 184 {
185 185 endDrawingZone();
186 186
187 187 auto axisPos = posToAxisPos(pos);
188 188
189 189 m_DrawingZone = new VisualizationSelectionZoneItem{m_plot};
190 190 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
191 191 m_DrawingZone->setEditionEnabled(false);
192 192 }
193 193
194 194 void endDrawingZone()
195 195 {
196 196 if (m_DrawingZone) {
197 197 auto drawingZoneRange = m_DrawingZone->range();
198 198 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
199 199 m_DrawingZone->setEditionEnabled(true);
200 200 addSelectionZone(m_DrawingZone);
201 201 }
202 202 else {
203 203 m_plot->removeItem(m_DrawingZone);
204 204 }
205 205
206 206 m_plot->replot(QCustomPlot::rpQueuedReplot);
207 207 m_DrawingZone = nullptr;
208 208 }
209 209 }
210 210
211 211 void moveSelectionZone(const QPoint& destination)
212 212 {
213 213 /*
214 214 * I give up on this for now
215 215 * @TODO implement this, the difficulty is that selection zones have their own
216 216 * event handling code which seems to rely on QCP GUI event handling propagation
217 217 * which was a realy bad design choice.
218 218 */
219 219 }
220 220
221 221 void setSelectionZonesEditionEnabled(bool value)
222 222 {
223 223 for (auto s : m_SelectionZones) {
224 224 s->setEditionEnabled(value);
225 225 }
226 226 }
227 227
228 228 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
229 229
230 230 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos) const
231 231 {
232 232 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
233 233 auto minDistanceToZone = -1;
234 234 for (auto zone : m_SelectionZones) {
235 235 auto distanceToZone = zone->selectTest(pos, false);
236 236 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
237 237 && distanceToZone >= 0 && distanceToZone < m_plot->selectionTolerance()) {
238 238 selectionZoneItemUnderCursor = zone;
239 239 }
240 240 }
241 241
242 242 return selectionZoneItemUnderCursor;
243 243 }
244 244
245 245 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
246 246 const QCustomPlot &plot) const
247 247 {
248 248 QVector<VisualizationSelectionZoneItem *> zones;
249 249 for (auto zone : m_SelectionZones) {
250 250 auto distanceToZone = zone->selectTest(pos, false);
251 251 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
252 252 zones << zone;
253 253 }
254 254 }
255 255
256 256 return zones;
257 257 }
258 258
259 259 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
260 260 {
261 261 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
262 262 zone->moveToTop();
263 263 m_SelectionZones.removeAll(zone);
264 264 m_SelectionZones.append(zone);
265 265 }
266 266 }
267 267
268 268 QPointF posToAxisPos(const QPoint &pos) const
269 269 {
270 270 auto axisX = m_plot->axisRect()->axis(QCPAxis::atBottom);
271 271 auto axisY = m_plot->axisRect()->axis(QCPAxis::atLeft);
272 272 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
273 273 }
274 274
275 275 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
276 276 {
277 277 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
278 278 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
279 279 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
280 280 }
281 281
282 282 inline QCPRange _pixDistanceToRange(double pos1, double pos2, QCPAxis *axis)
283 283 {
284 284 if (axis->scaleType() == QCPAxis::stLinear)
285 285 {
286 286 auto diff = axis->pixelToCoord(pos1) - axis->pixelToCoord(pos2);
287 287 return QCPRange{axis->range().lower + diff, axis->range().upper + diff};
288 288 }
289 289 else
290 290 {
291 291 auto diff = axis->pixelToCoord(pos1) / axis->pixelToCoord(pos2);
292 292 return QCPRange{axis->range().lower * diff, axis->range().upper * diff};
293 293 }
294 294 }
295 295
296 296 void setRange(const DateTimeRange &newRange)
297 297 {
298 298 if (m_Flags.testFlag(GraphFlag::EnableAcquisition))
299 299 {
300 300 for (auto it = m_VariableToPlotMultiMap.begin(),
301 301 end = m_VariableToPlotMultiMap.end();
302 302 it != end; it = m_VariableToPlotMultiMap.upper_bound(it->first))
303 303 {
304 304 sqpApp->variableController().asyncChangeRange(it->first, newRange);
305 305 }
306 306 }
307 307 }
308 308
309 309 void setRange(const QCPRange &newRange)
310 310 {
311 311 auto graphRange = DateTimeRange{newRange.lower, newRange.upper};
312 312 setRange(graphRange);
313 313 }
314 314
315 315 std::tuple<double,double> moveGraph(const QPoint& destination)
316 316 {
317 317 auto currentPos = m_plot->mapFromParent(destination);
318 318 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
319 319 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
320 320 auto oldXRange = xAxis->range();
321 321 auto oldYRange = yAxis->range();
322 322 double dx = xAxis->pixelToCoord(m_lastMousePos.x()) - xAxis->pixelToCoord(currentPos.x());
323 323 xAxis->setRange(m_lastXRange.lower+dx, m_lastXRange.upper+dx);
324 324 if(yAxis->scaleType() == QCPAxis::stLinear)
325 325 {
326 326 double dy = yAxis->pixelToCoord(m_lastMousePos.y()) - yAxis->pixelToCoord(currentPos.y());
327 327 yAxis->setRange(m_lastYRange.lower+dy, m_lastYRange.upper+dy);
328 328 }
329 329 else
330 330 {
331 331 double dy = yAxis->pixelToCoord(m_lastMousePos.y()) / yAxis->pixelToCoord(currentPos.y());
332 332 yAxis->setRange(m_lastYRange.lower*dy, m_lastYRange.upper*dy);
333 333 }
334 334 auto newXRange = xAxis->range();
335 335 auto newYRange = yAxis->range();
336 336 setRange(xAxis->range());
337 337 m_plot->replot(QCustomPlot::rpQueuedReplot);
338 338 //m_lastMousePos = currentPos;
339 339 return {newXRange.lower - oldXRange.lower, newYRange.lower - oldYRange.lower};
340 340 }
341 341
342 342 void zoom(double factor, int center, Qt::Orientation orientation)
343 343 {
344 344 QCPAxis *axis = m_plot->axisRect()->rangeZoomAxis(orientation);
345 345 axis->scaleRange(factor, axis->pixelToCoord(center));
346 346 if (orientation == Qt::Horizontal)
347 347 setRange(axis->range());
348 348 m_plot->replot(QCustomPlot::rpQueuedReplot);
349 349 }
350 350
351 351 void move(double dx, double dy)
352 352 {
353 353 auto xAxis = m_plot->axisRect()->rangeDragAxis(Qt::Horizontal);
354 354 auto yAxis = m_plot->axisRect()->rangeDragAxis(Qt::Vertical);
355 355 xAxis->setRange(QCPRange(xAxis->range().lower+dx, xAxis->range().upper+dx));
356 356 yAxis->setRange(QCPRange(yAxis->range().lower+dy, yAxis->range().upper+dy));
357 357 setRange(xAxis->range());
358 358 m_plot->replot(QCustomPlot::rpQueuedReplot);
359 359 }
360 360
361 361 void move(double factor, Qt::Orientation orientation)
362 362 {
363 363 auto oldRange = m_plot->xAxis->range();
364 364 QCPAxis *axis = m_plot->axisRect()->rangeDragAxis(orientation);
365 365 if (m_plot->xAxis->scaleType() == QCPAxis::stLinear) {
366 366 double rg = (axis->range().upper - axis->range().lower) * (factor / 10);
367 367 axis->setRange(axis->range().lower + (rg), axis->range().upper + (rg));
368 368 }
369 369 else if (m_plot->xAxis->scaleType() == QCPAxis::stLogarithmic) {
370 370 int start = 0, stop = 0;
371 371 double diff = 0.;
372 372 if (factor > 0.0) {
373 373 stop = m_plot->width() * factor / 10;
374 374 start = 2 * m_plot->width() * factor / 10;
375 375 }
376 376 if (factor < 0.0) {
377 377 factor *= -1.0;
378 378 start = m_plot->width() * factor / 10;
379 379 stop = 2 * m_plot->width() * factor / 10;
380 380 }
381 381 diff = axis->pixelToCoord(start) / axis->pixelToCoord(stop);
382 382 axis->setRange(m_plot->axisRect()->rangeDragAxis(orientation)->range().lower * diff,
383 383 m_plot->axisRect()->rangeDragAxis(orientation)->range().upper * diff);
384 384 }
385 385 if (orientation == Qt::Horizontal)
386 386 setRange(axis->range());
387 387 m_plot->replot(QCustomPlot::rpQueuedReplot);
388 388 }
389 389 };
390 390
391 391 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
392 392 : VisualizationDragWidget{parent},
393 393 ui{new Ui::VisualizationGraphWidget},
394 394 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
395 395 {
396 396 ui->setupUi(this);
397 397 this->layout()->addWidget(impl->m_plot);
398 398 // 'Close' options : widget is deleted when closed
399 399 setAttribute(Qt::WA_DeleteOnClose);
400 400
401 401 // The delegate must be initialized after the ui as it uses the plot
402 402 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
403 403
404 404 // Init the cursors
405 405 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
406 406 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
407 407 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
408 408 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
409 409
410 410 this->setFocusPolicy(Qt::WheelFocus);
411 411 this->setMouseTracking(true);
412 412 impl->m_plot->setAttribute(Qt::WA_TransparentForMouseEvents);
413 413 impl->m_plot->setContextMenuPolicy(Qt::CustomContextMenu);
414 414 impl->m_plot->setParent(this);
415 415 }
416 416
417 417
418 418 VisualizationGraphWidget::~VisualizationGraphWidget()
419 419 {
420 420 delete ui;
421 421 }
422 422
423 423 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
424 424 {
425 425 auto parent = parentWidget();
426 426 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
427 427 parent = parent->parentWidget();
428 428 }
429 429
430 430 return qobject_cast<VisualizationZoneWidget *>(parent);
431 431 }
432 432
433 433 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
434 434 {
435 435 auto parent = parentWidget();
436 436 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
437 437 parent = parent->parentWidget();
438 438 }
439 439
440 440 return qobject_cast<VisualizationWidget *>(parent);
441 441 }
442 442
443 443 void VisualizationGraphWidget::setFlags(GraphFlags flags)
444 444 {
445 445 impl->m_Flags = std::move(flags);
446 446 }
447 447
448 448 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, DateTimeRange range)
449 449 {
450 450 // Uses delegate to create the qcpplot components according to the variable
451 451 auto createdPlottables = VisualizationGraphHelper::create(variable, *impl->m_plot);
452 452
453 453 // Sets graph properties
454 454 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
455 455
456 456 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
457 457
458 458 // If the variable already has its data loaded, load its units and its range in the graph
459 459 if (variable->dataSeries() != nullptr) {
460 460 impl->m_RenderingDelegate->setAxesUnits(*variable);
461 461 this->setFlags(GraphFlag::DisableAll);
462 462 setGraphRange(range);
463 463 this->setFlags(GraphFlag::EnableAll);
464 464 }
465 465 //@TODO this is bad! when variable is moved to another graph it still fires
466 466 // even if this has been deleted
467 467 connect(variable.get(), &Variable::updated, this, &VisualizationGraphWidget::variableUpdated);
468 468 this->onUpdateVarDisplaying(variable, range); // My bullshit
469 469 emit variableAdded(variable);
470 470 }
471 471
472 472 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
473 473 {
474 474 // Each component associated to the variable :
475 475 // - is removed from qcpplot (which deletes it)
476 476 // - is no longer referenced in the map
477 477 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
478 478 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
479 479 emit variableAboutToBeRemoved(variable);
480 480
481 481 auto &plottablesMap = variableIt->second;
482 482
483 483 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
484 484 plottableIt != plottableEnd;) {
485 485 impl->m_plot->removePlottable(plottableIt->second);
486 486 plottableIt = plottablesMap.erase(plottableIt);
487 487 }
488 488
489 489 impl->m_VariableToPlotMultiMap.erase(variableIt);
490 490 }
491 491
492 492 // Updates graph
493 493 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
494 494 }
495 495
496 496 std::vector<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
497 497 {
498 498 auto variables = std::vector<std::shared_ptr<Variable> >{};
499 499 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
500 500 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
501 501 variables.push_back(it->first);
502 502 }
503 503
504 504 return variables;
505 505 }
506 506
507 507 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
508 508 {
509 509 if (!variable) {
510 510 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
511 511 return;
512 512 }
513 513
514 514 VisualizationGraphHelper::setYAxisRange(variable, *impl->m_plot);
515 515 }
516 516
517 517 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
518 518 {
519 519 auto graphRange = impl->m_plot->xAxis->range();
520 520 return DateTimeRange{graphRange.lower, graphRange.upper};
521 521 }
522 522
523 523 void VisualizationGraphWidget::setGraphRange(const DateTimeRange &range, bool updateVar)
524 524 {
525 525
526 526 if(updateVar)
527 527 impl->setRange(range);
528 528 impl->m_plot->xAxis->setRange(range.m_TStart, range.m_TEnd);
529 529 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
530 530
531 531 }
532 532
533 533 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
534 534 {
535 535 impl->m_VariableAutoRangeOnInit = value;
536 536 }
537 537
538 538 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
539 539 {
540 540 QVector<DateTimeRange> ranges;
541 541 for (auto zone : impl->m_SelectionZones) {
542 542 ranges << zone->range();
543 543 }
544 544
545 545 return ranges;
546 546 }
547 547
548 548 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange> &ranges)
549 549 {
550 550 for (const auto &range : ranges) {
551 551 // note: ownership is transfered to QCustomPlot
552 552 auto zone = new VisualizationSelectionZoneItem(&plot());
553 553 zone->setRange(range.m_TStart, range.m_TEnd);
554 554 impl->addSelectionZone(zone);
555 555 }
556 556
557 557 plot().replot(QCustomPlot::rpQueuedReplot);
558 558 }
559 559
560 560 VisualizationSelectionZoneItem *
561 561 VisualizationGraphWidget::addSelectionZone(const QString &name, const DateTimeRange &range)
562 562 {
563 563 // note: ownership is transfered to QCustomPlot
564 564 auto zone = new VisualizationSelectionZoneItem(&plot());
565 565 zone->setName(name);
566 566 zone->setRange(range.m_TStart, range.m_TEnd);
567 567 impl->addSelectionZone(zone);
568 568
569 569 plot().replot(QCustomPlot::rpQueuedReplot);
570 570
571 571 return zone;
572 572 }
573 573
574 574 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
575 575 {
576 576 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
577 577
578 578 if (impl->m_HoveredZone == selectionZone) {
579 579 impl->m_HoveredZone = nullptr;
580 580 setCursor(Qt::ArrowCursor);
581 581 }
582 582
583 583 impl->m_SelectionZones.removeAll(selectionZone);
584 584 plot().removeItem(selectionZone);
585 585 plot().replot(QCustomPlot::rpQueuedReplot);
586 586 }
587 587
588 588 void VisualizationGraphWidget::undoZoom()
589 589 {
590 590 auto zoom = impl->m_ZoomStack.pop();
591 591 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
592 592 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
593 593
594 594 axisX->setRange(zoom.first);
595 595 axisY->setRange(zoom.second);
596 596
597 597 plot().replot(QCustomPlot::rpQueuedReplot);
598 598 }
599 599
600 600 void VisualizationGraphWidget::zoom(double factor, int center, Qt::Orientation orientation, bool forward)
601 601 {
602 602 impl->zoom(factor, center, orientation);
603 603 if(forward && orientation==Qt::Horizontal)
604 604 emit this->zoom_sig(factor, center, orientation, false);
605 605 }
606 606
607 607 void VisualizationGraphWidget::move(double factor, Qt::Orientation orientation, bool forward)
608 608 {
609 609 impl->move(factor, orientation);
610 610 if(forward)
611 611 emit this->move_sig(factor, orientation, false);
612 612 }
613 613
614 614 void VisualizationGraphWidget::move(double dx, double dy, bool forward)
615 615 {
616 616 impl->move(dx, dy);
617 617 if(forward)
618 618 emit this->move_sig(dx, dy, false);
619 619 }
620 620
621 621 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
622 622 {
623 623 if (visitor) {
624 624 visitor->visit(this);
625 625 }
626 626 else {
627 627 qCCritical(LOG_VisualizationGraphWidget())
628 628 << tr("Can't visit widget : the visitor is null");
629 629 }
630 630 }
631 631
632 632 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
633 633 {
634 634 auto isSpectrogram = [](const auto &variable) {
635 635 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
636 636 };
637 637
638 638 // - A spectrogram series can't be dropped on graph with existing plottables
639 639 // - No data series can be dropped on graph with existing spectrogram series
640 640 return isSpectrogram(variable)
641 641 ? impl->m_VariableToPlotMultiMap.empty()
642 642 : std::none_of(
643 643 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
644 644 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
645 645 }
646 646
647 647 bool VisualizationGraphWidget::contains(const Variable &variable) const
648 648 {
649 649 // Finds the variable among the keys of the map
650 650 auto variablePtr = &variable;
651 651 auto findVariable
652 652 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
653 653
654 654 auto end = impl->m_VariableToPlotMultiMap.cend();
655 655 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
656 656 return it != end;
657 657 }
658 658
659 659 QString VisualizationGraphWidget::name() const
660 660 {
661 661 return impl->m_Name;
662 662 }
663 663
664 664 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
665 665 {
666 666 auto mimeData = new QMimeData;
667 667
668 668 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position);
669 669 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
670 670 && selectionZoneItemUnderCursor) {
671 671 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
672 672 selectionZoneItemUnderCursor->range()));
673 673 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
674 674 selectionZoneItemUnderCursor->range()));
675 675 }
676 676 else {
677 677 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
678 678
679 679 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
680 680 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
681 681 }
682 682
683 683 return mimeData;
684 684 }
685 685
686 686 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
687 687 {
688 688 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition);
689 689 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
690 690 && selectionZoneItemUnderCursor) {
691 691
692 692 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
693 693 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
694 694
695 695 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
696 696 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
697 697 .toSize();
698 698
699 699 auto pixmap = QPixmap(zoneSize);
700 700 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
701 701
702 702 return pixmap;
703 703 }
704 704
705 705 return QPixmap();
706 706 }
707 707
708 708 bool VisualizationGraphWidget::isDragAllowed() const
709 709 {
710 710 return true;
711 711 }
712 712
713 713 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
714 714 {
715 715 if (highlighted) {
716 716 plot().setBackground(QBrush(QColor("#BBD5EE")));
717 717 }
718 718 else {
719 719 plot().setBackground(QBrush(Qt::white));
720 720 }
721 721
722 722 plot().update();
723 723 }
724 724
725 725 void VisualizationGraphWidget::addVerticalCursor(double time)
726 726 {
727 727 impl->m_VerticalCursor->setPosition(time);
728 728 impl->m_VerticalCursor->setVisible(true);
729 729
730 730 auto text
731 731 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
732 732 impl->m_VerticalCursor->setLabelText(text);
733 733 }
734 734
735 735 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
736 736 {
737 737 impl->m_VerticalCursor->setAbsolutePosition(position);
738 738 impl->m_VerticalCursor->setVisible(true);
739 739
740 740 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
741 741 auto text
742 742 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
743 743 impl->m_VerticalCursor->setLabelText(text);
744 744 }
745 745
746 746 void VisualizationGraphWidget::removeVerticalCursor()
747 747 {
748 748 impl->m_VerticalCursor->setVisible(false);
749 749 plot().replot(QCustomPlot::rpQueuedReplot);
750 750 }
751 751
752 752 void VisualizationGraphWidget::addHorizontalCursor(double value)
753 753 {
754 754 impl->m_HorizontalCursor->setPosition(value);
755 755 impl->m_HorizontalCursor->setVisible(true);
756 756 impl->m_HorizontalCursor->setLabelText(QString::number(value));
757 757 }
758 758
759 759 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
760 760 {
761 761 impl->m_HorizontalCursor->setAbsolutePosition(position);
762 762 impl->m_HorizontalCursor->setVisible(true);
763 763
764 764 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
765 765 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
766 766 }
767 767
768 768 void VisualizationGraphWidget::removeHorizontalCursor()
769 769 {
770 770 impl->m_HorizontalCursor->setVisible(false);
771 771 plot().replot(QCustomPlot::rpQueuedReplot);
772 772 }
773 773
774 774 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
775 775 {
776 776 Q_UNUSED(event);
777 777
778 778 for (auto i : impl->m_SelectionZones) {
779 779 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
780 780 }
781 781
782 782 // Prevents that all variables will be removed from graph when it will be closed
783 783 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
784 784 emit variableAboutToBeRemoved(variableEntry.first);
785 785 }
786 786 }
787 787
788 788 void VisualizationGraphWidget::enterEvent(QEvent *event)
789 789 {
790 790 Q_UNUSED(event);
791 791 impl->m_RenderingDelegate->showGraphOverlay(true);
792 792 }
793 793
794 794 void VisualizationGraphWidget::leaveEvent(QEvent *event)
795 795 {
796 796 Q_UNUSED(event);
797 797 impl->m_RenderingDelegate->showGraphOverlay(false);
798 798
799 799 if (auto parentZone = parentZoneWidget()) {
800 800 parentZone->notifyMouseLeaveGraph(this);
801 801 }
802 802 else {
803 803 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
804 804 }
805 805
806 806 if (impl->m_HoveredZone) {
807 807 impl->m_HoveredZone->setHovered(false);
808 808 impl->m_HoveredZone = nullptr;
809 809 }
810 810 }
811 811
812 812 void VisualizationGraphWidget::wheelEvent(QWheelEvent *event)
813 813 {
814 814 double factor;
815 815 double wheelSteps = event->delta() / 120.0; // a single step delta is +/-120 usually
816 816 if (event->modifiers() == Qt::ControlModifier) {
817 817 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
818 818 {
819 819 setCursor(Qt::SizeVerCursor);
820 820 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Vertical), wheelSteps);
821 821 zoom(factor, event->pos().y(), Qt::Vertical);
822 822 }
823 823 }
824 824 else if (event->modifiers() == Qt::ShiftModifier) {
825 825 if (event->orientation() == Qt::Vertical) // mRangeZoom.testFlag(Qt::Vertical))
826 826 {
827 827 setCursor(Qt::SizeHorCursor);
828 828 factor = pow(impl->m_plot->axisRect()->rangeZoomFactor(Qt::Horizontal), wheelSteps);
829 829 zoom(factor, event->pos().x(), Qt::Horizontal);
830 830 }
831 831 }
832 832 else {
833 833 move(wheelSteps, Qt::Horizontal);
834 834 }
835 835 event->accept();
836 836 }
837 837
838 838
839 839
840 840 void VisualizationGraphWidget::mouseMoveEvent(QMouseEvent *event)
841 841 {
842 842 if(impl->isDrawingZoomRect())
843 843 {
844 844 impl->updateZoomRect(event->pos());
845 845 }
846 846 else if (impl->isDrawingZoneRect())
847 847 {
848 848 impl->updateZoneRect(event->pos());
849 849 }
850 850 else if (event->buttons() == Qt::LeftButton)
851 851 {
852 852 if(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::None)
853 853 {
854 854 auto [dx,dy] = impl->moveGraph(event->pos());
855 855 emit this->move_sig(dx,0., false); // don't sync Y transformations
856 856 }
857 857 else if(sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones)
858 858 {
859 859
860 860 }
861 861 }
862 862 else
863 863 {
864 864 impl->m_RenderingDelegate->updateTooltip(event);
865 865 }
866 event->accept();
866 //event->accept();
867 QWidget::mouseMoveEvent(event);
867 868 }
868 869
869 870 void VisualizationGraphWidget::mouseReleaseEvent(QMouseEvent *event)
870 871 {
871 872 if(impl->isDrawingZoomRect())
872 873 {
873 874 impl->applyZoomRect();
874 875 }
875 876 else if(impl->isDrawingZoneRect())
876 877 {
877 878 impl->endDrawingZone();
878 879 }
879 880 else
880 881 {
881 882 setCursor(Qt::ArrowCursor);
882 883 }
883 884 event->accept();
884 885 }
885 886
886 887 void VisualizationGraphWidget::mousePressEvent(QMouseEvent *event)
887 888 {
888 889 if (event->button()==Qt::RightButton)
889 890 {
890 891 onGraphMenuRequested(event->pos());
891 892 }
892 893 else
893 894 {
894 895 auto selectedZone = impl->selectionZoneAt(event->pos());
895 896 switch (sqpApp->plotsInteractionMode())
896 897 {
897 898 case SqpApplication::PlotsInteractionMode::DragAndDrop :
898 899 break;
899 900 case SqpApplication::PlotsInteractionMode::SelectionZones :
900 901 impl->setSelectionZonesEditionEnabled(true);
901 902 if ((event->modifiers() == Qt::ControlModifier) && (selectedZone != nullptr))
902 903 {
903 904 selectedZone->setAssociatedEditedZones(parentVisualizationWidget()->selectionZoneManager().selectedItems());
904 905 }
905 906 else
906 907 {
907 908 if (!selectedZone)
908 909 {
909 910 parentVisualizationWidget()->selectionZoneManager().clearSelection();
910 911 impl->startDrawingZone(event->pos());
911 912 }
912 913 else
913 914 {
914 915 parentVisualizationWidget()->selectionZoneManager().select({ selectedZone });
915 916 }
916 917 }
917 918 break;
918 919 case SqpApplication::PlotsInteractionMode::ZoomBox :
919 920 impl->startDrawingRect(event->pos());
920 921 break;
921 922 default:
922 923 setCursor(Qt::ClosedHandCursor);
923 924 impl->enterPlotDrag(event->pos());
924 925 }
925 926 }
926 event->accept();
927 //event->accept();
928 QWidget::mousePressEvent(event);
927 929 }
928 930
929 931 void VisualizationGraphWidget::mouseDoubleClickEvent(QMouseEvent *event)
930 932 {
931 933 impl->m_RenderingDelegate->onMouseDoubleClick(event);
932 934 }
933 935
934 936 void VisualizationGraphWidget::keyReleaseEvent(QKeyEvent *event)
935 937 {
936 938 switch (event->key()) {
937 939 case Qt::Key_Control:
938 940 event->accept();
939 941 break;
940 942 case Qt::Key_Shift:
941 943 event->accept();
942 944 break;
943 945 default:
944 946 QWidget::keyReleaseEvent(event);
945 947 break;
946 948 }
947 949 setCursor(Qt::ArrowCursor);
948 event->accept();
950 //event->accept();
949 951 }
950 952
951 953 void VisualizationGraphWidget::keyPressEvent(QKeyEvent *event)
952 954 {
953 955 switch (event->key()) {
954 956 case Qt::Key_Control:
955 957 setCursor(Qt::CrossCursor);
956 958 break;
957 959 case Qt::Key_Shift:
958 960 break;
959 961 case Qt::Key_M:
960 962 impl->m_plot->rescaleAxes();
961 963 impl->m_plot->replot(QCustomPlot::rpQueuedReplot);
962 964 break;
963 965 case Qt::Key_Left:
964 966 if (event->modifiers() != Qt::ControlModifier) {
965 967 move(-0.1, Qt::Horizontal);
966 968 }
967 969 else {
968 970 zoom(2, this->width() / 2, Qt::Horizontal);
969 971 }
970 972 break;
971 973 case Qt::Key_Right:
972 974 if (event->modifiers() != Qt::ControlModifier) {
973 975 move(0.1, Qt::Horizontal);
974 976 }
975 977 else {
976 978 zoom(0.5, this->width() / 2, Qt::Horizontal);
977 979 }
978 980 break;
979 981 case Qt::Key_Up:
980 982 if (event->modifiers() != Qt::ControlModifier) {
981 983 move(0.1, Qt::Vertical);
982 984 }
983 985 else {
984 986 zoom(0.5, this->height() / 2, Qt::Vertical);
985 987 }
986 988 break;
987 989 case Qt::Key_Down:
988 990 if (event->modifiers() != Qt::ControlModifier) {
989 991 move(-0.1, Qt::Vertical);
990 992 }
991 993 else {
992 994 zoom(2, this->height() / 2, Qt::Vertical);
993 995 }
994 996 break;
995 997 default:
996 998 QWidget::keyPressEvent(event);
997 999 break;
998 1000 }
999 1001 }
1000 1002
1001 1003 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
1002 1004 {
1003 1005 return *impl->m_plot;
1004 1006 }
1005 1007
1006 1008 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
1007 1009 {
1008 1010 QMenu graphMenu{};
1009 1011
1010 1012 // Iterates on variables (unique keys)
1011 1013 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
1012 1014 end = impl->m_VariableToPlotMultiMap.cend();
1013 1015 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
1014 1016 // 'Remove variable' action
1015 1017 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
1016 1018 [this, var = it->first]() { removeVariable(var); });
1017 1019 }
1018 1020
1019 1021 if (!impl->m_ZoomStack.isEmpty()) {
1020 1022 if (!graphMenu.isEmpty()) {
1021 1023 graphMenu.addSeparator();
1022 1024 }
1023 1025
1024 1026 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
1025 1027 }
1026 1028
1027 1029 // Selection Zone Actions
1028 1030 auto selectionZoneItem = impl->selectionZoneAt(pos);
1029 1031 if (selectionZoneItem) {
1030 1032 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
1031 1033 selectedItems.removeAll(selectionZoneItem);
1032 1034 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
1033 1035
1034 1036 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
1035 1037 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
1036 1038 graphMenu.addSeparator();
1037 1039 }
1038 1040
1039 1041 QHash<QString, QMenu *> subMenus;
1040 1042 QHash<QString, bool> subMenusEnabled;
1041 1043 QHash<QString, FilteringAction *> filteredMenu;
1042 1044
1043 1045 for (auto zoneAction : zoneActions) {
1044 1046
1045 1047 auto isEnabled = zoneAction->isEnabled(selectedItems);
1046 1048
1047 1049 auto menu = &graphMenu;
1048 1050 QString menuPath;
1049 1051 for (auto subMenuName : zoneAction->subMenuList()) {
1050 1052 menuPath += '/';
1051 1053 menuPath += subMenuName;
1052 1054
1053 1055 if (!subMenus.contains(menuPath)) {
1054 1056 menu = menu->addMenu(subMenuName);
1055 1057 subMenus[menuPath] = menu;
1056 1058 subMenusEnabled[menuPath] = isEnabled;
1057 1059 }
1058 1060 else {
1059 1061 menu = subMenus.value(menuPath);
1060 1062 if (isEnabled) {
1061 1063 // The sub menu is enabled if at least one of its actions is enabled
1062 1064 subMenusEnabled[menuPath] = true;
1063 1065 }
1064 1066 }
1065 1067 }
1066 1068
1067 1069 FilteringAction *filterAction = nullptr;
1068 1070 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList())) {
1069 1071 filterAction = filteredMenu.value(menuPath);
1070 1072 if (!filterAction) {
1071 1073 filterAction = new FilteringAction{this};
1072 1074 filteredMenu[menuPath] = filterAction;
1073 1075 menu->addAction(filterAction);
1074 1076 }
1075 1077 }
1076 1078
1077 1079 auto action = menu->addAction(zoneAction->name());
1078 1080 action->setEnabled(isEnabled);
1079 1081 action->setShortcut(zoneAction->displayedShortcut());
1080 1082 QObject::connect(action, &QAction::triggered,
1081 1083 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
1082 1084
1083 1085 if (filterAction && zoneAction->isFilteringAllowed()) {
1084 1086 filterAction->addActionToFilter(action);
1085 1087 }
1086 1088 }
1087 1089
1088 1090 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
1089 1091 it.value()->setEnabled(subMenusEnabled[it.key()]);
1090 1092 }
1091 1093 }
1092 1094
1093 1095 if (!graphMenu.isEmpty()) {
1094 1096 graphMenu.exec(QCursor::pos());
1095 1097 }
1096 1098 }
1097 1099
1098 1100 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
1099 1101 {
1100 1102 impl->m_RenderingDelegate->onMouseDoubleClick(event);
1101 1103 }
1102 1104
1103 1105 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
1104 1106 {
1105 1107 // Handles plot rendering when mouse is moving
1106 1108 impl->m_RenderingDelegate->updateTooltip(event);
1107 1109
1108 1110 auto axisPos = impl->posToAxisPos(event->pos());
1109 1111
1110 1112 // Zoom box and zone drawing
1111 1113 if (impl->m_DrawingZoomRect) {
1112 1114 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
1113 1115 }
1114 1116 else if (impl->m_DrawingZone) {
1115 1117 impl->m_DrawingZone->setEnd(axisPos.x());
1116 1118 }
1117 1119
1118 1120 // Cursor
1119 1121 if (auto parentZone = parentZoneWidget()) {
1120 1122 if (impl->pointIsInAxisRect(axisPos, plot())) {
1121 1123 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
1122 1124 }
1123 1125 else {
1124 1126 parentZone->notifyMouseLeaveGraph(this);
1125 1127 }
1126 1128 }
1127 1129
1128 1130 // Search for the selection zone under the mouse
1129 1131 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1130 1132 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
1131 1133 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
1132 1134
1133 1135 // Sets the appropriate cursor shape
1134 1136 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
1135 1137 setCursor(cursorShape);
1136 1138
1137 1139 // Manages the hovered zone
1138 1140 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
1139 1141 if (impl->m_HoveredZone) {
1140 1142 impl->m_HoveredZone->setHovered(false);
1141 1143 }
1142 1144 selectionZoneItemUnderCursor->setHovered(true);
1143 1145 impl->m_HoveredZone = selectionZoneItemUnderCursor;
1144 1146 plot().replot(QCustomPlot::rpQueuedReplot);
1145 1147 }
1146 1148 }
1147 1149 else {
1148 1150 // There is no zone under the mouse or the interaction mode is not "selection zones"
1149 1151 if (impl->m_HoveredZone) {
1150 1152 impl->m_HoveredZone->setHovered(false);
1151 1153 impl->m_HoveredZone = nullptr;
1152 1154 }
1153 1155
1154 1156 setCursor(Qt::ArrowCursor);
1155 1157 }
1156 1158
1157 1159 impl->m_HasMovedMouse = true;
1158 1160 VisualizationDragWidget::mouseMoveEvent(event);
1159 1161 }
1160 1162
1161 1163 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
1162 1164 {
1163 1165 // Processes event only if the wheel occurs on axis rect
1164 1166 if (!dynamic_cast<QCPAxisRect *>(impl->m_plot->layoutElementAt(event->posF()))) {
1165 1167 return;
1166 1168 }
1167 1169
1168 1170 auto value = event->angleDelta().x() + event->angleDelta().y();
1169 1171 if (value != 0) {
1170 1172
1171 1173 auto direction = value > 0 ? 1.0 : -1.0;
1172 1174 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
1173 1175 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
1174 1176 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
1175 1177
1176 1178 auto zoomOrientations = QFlags<Qt::Orientation>{};
1177 1179 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
1178 1180 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
1179 1181
1180 1182 impl->m_plot->axisRect()->setRangeZoom(zoomOrientations);
1181 1183
1182 1184 if (!isZoomX && !isZoomY) {
1183 1185 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
1184 1186 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
1185 1187
1186 1188 axis->setRange(axis->range() + diff);
1187 1189
1188 1190 if (plot().noAntialiasingOnDrag()) {
1189 1191 plot().setNotAntialiasedElements(QCP::aeAll);
1190 1192 }
1191 1193
1192 1194 // plot().replot(QCustomPlot::rpQueuedReplot);
1193 1195 }
1194 1196 }
1195 1197 }
1196 1198
1197 1199 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
1198 1200 {
1199 1201 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
1200 1202 auto isSelectionZoneMode
1201 1203 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1202 1204 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
1203 1205
1204 1206 if (!isDragDropClick && isLeftClick) {
1205 1207 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
1206 1208 // Starts a zoom box
1207 1209 impl->startDrawingRect(event->pos());
1208 1210 }
1209 1211 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
1210 1212 // Starts a new selection zone
1211 1213 auto zoneAtPos = impl->selectionZoneAt(event->pos());
1212 1214 if (!zoneAtPos) {
1213 1215 impl->startDrawingZone(event->pos());
1214 1216 }
1215 1217 }
1216 1218 }
1217 1219
1218 1220
1219 1221 // Allows zone edition only in selection zone mode without drag&drop
1220 1222 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
1221 1223
1222 1224 // Selection / Deselection
1223 1225 if (isSelectionZoneMode) {
1224 1226 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1225 1227 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1226 1228
1227 1229
1228 1230 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
1229 1231 && !isMultiSelectionClick) {
1230 1232 parentVisualizationWidget()->selectionZoneManager().select(
1231 1233 {selectionZoneItemUnderCursor});
1232 1234 }
1233 1235 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
1234 1236 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1235 1237 }
1236 1238 else {
1237 1239 // No selection change
1238 1240 }
1239 1241
1240 1242 if (selectionZoneItemUnderCursor && isLeftClick) {
1241 1243 selectionZoneItemUnderCursor->setAssociatedEditedZones(
1242 1244 parentVisualizationWidget()->selectionZoneManager().selectedItems());
1243 1245 }
1244 1246 }
1245 1247
1246 1248
1247 1249 impl->m_HasMovedMouse = false;
1248 1250 VisualizationDragWidget::mousePressEvent(event);
1249 1251 }
1250 1252
1251 1253 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
1252 1254 {
1253 1255 if (impl->m_DrawingZoomRect) {
1254 1256
1255 1257 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
1256 1258 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
1257 1259
1258 1260 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
1259 1261 impl->m_DrawingZoomRect->bottomRight->coords().x()};
1260 1262
1261 1263 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
1262 1264 impl->m_DrawingZoomRect->bottomRight->coords().y()};
1263 1265
1264 1266 impl->removeDrawingRect();
1265 1267
1266 1268 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
1267 1269 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
1268 1270 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
1269 1271 axisX->setRange(newAxisXRange);
1270 1272 axisY->setRange(newAxisYRange);
1271 1273
1272 1274 plot().replot(QCustomPlot::rpQueuedReplot);
1273 1275 }
1274 1276 }
1275 1277
1276 1278 impl->endDrawingZone();
1277 1279
1278 1280 // Selection / Deselection
1279 1281 auto isSelectionZoneMode
1280 1282 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1281 1283 if (isSelectionZoneMode) {
1282 1284 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1283 1285 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos());
1284 1286 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1285 1287 && !impl->m_HasMovedMouse) {
1286 1288
1287 1289 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1288 1290 if (zonesUnderCursor.count() > 1) {
1289 1291 // There are multiple zones under the mouse.
1290 1292 // Performs the selection with a selection dialog.
1291 1293 VisualizationMultiZoneSelectionDialog dialog{this};
1292 1294 dialog.setZones(zonesUnderCursor);
1293 1295 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1294 1296 dialog.activateWindow();
1295 1297 dialog.raise();
1296 1298 if (dialog.exec() == QDialog::Accepted) {
1297 1299 auto selection = dialog.selectedZones();
1298 1300
1299 1301 if (!isMultiSelectionClick) {
1300 1302 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1301 1303 }
1302 1304
1303 1305 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
1304 1306 auto zone = it.key();
1305 1307 auto isSelected = it.value();
1306 1308 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
1307 1309 isSelected);
1308 1310
1309 1311 if (isSelected) {
1310 1312 // Puts the zone on top of the stack so it can be moved or resized
1311 1313 impl->moveSelectionZoneOnTop(zone, plot());
1312 1314 }
1313 1315 }
1314 1316 }
1315 1317 }
1316 1318 else {
1317 1319 if (!isMultiSelectionClick) {
1318 1320 parentVisualizationWidget()->selectionZoneManager().select(
1319 1321 {selectionZoneItemUnderCursor});
1320 1322 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1321 1323 }
1322 1324 else {
1323 1325 parentVisualizationWidget()->selectionZoneManager().setSelected(
1324 1326 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
1325 1327 || event->button() == Qt::RightButton);
1326 1328 }
1327 1329 }
1328 1330 }
1329 1331 else {
1330 1332 // No selection change
1331 1333 }
1332 1334 }
1333 1335 }
1334 1336
1335 1337 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1336 1338 {
1337 1339 auto graphRange = impl->m_plot->xAxis->range();
1338 1340 auto dateTime = DateTimeRange{graphRange.lower, graphRange.upper};
1339 1341
1340 1342 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
1341 1343 auto variable = variableEntry.first;
1342 1344 qCDebug(LOG_VisualizationGraphWidget())
1343 1345 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1344 1346 qCDebug(LOG_VisualizationGraphWidget())
1345 1347 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1346 1348 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
1347 1349 impl->updateData(variableEntry.second, variable, variable->range());
1348 1350 }
1349 1351 }
1350 1352 }
1351 1353
1352 1354 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
1353 1355 const DateTimeRange &range)
1354 1356 {
1355 1357 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1356 1358 if (it != impl->m_VariableToPlotMultiMap.end()) {
1357 1359 impl->updateData(it->second, variable, range);
1358 1360 }
1359 1361 }
1360 1362
1361 1363 void VisualizationGraphWidget::variableUpdated(QUuid id)
1362 1364 {
1363 1365 for (auto &[var, plotables] : impl->m_VariableToPlotMultiMap) {
1364 1366 if (var->ID() == id) {
1365 1367 impl->updateData(plotables, var, this->graphRange());
1366 1368 }
1367 1369 }
1368 1370 }
General Comments 0
You need to be logged in to leave comments. Login now