##// END OF EJS Templates
(•̀ᴗ•́)و ̑̑ Some cleaning, remover previous implementation of VariableController...
jeandet -
r1349:3fc54ca0e4e0
parent child
Show More
@@ -1,405 +1,404
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 #include <Variable/VariableController.h>
37 36 #include <Visualization/VisualizationController.h>
38 37
39 38 #include <QAction>
40 39 #include <QCloseEvent>
41 40 #include <QDate>
42 41 #include <QDir>
43 42 #include <QFileDialog>
44 43 #include <QMessageBox>
45 44 #include <QToolBar>
46 45 #include <QToolButton>
47 46 #include <memory.h>
48 47
49 48 #include "iostream"
50 49
51 50 Q_LOGGING_CATEGORY(LOG_MainWindow, "MainWindow")
52 51
53 52 namespace {
54 53 const auto LEFTMAININSPECTORWIDGETSPLITTERINDEX = 0;
55 54 const auto LEFTINSPECTORSIDEPANESPLITTERINDEX = 1;
56 55 const auto VIEWPLITTERINDEX = 2;
57 56 const auto RIGHTINSPECTORSIDEPANESPLITTERINDEX = 3;
58 57 const auto RIGHTMAININSPECTORWIDGETSPLITTERINDEX = 4;
59 58 }
60 59
61 60 class MainWindow::MainWindowPrivate {
62 61 public:
63 62 explicit MainWindowPrivate(MainWindow *mainWindow)
64 63 : m_LastOpenLeftInspectorSize{},
65 64 m_LastOpenRightInspectorSize{},
66 65 m_GeneralSettingsWidget{new SqpSettingsGeneralWidget{mainWindow}},
67 66 m_SettingsDialog{new SqpSettingsDialog{mainWindow}},
68 67 m_CatalogExplorer{new CatalogueExplorer{mainWindow}}
69 68 {
70 69 }
71 70
72 71 QSize m_LastOpenLeftInspectorSize;
73 72 QSize m_LastOpenRightInspectorSize;
74 73 /// General settings widget. MainWindow has the ownership
75 74 SqpSettingsGeneralWidget *m_GeneralSettingsWidget;
76 75 /// Settings dialog. MainWindow has the ownership
77 76 SqpSettingsDialog *m_SettingsDialog;
78 77 /// Catalogue dialog. MainWindow has the ownership
79 78 CatalogueExplorer *m_CatalogExplorer;
80 79
81 80 bool checkDataToSave(QWidget *parentWidget);
82 81 };
83 82
84 83 MainWindow::MainWindow(QWidget *parent)
85 84 : QMainWindow{parent},
86 85 m_Ui{new Ui::MainWindow},
87 86 impl{spimpl::make_unique_impl<MainWindowPrivate>(this)}
88 87 {
89 88 m_Ui->setupUi(this);
90 89
91 90 m_Ui->splitter->setCollapsible(LEFTINSPECTORSIDEPANESPLITTERINDEX, false);
92 91 m_Ui->splitter->setCollapsible(RIGHTINSPECTORSIDEPANESPLITTERINDEX, false);
93 92
94 93 impl->m_CatalogExplorer->setVisualizationWidget(m_Ui->view);
95 94
96 95
97 96 auto leftSidePane = m_Ui->leftInspectorSidePane->sidePane();
98 97 auto openLeftInspectorAction = new QAction{QIcon{
99 98 ":/icones/previous.png",
100 99 },
101 100 tr("Show/hide the left inspector"), this};
102 101
103 102
104 103 auto spacerLeftTop = new QWidget{};
105 104 spacerLeftTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
106 105
107 106 auto spacerLeftBottom = new QWidget{};
108 107 spacerLeftBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
109 108
110 109 leftSidePane->addWidget(spacerLeftTop);
111 110 leftSidePane->addAction(openLeftInspectorAction);
112 111 leftSidePane->addWidget(spacerLeftBottom);
113 112
114 113
115 114 auto rightSidePane = m_Ui->rightInspectorSidePane->sidePane();
116 115 auto openRightInspectorAction = new QAction{QIcon{
117 116 ":/icones/next.png",
118 117 },
119 118 tr("Show/hide the right inspector"), this};
120 119
121 120 auto spacerRightTop = new QWidget{};
122 121 spacerRightTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
123 122
124 123 auto spacerRightBottom = new QWidget{};
125 124 spacerRightBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
126 125
127 126 rightSidePane->addWidget(spacerRightTop);
128 127 rightSidePane->addAction(openRightInspectorAction);
129 128 rightSidePane->addWidget(spacerRightBottom);
130 129
131 130 openLeftInspectorAction->setCheckable(true);
132 131 openRightInspectorAction->setCheckable(true);
133 132
134 133 auto openInspector = [this](bool checked, bool right, auto action) {
135 134
136 135 action->setIcon(QIcon{(checked ^ right) ? ":/icones/next.png" : ":/icones/previous.png"});
137 136
138 137 auto &lastInspectorSize
139 138 = right ? impl->m_LastOpenRightInspectorSize : impl->m_LastOpenLeftInspectorSize;
140 139
141 140 auto nextInspectorSize = right ? m_Ui->rightMainInspectorWidget->size()
142 141 : m_Ui->leftMainInspectorWidget->size();
143 142
144 143 // Update of the last opened geometry
145 144 if (checked) {
146 145 lastInspectorSize = nextInspectorSize;
147 146 }
148 147
149 148 auto startSize = lastInspectorSize;
150 149 auto endSize = startSize;
151 150 endSize.setWidth(0);
152 151
153 152 auto splitterInspectorIndex
154 153 = right ? RIGHTMAININSPECTORWIDGETSPLITTERINDEX : LEFTMAININSPECTORWIDGETSPLITTERINDEX;
155 154
156 155 auto currentSizes = m_Ui->splitter->sizes();
157 156 if (checked) {
158 157 // adjust sizes individually here, e.g.
159 158 currentSizes[splitterInspectorIndex] -= lastInspectorSize.width();
160 159 currentSizes[VIEWPLITTERINDEX] += lastInspectorSize.width();
161 160 m_Ui->splitter->setSizes(currentSizes);
162 161 }
163 162 else {
164 163 // adjust sizes individually here, e.g.
165 164 currentSizes[splitterInspectorIndex] += lastInspectorSize.width();
166 165 currentSizes[VIEWPLITTERINDEX] -= lastInspectorSize.width();
167 166 m_Ui->splitter->setSizes(currentSizes);
168 167 }
169 168
170 169 };
171 170
172 171
173 172 connect(openLeftInspectorAction, &QAction::triggered,
174 173 [openInspector, openLeftInspectorAction](bool checked) {
175 174 openInspector(checked, false, openLeftInspectorAction);
176 175 });
177 176 connect(openRightInspectorAction, &QAction::triggered,
178 177 [openInspector, openRightInspectorAction](bool checked) {
179 178 openInspector(checked, true, openRightInspectorAction);
180 179 });
181 180
182 181 // //////////////// //
183 182 // Menu and Toolbar //
184 183 // //////////////// //
185 184 this->menuBar()->addAction(tr("File"));
186 185 auto toolsMenu = this->menuBar()->addMenu(tr("Tools"));
187 186 toolsMenu->addAction(tr("Settings..."), [this]() {
188 187 // Loads settings
189 188 impl->m_SettingsDialog->loadSettings();
190 189
191 190 // Open settings dialog and save settings if the dialog is accepted
192 191 if (impl->m_SettingsDialog->exec() == QDialog::Accepted) {
193 192 impl->m_SettingsDialog->saveSettings();
194 193 }
195 194
196 195 });
197 196
198 197 auto mainToolBar = this->addToolBar(QStringLiteral("MainToolBar"));
199 198
200 199 auto timeWidget = new TimeWidget{};
201 200 mainToolBar->addWidget(timeWidget);
202 201
203 202 // Interaction modes
204 203 auto actionPointerMode = new QAction{QIcon(":/icones/pointer.png"), "Move", this};
205 204 actionPointerMode->setCheckable(true);
206 205 actionPointerMode->setChecked(sqpApp->plotsInteractionMode()
207 206 == SqpApplication::PlotsInteractionMode::None);
208 207 connect(actionPointerMode, &QAction::triggered,
209 208 []() { sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::None); });
210 209
211 210 auto actionZoomMode = new QAction{QIcon(":/icones/zoom.png"), "Zoom", this};
212 211 actionZoomMode->setCheckable(true);
213 212 actionZoomMode->setChecked(sqpApp->plotsInteractionMode()
214 213 == SqpApplication::PlotsInteractionMode::ZoomBox);
215 214 connect(actionZoomMode, &QAction::triggered, []() {
216 215 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::ZoomBox);
217 216 });
218 217
219 218 auto actionOrganisationMode = new QAction{QIcon(":/icones/drag.png"), "Organize", this};
220 219 actionOrganisationMode->setCheckable(true);
221 220 actionOrganisationMode->setChecked(sqpApp->plotsInteractionMode()
222 221 == SqpApplication::PlotsInteractionMode::DragAndDrop);
223 222 connect(actionOrganisationMode, &QAction::triggered, []() {
224 223 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::DragAndDrop);
225 224 });
226 225
227 226 auto actionZonesMode = new QAction{QIcon(":/icones/rectangle.png"), "Zones", this};
228 227 actionZonesMode->setCheckable(true);
229 228 actionZonesMode->setChecked(sqpApp->plotsInteractionMode()
230 229 == SqpApplication::PlotsInteractionMode::SelectionZones);
231 230 connect(actionZonesMode, &QAction::triggered, []() {
232 231 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::SelectionZones);
233 232 });
234 233
235 234 auto modeActionGroup = new QActionGroup{this};
236 235 modeActionGroup->addAction(actionZoomMode);
237 236 modeActionGroup->addAction(actionZonesMode);
238 237 modeActionGroup->addAction(actionOrganisationMode);
239 238 modeActionGroup->addAction(actionPointerMode);
240 239 modeActionGroup->setExclusive(true);
241 240
242 241 mainToolBar->addSeparator();
243 242 mainToolBar->addAction(actionPointerMode);
244 243 mainToolBar->addAction(actionZoomMode);
245 244 mainToolBar->addAction(actionOrganisationMode);
246 245 mainToolBar->addAction(actionZonesMode);
247 246 mainToolBar->addSeparator();
248 247
249 248 // Cursors
250 249 auto btnCursor = new QToolButton{this};
251 250 btnCursor->setIcon(QIcon(":/icones/cursor.png"));
252 251 btnCursor->setText("Cursor");
253 252 btnCursor->setToolTip("Cursor");
254 253 btnCursor->setPopupMode(QToolButton::InstantPopup);
255 254 auto cursorMenu = new QMenu("CursorMenu", this);
256 255 btnCursor->setMenu(cursorMenu);
257 256
258 257 auto noCursorAction = cursorMenu->addAction("No Cursor");
259 258 noCursorAction->setCheckable(true);
260 259 noCursorAction->setChecked(sqpApp->plotsCursorMode()
261 260 == SqpApplication::PlotsCursorMode::NoCursor);
262 261 connect(noCursorAction, &QAction::triggered,
263 262 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::NoCursor); });
264 263
265 264 cursorMenu->addSeparator();
266 265 auto verticalCursorAction = cursorMenu->addAction("Vertical Cursor");
267 266 verticalCursorAction->setCheckable(true);
268 267 verticalCursorAction->setChecked(sqpApp->plotsCursorMode()
269 268 == SqpApplication::PlotsCursorMode::Vertical);
270 269 connect(verticalCursorAction, &QAction::triggered,
271 270 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Vertical); });
272 271
273 272 auto temporalCursorAction = cursorMenu->addAction("Temporal Cursor");
274 273 temporalCursorAction->setCheckable(true);
275 274 temporalCursorAction->setChecked(sqpApp->plotsCursorMode()
276 275 == SqpApplication::PlotsCursorMode::Temporal);
277 276 connect(temporalCursorAction, &QAction::triggered,
278 277 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Temporal); });
279 278
280 279 auto horizontalCursorAction = cursorMenu->addAction("Horizontal Cursor");
281 280 horizontalCursorAction->setCheckable(true);
282 281 horizontalCursorAction->setChecked(sqpApp->plotsCursorMode()
283 282 == SqpApplication::PlotsCursorMode::Horizontal);
284 283 connect(horizontalCursorAction, &QAction::triggered,
285 284 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Horizontal); });
286 285
287 286 auto crossCursorAction = cursorMenu->addAction("Cross Cursor");
288 287 crossCursorAction->setCheckable(true);
289 288 crossCursorAction->setChecked(sqpApp->plotsCursorMode()
290 289 == SqpApplication::PlotsCursorMode::Cross);
291 290 connect(crossCursorAction, &QAction::triggered,
292 291 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Cross); });
293 292
294 293 mainToolBar->addWidget(btnCursor);
295 294
296 295 auto cursorModeActionGroup = new QActionGroup{this};
297 296 cursorModeActionGroup->setExclusive(true);
298 297 cursorModeActionGroup->addAction(noCursorAction);
299 298 cursorModeActionGroup->addAction(verticalCursorAction);
300 299 cursorModeActionGroup->addAction(temporalCursorAction);
301 300 cursorModeActionGroup->addAction(horizontalCursorAction);
302 301 cursorModeActionGroup->addAction(crossCursorAction);
303 302
304 303 // Catalog
305 304 mainToolBar->addSeparator();
306 305 mainToolBar->addAction(QIcon(":/icones/catalogue.png"), "Catalogues",
307 306 [this]() { impl->m_CatalogExplorer->show(); });
308 307
309 308 // //////// //
310 309 // Settings //
311 310 // //////// //
312 311
313 312 // Registers "general settings" widget to the settings dialog
314 313 impl->m_SettingsDialog->registerWidget(QStringLiteral("General"),
315 314 impl->m_GeneralSettingsWidget);
316 315
317 316 // /////////// //
318 317 // Connections //
319 318 // /////////// //
320 319
321 320 // Controllers / controllers connections
322 321 // connect(&sqpApp->timeController(), SIGNAL(timeUpdated(DateTimeRange)), &sqpApp->variableController(),
323 322 // SLOT(onDateTimeOnSelection(DateTimeRange)));
324 323
325 324 // Widgets / controllers connections
326 325
327 326 // DataSource
328 327 connect(&sqpApp->dataSourceController(), SIGNAL(dataSourceItemSet(DataSourceItem *)),
329 328 m_Ui->dataSourceWidget, SLOT(addDataSource(DataSourceItem *)));
330 329
331 330 // Time
332 331 connect(timeWidget, SIGNAL(timeUpdated(DateTimeRange)), &sqpApp->timeController(),
333 332 SLOT(onTimeToUpdate(DateTimeRange)));
334 333
335 334 // Visualization
336 335 connect(&sqpApp->visualizationController(),
337 336 SIGNAL(variableAboutToBeDeleted(std::shared_ptr<Variable>)), m_Ui->view,
338 337 SLOT(onVariableAboutToBeDeleted(std::shared_ptr<Variable>)));
339 338
340 339 connect(&sqpApp->visualizationController(),
341 340 SIGNAL(rangeChanged(std::shared_ptr<Variable>, const DateTimeRange &)), m_Ui->view,
342 341 SLOT(onRangeChanged(std::shared_ptr<Variable>, const DateTimeRange &)));
343 342
344 343 // Widgets / widgets connections
345 344
346 345 // For the following connections, we use DirectConnection to allow each widget that can
347 346 // potentially attach a menu to the variable's menu to do so before this menu is displayed.
348 347 // The order of connections is also important, since it determines the order in which each
349 348 // widget will attach its menu
350 349 connect(
351 350 m_Ui->variableInspectorWidget,
352 351 SIGNAL(tableMenuAboutToBeDisplayed(QMenu *, const QVector<std::shared_ptr<Variable> > &)),
353 352 m_Ui->view, SLOT(attachVariableMenu(QMenu *, const QVector<std::shared_ptr<Variable> > &)),
354 353 Qt::DirectConnection);
355 354 }
356 355
357 356 MainWindow::~MainWindow()
358 357 {
359 358 }
360 359
361 360 void MainWindow::changeEvent(QEvent *e)
362 361 {
363 362 QMainWindow::changeEvent(e);
364 363 switch (e->type()) {
365 364 case QEvent::LanguageChange:
366 365 m_Ui->retranslateUi(this);
367 366 break;
368 367 default:
369 368 break;
370 369 }
371 370 }
372 371
373 372 void MainWindow::closeEvent(QCloseEvent *event)
374 373 {
375 374 if (!impl->checkDataToSave(this)) {
376 375 event->ignore();
377 376 }
378 377 else {
379 378 event->accept();
380 379 }
381 380 }
382 381
383 382 bool MainWindow::MainWindowPrivate::checkDataToSave(QWidget *parentWidget)
384 383 {
385 384 auto hasChanges = sqpApp->catalogueController().hasChanges();
386 385 if (hasChanges) {
387 386 // There are some unsaved changes
388 387 switch (QMessageBox::question(
389 388 parentWidget, tr("Save changes"),
390 389 tr("The catalogue controller has unsaved changes.\nDo you want to save them ?"),
391 390 QMessageBox::SaveAll | QMessageBox::Discard | QMessageBox::Cancel,
392 391 QMessageBox::SaveAll)) {
393 392 case QMessageBox::SaveAll:
394 393 sqpApp->catalogueController().saveAll();
395 394 break;
396 395 case QMessageBox::Discard:
397 396 break;
398 397 case QMessageBox::Cancel:
399 398 default:
400 399 return false;
401 400 }
402 401 }
403 402
404 403 return true;
405 404 }
@@ -1,1 +1,1
1 Subproject commit 5f4f9560990ba00394322fa1d06d4a30feaf7075
1 Subproject commit de55e7be90966d1efbd34a10df9a3231c50e245b
@@ -1,616 +1,615
1 1 #include "Catalogue/CatalogueEventsWidget.h"
2 2 #include "ui_CatalogueEventsWidget.h"
3 3
4 4 #include <Catalogue/CatalogueController.h>
5 5 #include <Catalogue/CatalogueEventsModel.h>
6 6 #include <Catalogue/CatalogueExplorerHelper.h>
7 7 #include <CatalogueDao.h>
8 8 #include <DBCatalogue.h>
9 9 #include <DBEventProduct.h>
10 10 #include <DataSource/DataSourceController.h>
11 11 #include <DataSource/DataSourceItem.h>
12 12 #include <SqpApplication.h>
13 13 #include <Variable/Variable.h>
14 #include <Variable/VariableController.h>
15 14 #include <Variable/VariableController2.h>
16 15 #include <Visualization/VisualizationGraphWidget.h>
17 16 #include <Visualization/VisualizationTabWidget.h>
18 17 #include <Visualization/VisualizationWidget.h>
19 18 #include <Visualization/VisualizationZoneWidget.h>
20 19
21 20 #include <QActionGroup>
22 21 #include <QDialog>
23 22 #include <QDialogButtonBox>
24 23 #include <QKeyEvent>
25 24 #include <QListWidget>
26 25 #include <QMenu>
27 26 #include <QMessageBox>
28 27
29 28 Q_LOGGING_CATEGORY(LOG_CatalogueEventsWidget, "CatalogueEventsWidget")
30 29
31 30 /// Percentage added to the range of a event when it is displayed
32 31 const auto EVENT_RANGE_MARGE = 30; // in %
33 32
34 33 const QString NEW_ZONE_TEXT = QStringLiteral("New Zone");
35 34
36 35 struct CatalogueEventsWidget::CatalogueEventsWidgetPrivate {
37 36
38 37 CatalogueEventsModel *m_Model = nullptr;
39 38 QStringList m_ZonesForTimeMode;
40 39 QString m_ZoneForGraphMode;
41 40 QVector<std::shared_ptr<DBCatalogue> > m_DisplayedCatalogues;
42 41 bool m_AllEventDisplayed = false;
43 42 QVector<VisualizationGraphWidget *> m_CustomGraphs;
44 43
45 44 VisualizationWidget *m_VisualizationWidget = nullptr;
46 45
47 46 void setEvents(const QVector<std::shared_ptr<DBEvent> > &events, CatalogueEventsWidget *widget)
48 47 {
49 48 widget->ui->treeView->setSortingEnabled(false);
50 49 m_Model->setSourceCatalogues(m_DisplayedCatalogues);
51 50 m_Model->setEvents(events);
52 51 widget->ui->treeView->setSortingEnabled(true);
53 52
54 53 for (auto event : events) {
55 54 if (sqpApp->catalogueController().eventHasChanges(event)) {
56 55 auto index = m_Model->indexOf(event);
57 56 widget->setEventChanges(event, true);
58 57 }
59 58 }
60 59 }
61 60
62 61 void addEvent(const std::shared_ptr<DBEvent> &event, QTreeView *treeView)
63 62 {
64 63 treeView->setSortingEnabled(false);
65 64 m_Model->addEvent(event);
66 65 treeView->setSortingEnabled(true);
67 66 }
68 67
69 68 void removeEvent(const std::shared_ptr<DBEvent> &event, QTreeView *treeView)
70 69 {
71 70 treeView->setSortingEnabled(false);
72 71 m_Model->removeEvent(event);
73 72 treeView->setSortingEnabled(true);
74 73 }
75 74
76 75 QStringList getAvailableVisualizationZoneList() const
77 76 {
78 77 if (m_VisualizationWidget) {
79 78 if (auto tab = m_VisualizationWidget->currentTabWidget()) {
80 79 return tab->availableZoneWidgets();
81 80 }
82 81 }
83 82
84 83 return QStringList{};
85 84 }
86 85
87 86 QStringList selectZone(QWidget *parent, const QStringList &selectedZones,
88 87 bool allowMultiSelection, bool addNewZoneOption, const QPoint &location)
89 88 {
90 89 auto availableZones = getAvailableVisualizationZoneList();
91 90 if (!addNewZoneOption && availableZones.isEmpty()) {
92 91 return QStringList{};
93 92 }
94 93
95 94 QActionGroup actionGroup{parent};
96 95 actionGroup.setExclusive(!allowMultiSelection);
97 96
98 97 QVector<QAction *> zoneActions;
99 98
100 99 QMenu selectionMenu{parent};
101 100
102 101 if (addNewZoneOption) {
103 102 availableZones.prepend(NEW_ZONE_TEXT);
104 103 }
105 104
106 105 selectionMenu.addSeparator();
107 106 for (auto zone : availableZones) {
108 107 auto zoneAction = selectionMenu.addAction(zone);
109 108 zoneAction->setCheckable(true);
110 109 zoneAction->setChecked(selectedZones.contains(zone));
111 110 actionGroup.addAction(zoneAction);
112 111 zoneActions << zoneAction;
113 112 }
114 113
115 114 auto resultAction = selectionMenu.exec(QCursor::pos());
116 115
117 116 QStringList result;
118 117
119 118 if (resultAction == nullptr) {
120 119 result = selectedZones;
121 120 }
122 121 else {
123 122 for (auto zoneAction : zoneActions) {
124 123 if (zoneAction->isChecked()) {
125 124 result << zoneAction->text();
126 125 }
127 126 }
128 127 }
129 128
130 129 return result;
131 130 }
132 131
133 132 void updateForTimeMode(QTreeView *treeView)
134 133 {
135 134 auto selectedRows = treeView->selectionModel()->selectedRows();
136 135
137 136 if (selectedRows.count() == 1) {
138 137 auto event = m_Model->getEvent(selectedRows.first());
139 138 if (event) {
140 139 if (m_VisualizationWidget) {
141 140 if (auto tab = m_VisualizationWidget->currentTabWidget()) {
142 141
143 142 for (auto zoneName : m_ZonesForTimeMode) {
144 143 if (auto zone = tab->getZoneWithName(zoneName)) {
145 144 DateTimeRange eventRange;
146 145 eventRange.m_TStart = event->getTStart();
147 146 eventRange.m_TEnd = event->getTEnd();
148 147 zone->setZoneRange(eventRange);
149 148 }
150 149 }
151 150 }
152 151 else {
153 152 qCWarning(LOG_CatalogueEventsWidget())
154 153 << "updateTimeZone: no tab found in the visualization";
155 154 }
156 155 }
157 156 else {
158 157 qCWarning(LOG_CatalogueEventsWidget())
159 158 << "updateTimeZone: visualization widget not found";
160 159 }
161 160 }
162 161 }
163 162 else {
164 163 qCWarning(LOG_CatalogueEventsWidget())
165 164 << "updateTimeZone: not compatible with multiple events selected";
166 165 }
167 166 }
168 167
169 168 QVector<DateTimeRange> getGraphRanges(const std::shared_ptr<DBEvent> &event)
170 169 {
171 170 // Retrieves the range of each product and the maximum size
172 171 QVector<DateTimeRange> graphRanges;
173 172 double maxDt = 0;
174 173 for (auto eventProduct : event->getEventProducts()) {
175 174 DateTimeRange eventRange;
176 175 eventRange.m_TStart = eventProduct.getTStart();
177 176 eventRange.m_TEnd = eventProduct.getTEnd();
178 177 graphRanges << eventRange;
179 178
180 179 auto dt = eventRange.m_TEnd - eventRange.m_TStart;
181 180 if (dt > maxDt) {
182 181 maxDt = dt;
183 182 }
184 183 }
185 184
186 185 // Adds the marge
187 186 maxDt *= (100.0 + EVENT_RANGE_MARGE) / 100.0;
188 187
189 188 // Corrects the graph ranges so that they all have the same size
190 189 QVector<DateTimeRange> correctedGraphRanges;
191 190 for (auto range : graphRanges) {
192 191 auto dt = range.m_TEnd - range.m_TStart;
193 192 auto diff = qAbs((maxDt - dt) / 2.0);
194 193
195 194 DateTimeRange correctedRange;
196 195 correctedRange.m_TStart = range.m_TStart - diff;
197 196 correctedRange.m_TEnd = range.m_TEnd + diff;
198 197
199 198 correctedGraphRanges << correctedRange;
200 199 }
201 200
202 201 return correctedGraphRanges;
203 202 }
204 203
205 204 void updateForGraphMode(CatalogueEventsWidget *catalogueEventWidget)
206 205 {
207 206 auto selectedRows = catalogueEventWidget->ui->treeView->selectionModel()->selectedRows();
208 207 if (selectedRows.count() != 1) {
209 208 qCWarning(LOG_CatalogueEventsWidget())
210 209 << "updateGraphMode: not compatible with multiple events selected";
211 210 return;
212 211 }
213 212
214 213 if (!m_VisualizationWidget) {
215 214 qCWarning(LOG_CatalogueEventsWidget())
216 215 << "updateGraphMode: visualization widget not found";
217 216 return;
218 217 }
219 218
220 219 auto event = m_Model->getEvent(selectedRows.first());
221 220 if (!event) {
222 221 // A event product is probably selected
223 222 qCInfo(LOG_CatalogueEventsWidget()) << "updateGraphMode: no events are selected";
224 223 return;
225 224 }
226 225
227 226 auto tab = m_VisualizationWidget->currentTabWidget();
228 227 if (!tab) {
229 228 qCWarning(LOG_CatalogueEventsWidget())
230 229 << "updateGraphMode: no tab found in the visualization";
231 230 return;
232 231 }
233 232
234 233 auto isNewZone = m_ZoneForGraphMode == NEW_ZONE_TEXT;
235 234 auto zone = tab->getZoneWithName(m_ZoneForGraphMode);
236 235 if (!isNewZone && !zone) {
237 236 qCWarning(LOG_CatalogueEventsWidget()) << "updateGraphMode: zone not found";
238 237 return;
239 238 }
240 239
241 240 // Closes the previous graph and delete the asociated variables
242 241 for (auto graph : m_CustomGraphs) {
243 242 graph->close();
244 243 auto variables = graph->variables();
245 244 for(const auto& variable:variables)
246 245 sqpApp->variableController().deleteVariable(variable);
247 246 }
248 247 m_CustomGraphs.clear();
249 248
250 249 // Closes the remaining graphs inside the zone
251 250 if (zone) {
252 251 zone->closeAllGraphs();
253 252 }
254 253
255 254 // Creates the zone if needed
256 255 if (isNewZone) {
257 256 zone = tab->createEmptyZone(0);
258 257 m_ZoneForGraphMode = zone->name();
259 258 }
260 259
261 260 // Calculates the range of each graph which will be created
262 261 auto graphRange = getGraphRanges(event);
263 262
264 263 // Loops through the event products and create the graph
265 264 auto itRange = graphRange.cbegin();
266 265 for (auto eventProduct : event->getEventProducts()) {
267 266 auto productId = eventProduct.getProductId();
268 267
269 268 auto range = *itRange;
270 269 ++itRange;
271 270
272 271 DateTimeRange productRange;
273 272 productRange.m_TStart = eventProduct.getTStart();
274 273 productRange.m_TEnd = eventProduct.getTEnd();
275 274
276 275 auto context = new QObject{catalogueEventWidget};
277 276 QObject::connect(
278 277 &sqpApp->variableController(), &VariableController2::variableAdded, context,
279 278 [this, catalogueEventWidget, zone, context, event, range, productRange,
280 279 productId](auto variable) {
281 280
282 281 if (variable->metadata().value(DataSourceItem::ID_DATA_KEY).toString()
283 282 == productId) {
284 283 auto graph = zone->createGraph(variable);
285 284 graph->setAutoRangeOnVariableInitialization(false);
286 285
287 286 auto selectionZone
288 287 = graph->addSelectionZone(event->getName(), productRange);
289 288 emit catalogueEventWidget->selectionZoneAdded(event, productId,
290 289 selectionZone);
291 290 m_CustomGraphs << graph;
292 291
293 292 graph->setGraphRange(range, true);
294 293
295 294 // Removes the graph from the graph list if it is closed manually
296 295 QObject::connect(graph, &VisualizationGraphWidget::destroyed,
297 296 [this, graph]() { m_CustomGraphs.removeAll(graph); });
298 297
299 298 delete context; // removes the connection
300 299 }
301 300 },
302 301 Qt::QueuedConnection);
303 302
304 303 QMetaObject::invokeMethod(&sqpApp->dataSourceController(),
305 304 "requestVariableFromProductIdKey", Qt::QueuedConnection,
306 305 Q_ARG(QString, productId));
307 306 }
308 307 }
309 308
310 309 void getSelectedItems(
311 310 QTreeView *treeView, QVector<std::shared_ptr<DBEvent> > &events,
312 311 QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > > &eventProducts)
313 312 {
314 313 for (auto rowIndex : treeView->selectionModel()->selectedRows()) {
315 314 auto itemType = m_Model->itemTypeOf(rowIndex);
316 315 if (itemType == CatalogueEventsModel::ItemType::Event) {
317 316 events << m_Model->getEvent(rowIndex);
318 317 }
319 318 else if (itemType == CatalogueEventsModel::ItemType::EventProduct) {
320 319 eventProducts << qMakePair(m_Model->getParentEvent(rowIndex),
321 320 m_Model->getEventProduct(rowIndex));
322 321 }
323 322 }
324 323 }
325 324 };
326 325
327 326 CatalogueEventsWidget::CatalogueEventsWidget(QWidget *parent)
328 327 : QWidget(parent),
329 328 ui(new Ui::CatalogueEventsWidget),
330 329 impl{spimpl::make_unique_impl<CatalogueEventsWidgetPrivate>()}
331 330 {
332 331 ui->setupUi(this);
333 332
334 333 impl->m_Model = new CatalogueEventsModel{this};
335 334 ui->treeView->setModel(impl->m_Model);
336 335
337 336 ui->treeView->setSortingEnabled(true);
338 337 ui->treeView->setDragDropMode(QAbstractItemView::DragDrop);
339 338 ui->treeView->setDragEnabled(true);
340 339
341 340
342 341 connect(ui->btnTime, &QToolButton::clicked, [this](auto checked) {
343 342 if (checked) {
344 343 ui->btnChart->setChecked(false);
345 344 impl->m_ZonesForTimeMode
346 345 = impl->selectZone(this, impl->m_ZonesForTimeMode, true, false,
347 346 this->mapToGlobal(ui->btnTime->frameGeometry().center()));
348 347
349 348 impl->updateForTimeMode(ui->treeView);
350 349 }
351 350 });
352 351
353 352 connect(ui->btnChart, &QToolButton::clicked, [this](auto checked) {
354 353 if (checked) {
355 354 ui->btnTime->setChecked(false);
356 355
357 356 impl->m_ZoneForGraphMode
358 357 = impl->selectZone(this, {impl->m_ZoneForGraphMode}, false, true,
359 358 this->mapToGlobal(ui->btnChart->frameGeometry().center()))
360 359 .value(0);
361 360
362 361 impl->updateForGraphMode(this);
363 362 }
364 363 });
365 364
366 365 connect(ui->btnRemove, &QToolButton::clicked, [this]() {
367 366 QVector<std::shared_ptr<DBEvent> > events;
368 367 QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > > eventProducts;
369 368 impl->getSelectedItems(ui->treeView, events, eventProducts);
370 369
371 370 if (!events.isEmpty() && eventProducts.isEmpty()) {
372 371
373 372 auto canRemoveEvent
374 373 = !this->isAllEventsDisplayed()
375 374 || (QMessageBox::warning(
376 375 this, tr("Remove Event(s)"),
377 376 tr("The selected event(s) will be permanently removed "
378 377 "from the repository!\nAre you sure you want to continue?"),
379 378 QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
380 379 == QMessageBox::Yes);
381 380
382 381 if (canRemoveEvent) {
383 382 for (auto event : events) {
384 383 if (this->isAllEventsDisplayed()) {
385 384 sqpApp->catalogueController().removeEvent(event);
386 385 impl->removeEvent(event, ui->treeView);
387 386 }
388 387 else {
389 388 QVector<std::shared_ptr<DBCatalogue> > modifiedCatalogues;
390 389 for (auto catalogue : this->displayedCatalogues()) {
391 390 if (catalogue->removeEvent(event->getUniqId())) {
392 391 sqpApp->catalogueController().updateCatalogue(catalogue);
393 392 modifiedCatalogues << catalogue;
394 393 }
395 394 }
396 395 if (!modifiedCatalogues.empty()) {
397 396 emit eventCataloguesModified(modifiedCatalogues);
398 397 }
399 398 }
400 399 impl->m_Model->removeEvent(event);
401 400 }
402 401
403 402
404 403 emit this->eventsRemoved(events);
405 404 }
406 405 }
407 406 });
408 407
409 408 connect(ui->treeView, &QTreeView::clicked, this, &CatalogueEventsWidget::emitSelection);
410 409 connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
411 410 &CatalogueEventsWidget::emitSelection);
412 411
413 412 ui->btnRemove->setEnabled(false); // Disabled by default when nothing is selected
414 413 connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, [this]() {
415 414 auto isNotMultiSelection = ui->treeView->selectionModel()->selectedRows().count() <= 1;
416 415 ui->btnChart->setEnabled(isNotMultiSelection);
417 416 ui->btnTime->setEnabled(isNotMultiSelection);
418 417
419 418 if (isNotMultiSelection && ui->btnTime->isChecked()) {
420 419 impl->updateForTimeMode(ui->treeView);
421 420 }
422 421 else if (isNotMultiSelection && ui->btnChart->isChecked()) {
423 422 impl->updateForGraphMode(this);
424 423 }
425 424
426 425 QVector<std::shared_ptr<DBEvent> > events;
427 426 QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > > eventProducts;
428 427 impl->getSelectedItems(ui->treeView, events, eventProducts);
429 428 ui->btnRemove->setEnabled(!events.isEmpty() && eventProducts.isEmpty());
430 429 });
431 430
432 431 ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
433 432 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Tags,
434 433 QHeaderView::Stretch);
435 434 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Validation,
436 435 QHeaderView::ResizeToContents);
437 436 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Name,
438 437 QHeaderView::Interactive);
439 438 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::TStart,
440 439 QHeaderView::ResizeToContents);
441 440 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::TEnd,
442 441 QHeaderView::ResizeToContents);
443 442 ui->treeView->header()->setSortIndicatorShown(true);
444 443
445 444 connect(impl->m_Model, &CatalogueEventsModel::modelSorted, [this]() {
446 445 auto allEvents = impl->m_Model->events();
447 446 for (auto event : allEvents) {
448 447 setEventChanges(event, sqpApp->catalogueController().eventHasChanges(event));
449 448 }
450 449 });
451 450
452 451 populateWithAllEvents();
453 452 }
454 453
455 454 CatalogueEventsWidget::~CatalogueEventsWidget()
456 455 {
457 456 delete ui;
458 457 }
459 458
460 459 void CatalogueEventsWidget::setVisualizationWidget(VisualizationWidget *visualization)
461 460 {
462 461 impl->m_VisualizationWidget = visualization;
463 462 }
464 463
465 464 void CatalogueEventsWidget::addEvent(const std::shared_ptr<DBEvent> &event)
466 465 {
467 466 impl->addEvent(event, ui->treeView);
468 467 }
469 468
470 469 void CatalogueEventsWidget::setEventChanges(const std::shared_ptr<DBEvent> &event, bool hasChanges)
471 470 {
472 471 impl->m_Model->refreshEvent(event);
473 472
474 473 auto eventIndex = impl->m_Model->indexOf(event);
475 474 auto validationIndex
476 475 = eventIndex.sibling(eventIndex.row(), (int)CatalogueEventsModel::Column::Validation);
477 476
478 477 if (validationIndex.isValid()) {
479 478 if (hasChanges) {
480 479 if (ui->treeView->indexWidget(validationIndex) == nullptr) {
481 480 auto widget = CatalogueExplorerHelper::buildValidationWidget(
482 481 ui->treeView,
483 482 [this, event]() {
484 483 sqpApp->catalogueController().saveEvent(event);
485 484 setEventChanges(event, false);
486 485 },
487 486 [this, event]() {
488 487 bool removed = false;
489 488 sqpApp->catalogueController().discardEvent(event, removed);
490 489 if (removed) {
491 490 impl->m_Model->removeEvent(event);
492 491 }
493 492 else {
494 493 setEventChanges(event, false);
495 494 impl->m_Model->refreshEvent(event, true);
496 495 }
497 496 emitSelection();
498 497 });
499 498 ui->treeView->setIndexWidget(validationIndex, widget);
500 499 ui->treeView->header()->resizeSection((int)CatalogueEventsModel::Column::Validation,
501 500 QHeaderView::ResizeToContents);
502 501 }
503 502 }
504 503 else {
505 504 // Note: the widget is destroyed
506 505 ui->treeView->setIndexWidget(validationIndex, nullptr);
507 506 }
508 507 }
509 508 else {
510 509 qCWarning(LOG_CatalogueEventsWidget())
511 510 << "setEventChanges: the event is not displayed in the model.";
512 511 }
513 512 }
514 513
515 514 QVector<std::shared_ptr<DBCatalogue> > CatalogueEventsWidget::displayedCatalogues() const
516 515 {
517 516 return impl->m_DisplayedCatalogues;
518 517 }
519 518
520 519 bool CatalogueEventsWidget::isAllEventsDisplayed() const
521 520 {
522 521 return impl->m_AllEventDisplayed;
523 522 }
524 523
525 524 bool CatalogueEventsWidget::isEventDisplayed(const std::shared_ptr<DBEvent> &event) const
526 525 {
527 526 return impl->m_Model->indexOf(event).isValid();
528 527 }
529 528
530 529 void CatalogueEventsWidget::refreshEvent(const std::shared_ptr<DBEvent> &event)
531 530 {
532 531 impl->m_Model->refreshEvent(event, true);
533 532 }
534 533
535 534 void CatalogueEventsWidget::populateWithCatalogues(
536 535 const QVector<std::shared_ptr<DBCatalogue> > &catalogues)
537 536 {
538 537 impl->m_DisplayedCatalogues = catalogues;
539 538 impl->m_AllEventDisplayed = false;
540 539
541 540 QSet<QUuid> eventIds;
542 541 QVector<std::shared_ptr<DBEvent> > events;
543 542
544 543 for (auto catalogue : catalogues) {
545 544 auto catalogueEvents = sqpApp->catalogueController().retrieveEventsFromCatalogue(catalogue);
546 545 for (auto event : catalogueEvents) {
547 546 if (!eventIds.contains(event->getUniqId())) {
548 547 events << event;
549 548 eventIds.insert(event->getUniqId());
550 549 }
551 550 }
552 551 }
553 552
554 553 impl->setEvents(events, this);
555 554 }
556 555
557 556 void CatalogueEventsWidget::populateWithAllEvents()
558 557 {
559 558 impl->m_DisplayedCatalogues.clear();
560 559 impl->m_AllEventDisplayed = true;
561 560
562 561 auto allEvents = sqpApp->catalogueController().retrieveAllEvents();
563 562
564 563 QVector<std::shared_ptr<DBEvent> > events;
565 564 for (auto event : allEvents) {
566 565 events << event;
567 566 }
568 567
569 568 impl->setEvents(events, this);
570 569 }
571 570
572 571 void CatalogueEventsWidget::clear()
573 572 {
574 573 impl->m_DisplayedCatalogues.clear();
575 574 impl->m_AllEventDisplayed = false;
576 575 impl->setEvents({}, this);
577 576 }
578 577
579 578 void CatalogueEventsWidget::refresh()
580 579 {
581 580 if (isAllEventsDisplayed()) {
582 581 populateWithAllEvents();
583 582 }
584 583 else if (!impl->m_DisplayedCatalogues.isEmpty()) {
585 584 populateWithCatalogues(impl->m_DisplayedCatalogues);
586 585 }
587 586 }
588 587
589 588 void CatalogueEventsWidget::emitSelection()
590 589 {
591 590 QVector<std::shared_ptr<DBEvent> > events;
592 591 QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > > eventProducts;
593 592 impl->getSelectedItems(ui->treeView, events, eventProducts);
594 593
595 594 if (!events.isEmpty() && eventProducts.isEmpty()) {
596 595 emit eventsSelected(events);
597 596 }
598 597 else if (events.isEmpty() && !eventProducts.isEmpty()) {
599 598 emit eventProductsSelected(eventProducts);
600 599 }
601 600 else {
602 601 emit selectionCleared();
603 602 }
604 603 }
605 604
606 605
607 606 void CatalogueEventsWidget::keyPressEvent(QKeyEvent *event)
608 607 {
609 608 switch (event->key()) {
610 609 case Qt::Key_Delete: {
611 610 ui->btnRemove->click();
612 611 }
613 612 default:
614 613 break;
615 614 }
616 615 }
@@ -1,202 +1,200
1 1 #include "SqpApplication.h"
2 2
3 3 #include <Actions/ActionsGuiController.h>
4 4 #include <Catalogue/CatalogueController.h>
5 5 #include <Data/IDataProvider.h>
6 6 #include <DataSource/DataSourceController.h>
7 7 #include <DragAndDrop/DragDropGuiController.h>
8 8 #include <Network/NetworkController.h>
9 9 #include <QThread>
10 10 #include <Time/TimeController.h>
11 11 #include <Variable/Variable.h>
12 #include <Variable/VariableController.h>
13 12 #include <Variable/VariableController2.h>
14 13 #include <Variable/VariableModel2.h>
15 #include <Variable/VariableModel.h>
16 14 #include <Visualization/VisualizationController.h>
17 15
18 16 Q_LOGGING_CATEGORY(LOG_SqpApplication, "SqpApplication")
19 17
20 18 class SqpApplication::SqpApplicationPrivate {
21 19 public:
22 20 SqpApplicationPrivate()
23 21 : m_VariableController{std::make_shared<VariableController2>()},
24 22 m_VariableModel{m_VariableController},
25 23 m_PlotInterractionMode(SqpApplication::PlotsInteractionMode::None),
26 24 m_PlotCursorMode(SqpApplication::PlotsCursorMode::NoCursor)
27 25 {
28 26 // /////////////////////////////// //
29 27 // Connections between controllers //
30 28 // /////////////////////////////// //
31 29
32 30 // VariableController <-> DataSourceController
33 31 connect(&m_DataSourceController,
34 32 SIGNAL(variableCreationRequested(const QString &, const QVariantHash &,
35 33 std::shared_ptr<IDataProvider>)),
36 34 m_VariableController.get(),
37 35 SLOT(createVariable(const QString &, const QVariantHash &,
38 36 std::shared_ptr<IDataProvider>)));
39 37
40 38 // connect(m_VariableController->variableModel(), &VariableModel::requestVariable,
41 39 // m_DataSourceController.get(), &DataSourceController::requestVariable);
42 40
43 41 // VariableController <-> VisualizationController
44 42 // connect(m_VariableController.get(),
45 43 // SIGNAL(variableAboutToBeDeleted(std::shared_ptr<Variable>)),
46 44 // m_VisualizationController.get(),
47 45 // SIGNAL(variableAboutToBeDeleted(std::shared_ptr<Variable>)), Qt::DirectConnection);
48 46
49 47 // connect(m_VariableController.get(),
50 48 // SIGNAL(rangeChanged(std::shared_ptr<Variable>, const DateTimeRange &)),
51 49 // m_VisualizationController.get(),
52 50 // SIGNAL(rangeChanged(std::shared_ptr<Variable>, const DateTimeRange &)));
53 51
54 52
55 53 m_DataSourceController.moveToThread(&m_DataSourceControllerThread);
56 54 m_DataSourceControllerThread.setObjectName("DataSourceControllerThread");
57 55 m_NetworkController.moveToThread(&m_NetworkControllerThread);
58 56 m_NetworkControllerThread.setObjectName("NetworkControllerThread");
59 57 m_VisualizationController.moveToThread(&m_VisualizationControllerThread);
60 58 m_VisualizationControllerThread.setObjectName("VsualizationControllerThread");
61 59
62 60 // Additionnal init
63 61 //m_VariableController->setTimeController(m_TimeController.get());
64 62 }
65 63
66 64 virtual ~SqpApplicationPrivate()
67 65 {
68 66 m_DataSourceControllerThread.quit();
69 67 m_DataSourceControllerThread.wait();
70 68
71 69 m_NetworkControllerThread.quit();
72 70 m_NetworkControllerThread.wait();
73 71
74 72 m_VisualizationControllerThread.quit();
75 73 m_VisualizationControllerThread.wait();
76 74 }
77 75
78 76 DataSourceController m_DataSourceController;
79 77 std::shared_ptr<VariableController2> m_VariableController;
80 78 TimeController m_TimeController;
81 79 NetworkController m_NetworkController;
82 80 VisualizationController m_VisualizationController;
83 81 CatalogueController m_CatalogueController;
84 82 VariableModel2 m_VariableModel;
85 83
86 84 QThread m_DataSourceControllerThread;
87 85 QThread m_NetworkControllerThread;
88 86 QThread m_VisualizationControllerThread;
89 87
90 88 DragDropGuiController m_DragDropGuiController;
91 89 ActionsGuiController m_ActionsGuiController;
92 90
93 91 SqpApplication::PlotsInteractionMode m_PlotInterractionMode;
94 92 SqpApplication::PlotsCursorMode m_PlotCursorMode;
95 93 };
96 94
97 95
98 96 SqpApplication::SqpApplication(int &argc, char **argv)
99 97 : QApplication{argc, argv}, impl{spimpl::make_unique_impl<SqpApplicationPrivate>()}
100 98 {
101 99 qCDebug(LOG_SqpApplication()) << tr("SqpApplication construction") << QThread::currentThread();
102 100
103 101 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
104 102
105 103 connect(&impl->m_DataSourceControllerThread, &QThread::started,
106 104 &impl->m_DataSourceController, &DataSourceController::initialize);
107 105 connect(&impl->m_DataSourceControllerThread, &QThread::finished,
108 106 &impl->m_DataSourceController, &DataSourceController::finalize);
109 107
110 108 connect(&impl->m_NetworkControllerThread, &QThread::started, &impl->m_NetworkController,
111 109 &NetworkController::initialize);
112 110 connect(&impl->m_NetworkControllerThread, &QThread::finished, &impl->m_NetworkController,
113 111 &NetworkController::finalize);
114 112
115 113 connect(&impl->m_VisualizationControllerThread, &QThread::started,
116 114 &impl->m_VisualizationController, &VisualizationController::initialize);
117 115 connect(&impl->m_VisualizationControllerThread, &QThread::finished,
118 116 &impl->m_VisualizationController, &VisualizationController::finalize);
119 117
120 118 impl->m_DataSourceControllerThread.start();
121 119 impl->m_NetworkControllerThread.start();
122 120 impl->m_VisualizationControllerThread.start();
123 121 impl->m_CatalogueController.initialize();
124 122 }
125 123
126 124 SqpApplication::~SqpApplication()
127 125 {
128 126 }
129 127
130 128 void SqpApplication::initialize()
131 129 {
132 130 }
133 131
134 132 DataSourceController &SqpApplication::dataSourceController() noexcept
135 133 {
136 134 return impl->m_DataSourceController;
137 135 }
138 136
139 137 NetworkController &SqpApplication::networkController() noexcept
140 138 {
141 139 return impl->m_NetworkController;
142 140 }
143 141
144 142 TimeController &SqpApplication::timeController() noexcept
145 143 {
146 144 return impl->m_TimeController;
147 145 }
148 146
149 147 VariableController2 &SqpApplication::variableController() noexcept
150 148 {
151 149 return *impl->m_VariableController;
152 150 }
153 151
154 152 std::shared_ptr<VariableController2> SqpApplication::variableControllerOwner() noexcept
155 153 {
156 154 return impl->m_VariableController;
157 155 }
158 156
159 157 //VariableModel2 &SqpApplication::variableModel() noexcept
160 158 //{
161 159 // return impl->m_VariableModel;
162 160 //}
163 161
164 162 VisualizationController &SqpApplication::visualizationController() noexcept
165 163 {
166 164 return impl->m_VisualizationController;
167 165 }
168 166
169 167 CatalogueController &SqpApplication::catalogueController() noexcept
170 168 {
171 169 return impl->m_CatalogueController;
172 170 }
173 171
174 172 DragDropGuiController &SqpApplication::dragDropGuiController() noexcept
175 173 {
176 174 return impl->m_DragDropGuiController;
177 175 }
178 176
179 177 ActionsGuiController &SqpApplication::actionsGuiController() noexcept
180 178 {
181 179 return impl->m_ActionsGuiController;
182 180 }
183 181
184 182 SqpApplication::PlotsInteractionMode SqpApplication::plotsInteractionMode() const
185 183 {
186 184 return impl->m_PlotInterractionMode;
187 185 }
188 186
189 187 void SqpApplication::setPlotsInteractionMode(SqpApplication::PlotsInteractionMode mode)
190 188 {
191 189 impl->m_PlotInterractionMode = mode;
192 190 }
193 191
194 192 SqpApplication::PlotsCursorMode SqpApplication::plotsCursorMode() const
195 193 {
196 194 return impl->m_PlotCursorMode;
197 195 }
198 196
199 197 void SqpApplication::setPlotsCursorMode(SqpApplication::PlotsCursorMode mode)
200 198 {
201 199 impl->m_PlotCursorMode = mode;
202 200 }
@@ -1,1083 +1,1083
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 #include <Variable/VariableController.h>
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 }
68 68
69 69 void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
70 70 const DateTimeRange &range)
71 71 {
72 72 VisualizationGraphHelper::updateData(plottables, variable, range);
73 73
74 74 // Prevents that data has changed to update rendering
75 75 m_RenderingDelegate->onPlotUpdated();
76 76 }
77 77
78 78 QString m_Name;
79 79 // 1 variable -> n qcpplot
80 80 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
81 81 GraphFlags m_Flags;
82 82 bool m_IsCalibration;
83 83 /// Delegate used to attach rendering features to the plot
84 84 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
85 85
86 86 QCPItemRect *m_DrawingZoomRect = nullptr;
87 87 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
88 88
89 89 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
90 90 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
91 91
92 92 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
93 93 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
94 94 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
95 95
96 96 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
97 97
98 98 bool m_VariableAutoRangeOnInit = true;
99 99
100 100 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
101 101 {
102 102 removeDrawingRect(plot);
103 103
104 104 auto axisPos = posToAxisPos(pos, plot);
105 105
106 106 m_DrawingZoomRect = new QCPItemRect{&plot};
107 107 QPen p;
108 108 p.setWidth(2);
109 109 m_DrawingZoomRect->setPen(p);
110 110
111 111 m_DrawingZoomRect->topLeft->setCoords(axisPos);
112 112 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
113 113 }
114 114
115 115 void removeDrawingRect(QCustomPlot &plot)
116 116 {
117 117 if (m_DrawingZoomRect) {
118 118 plot.removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
119 119 m_DrawingZoomRect = nullptr;
120 120 plot.replot(QCustomPlot::rpQueuedReplot);
121 121 }
122 122 }
123 123
124 124 void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph)
125 125 {
126 126 endDrawingZone(graph);
127 127
128 128 auto axisPos = posToAxisPos(pos, graph->plot());
129 129
130 130 m_DrawingZone = new VisualizationSelectionZoneItem{&graph->plot()};
131 131 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
132 132 m_DrawingZone->setEditionEnabled(false);
133 133 }
134 134
135 135 void endDrawingZone(VisualizationGraphWidget *graph)
136 136 {
137 137 if (m_DrawingZone) {
138 138 auto drawingZoneRange = m_DrawingZone->range();
139 139 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
140 140 m_DrawingZone->setEditionEnabled(true);
141 141 addSelectionZone(m_DrawingZone);
142 142 }
143 143 else {
144 144 graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot
145 145 }
146 146
147 147 graph->plot().replot(QCustomPlot::rpQueuedReplot);
148 148 m_DrawingZone = nullptr;
149 149 }
150 150 }
151 151
152 152 void setSelectionZonesEditionEnabled(bool value)
153 153 {
154 154 for (auto s : m_SelectionZones) {
155 155 s->setEditionEnabled(value);
156 156 }
157 157 }
158 158
159 159 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
160 160
161 161 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos,
162 162 const QCustomPlot &plot) const
163 163 {
164 164 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
165 165 auto minDistanceToZone = -1;
166 166 for (auto zone : m_SelectionZones) {
167 167 auto distanceToZone = zone->selectTest(pos, false);
168 168 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
169 169 && distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
170 170 selectionZoneItemUnderCursor = zone;
171 171 }
172 172 }
173 173
174 174 return selectionZoneItemUnderCursor;
175 175 }
176 176
177 177 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
178 178 const QCustomPlot &plot) const
179 179 {
180 180 QVector<VisualizationSelectionZoneItem *> zones;
181 181 for (auto zone : m_SelectionZones) {
182 182 auto distanceToZone = zone->selectTest(pos, false);
183 183 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
184 184 zones << zone;
185 185 }
186 186 }
187 187
188 188 return zones;
189 189 }
190 190
191 191 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
192 192 {
193 193 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
194 194 zone->moveToTop();
195 195 m_SelectionZones.removeAll(zone);
196 196 m_SelectionZones.append(zone);
197 197 }
198 198 }
199 199
200 200 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
201 201 {
202 202 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
203 203 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
204 204 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
205 205 }
206 206
207 207 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
208 208 {
209 209 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
210 210 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
211 211 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
212 212 }
213 213 };
214 214
215 215 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
216 216 : VisualizationDragWidget{parent},
217 217 ui{new Ui::VisualizationGraphWidget},
218 218 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
219 219 {
220 220 ui->setupUi(this);
221 221
222 222 // 'Close' options : widget is deleted when closed
223 223 setAttribute(Qt::WA_DeleteOnClose);
224 224
225 225 // Set qcpplot properties :
226 226 // - zoom is enabled
227 227 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
228 228 ui->widget->setInteractions(QCP::iRangeZoom);
229 229 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical);
230 230
231 231 // The delegate must be initialized after the ui as it uses the plot
232 232 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
233 233
234 234 // Init the cursors
235 235 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
236 236 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
237 237 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
238 238 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
239 239
240 240 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
241 241 connect(ui->widget, &QCustomPlot::mouseRelease, this,
242 242 &VisualizationGraphWidget::onMouseRelease);
243 243 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
244 244 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
245 245 connect(ui->widget, &QCustomPlot::mouseDoubleClick, this,
246 246 &VisualizationGraphWidget::onMouseDoubleClick);
247 247 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
248 248 &QCPAxis::rangeChanged),
249 249 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
250 250
251 251 // Activates menu when right clicking on the graph
252 252 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
253 253 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
254 254 &VisualizationGraphWidget::onGraphMenuRequested);
255 255
256 256 //@TODO implement this :)
257 257 // connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
258 258 // &VariableController::onRequestDataLoading);
259 259
260 260 // connect(&sqpApp->variableController(), &VariableController2::updateVarDisplaying, this,
261 261 // &VisualizationGraphWidget::onUpdateVarDisplaying);
262 262
263 263 // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable.
264 264 plot().setPlottingHint(QCP::phFastPolylines, true);
265 265 }
266 266
267 267
268 268 VisualizationGraphWidget::~VisualizationGraphWidget()
269 269 {
270 270 delete ui;
271 271 }
272 272
273 273 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
274 274 {
275 275 auto parent = parentWidget();
276 276 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
277 277 parent = parent->parentWidget();
278 278 }
279 279
280 280 return qobject_cast<VisualizationZoneWidget *>(parent);
281 281 }
282 282
283 283 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
284 284 {
285 285 auto parent = parentWidget();
286 286 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
287 287 parent = parent->parentWidget();
288 288 }
289 289
290 290 return qobject_cast<VisualizationWidget *>(parent);
291 291 }
292 292
293 293 void VisualizationGraphWidget::setFlags(GraphFlags flags)
294 294 {
295 295 impl->m_Flags = std::move(flags);
296 296 }
297 297
298 298 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, DateTimeRange range)
299 299 {
300 300 /// Lambda used to set graph's units and range according to the variable passed in parameter
301 301 auto loadRange = [this](std::shared_ptr<Variable> variable, const DateTimeRange &range) {
302 302 impl->m_RenderingDelegate->setAxesUnits(*variable);
303 303
304 304 this->setFlags(GraphFlag::DisableAll);
305 305 setGraphRange(range);
306 306 this->setFlags(GraphFlag::EnableAll);
307 307 emit requestDataLoading({variable}, range, false);
308 308 };
309 309
310 310 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
311 311
312 312 // Calls update of graph's range and units when the data of the variable have been initialized.
313 313 // Note: we use QueuedConnection here as the update event must be called in the UI thread
314 314 connect(variable.get(), &Variable::dataInitialized, this,
315 315 [ varW = std::weak_ptr<Variable>{variable}, range, loadRange, this ]() {
316 316 if (auto var = varW.lock()) {
317 317 // If the variable is the first added in the graph, we load its range
318 318 auto firstVariableInGraph = range == INVALID_RANGE;
319 319 auto loadedRange = graphRange();
320 320 if (impl->m_VariableAutoRangeOnInit) {
321 321 loadedRange = firstVariableInGraph ? var->range() : range;
322 322 }
323 323 loadRange(var, loadedRange);
324 324 setYRange(var);
325 325 }
326 326 },
327 327 Qt::QueuedConnection);
328 328
329 329 // Uses delegate to create the qcpplot components according to the variable
330 330 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
331 331
332 332 // Sets graph properties
333 333 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
334 334
335 335 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
336 336
337 337 // If the variable already has its data loaded, load its units and its range in the graph
338 338 if (variable->dataSeries() != nullptr) {
339 339 loadRange(variable, range);
340 340 }
341 341
342 342 emit variableAdded(variable);
343 343 }
344 344
345 345 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
346 346 {
347 347 // Each component associated to the variable :
348 348 // - is removed from qcpplot (which deletes it)
349 349 // - is no longer referenced in the map
350 350 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
351 351 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
352 352 emit variableAboutToBeRemoved(variable);
353 353
354 354 auto &plottablesMap = variableIt->second;
355 355
356 356 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
357 357 plottableIt != plottableEnd;) {
358 358 ui->widget->removePlottable(plottableIt->second);
359 359 plottableIt = plottablesMap.erase(plottableIt);
360 360 }
361 361
362 362 impl->m_VariableToPlotMultiMap.erase(variableIt);
363 363 }
364 364
365 365 // Updates graph
366 366 ui->widget->replot();
367 367 }
368 368
369 369 std::vector<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
370 370 {
371 371 auto variables = std::vector<std::shared_ptr<Variable> >{};
372 372 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
373 373 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
374 374 variables.push_back (it->first);
375 375 }
376 376
377 377 return variables;
378 378 }
379 379
380 380 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
381 381 {
382 382 if (!variable) {
383 383 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
384 384 return;
385 385 }
386 386
387 387 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
388 388 }
389 389
390 390 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
391 391 {
392 392 auto graphRange = ui->widget->xAxis->range();
393 393 return DateTimeRange{graphRange.lower, graphRange.upper};
394 394 }
395 395
396 396 void VisualizationGraphWidget::setGraphRange(const DateTimeRange &range, bool calibration)
397 397 {
398 398 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
399 399
400 400 if (calibration) {
401 401 impl->m_IsCalibration = true;
402 402 }
403 403
404 404 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
405 405 ui->widget->replot();
406 406
407 407 if (calibration) {
408 408 impl->m_IsCalibration = false;
409 409 }
410 410
411 411 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
412 412 }
413 413
414 414 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
415 415 {
416 416 impl->m_VariableAutoRangeOnInit = value;
417 417 }
418 418
419 419 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
420 420 {
421 421 QVector<DateTimeRange> ranges;
422 422 for (auto zone : impl->m_SelectionZones) {
423 423 ranges << zone->range();
424 424 }
425 425
426 426 return ranges;
427 427 }
428 428
429 429 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange> &ranges)
430 430 {
431 431 for (const auto &range : ranges) {
432 432 // note: ownership is transfered to QCustomPlot
433 433 auto zone = new VisualizationSelectionZoneItem(&plot());
434 434 zone->setRange(range.m_TStart, range.m_TEnd);
435 435 impl->addSelectionZone(zone);
436 436 }
437 437
438 438 plot().replot(QCustomPlot::rpQueuedReplot);
439 439 }
440 440
441 441 VisualizationSelectionZoneItem *VisualizationGraphWidget::addSelectionZone(const QString &name,
442 442 const DateTimeRange &range)
443 443 {
444 444 // note: ownership is transfered to QCustomPlot
445 445 auto zone = new VisualizationSelectionZoneItem(&plot());
446 446 zone->setName(name);
447 447 zone->setRange(range.m_TStart, range.m_TEnd);
448 448 impl->addSelectionZone(zone);
449 449
450 450 plot().replot(QCustomPlot::rpQueuedReplot);
451 451
452 452 return zone;
453 453 }
454 454
455 455 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
456 456 {
457 457 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
458 458
459 459 if (impl->m_HoveredZone == selectionZone) {
460 460 impl->m_HoveredZone = nullptr;
461 461 setCursor(Qt::ArrowCursor);
462 462 }
463 463
464 464 impl->m_SelectionZones.removeAll(selectionZone);
465 465 plot().removeItem(selectionZone);
466 466 plot().replot(QCustomPlot::rpQueuedReplot);
467 467 }
468 468
469 469 void VisualizationGraphWidget::undoZoom()
470 470 {
471 471 auto zoom = impl->m_ZoomStack.pop();
472 472 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
473 473 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
474 474
475 475 axisX->setRange(zoom.first);
476 476 axisY->setRange(zoom.second);
477 477
478 478 plot().replot(QCustomPlot::rpQueuedReplot);
479 479 }
480 480
481 481 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
482 482 {
483 483 if (visitor) {
484 484 visitor->visit(this);
485 485 }
486 486 else {
487 487 qCCritical(LOG_VisualizationGraphWidget())
488 488 << tr("Can't visit widget : the visitor is null");
489 489 }
490 490 }
491 491
492 492 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
493 493 {
494 494 auto isSpectrogram = [](const auto &variable) {
495 495 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
496 496 };
497 497
498 498 // - A spectrogram series can't be dropped on graph with existing plottables
499 499 // - No data series can be dropped on graph with existing spectrogram series
500 500 return isSpectrogram(variable)
501 501 ? impl->m_VariableToPlotMultiMap.empty()
502 502 : std::none_of(
503 503 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
504 504 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
505 505 }
506 506
507 507 bool VisualizationGraphWidget::contains(const Variable &variable) const
508 508 {
509 509 // Finds the variable among the keys of the map
510 510 auto variablePtr = &variable;
511 511 auto findVariable
512 512 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
513 513
514 514 auto end = impl->m_VariableToPlotMultiMap.cend();
515 515 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
516 516 return it != end;
517 517 }
518 518
519 519 QString VisualizationGraphWidget::name() const
520 520 {
521 521 return impl->m_Name;
522 522 }
523 523
524 524 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
525 525 {
526 526 auto mimeData = new QMimeData;
527 527
528 528 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position, plot());
529 529 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
530 530 && selectionZoneItemUnderCursor) {
531 531 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
532 532 selectionZoneItemUnderCursor->range()));
533 533 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
534 534 selectionZoneItemUnderCursor->range()));
535 535 }
536 536 else {
537 537 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
538 538
539 539 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
540 540 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
541 541 }
542 542
543 543 return mimeData;
544 544 }
545 545
546 546 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
547 547 {
548 548 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition, plot());
549 549 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
550 550 && selectionZoneItemUnderCursor) {
551 551
552 552 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
553 553 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
554 554
555 555 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
556 556 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
557 557 .toSize();
558 558
559 559 auto pixmap = QPixmap(zoneSize);
560 560 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
561 561
562 562 return pixmap;
563 563 }
564 564
565 565 return QPixmap();
566 566 }
567 567
568 568 bool VisualizationGraphWidget::isDragAllowed() const
569 569 {
570 570 return true;
571 571 }
572 572
573 573 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
574 574 {
575 575 if (highlighted) {
576 576 plot().setBackground(QBrush(QColor("#BBD5EE")));
577 577 }
578 578 else {
579 579 plot().setBackground(QBrush(Qt::white));
580 580 }
581 581
582 582 plot().update();
583 583 }
584 584
585 585 void VisualizationGraphWidget::addVerticalCursor(double time)
586 586 {
587 587 impl->m_VerticalCursor->setPosition(time);
588 588 impl->m_VerticalCursor->setVisible(true);
589 589
590 590 auto text
591 591 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
592 592 impl->m_VerticalCursor->setLabelText(text);
593 593 }
594 594
595 595 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
596 596 {
597 597 impl->m_VerticalCursor->setAbsolutePosition(position);
598 598 impl->m_VerticalCursor->setVisible(true);
599 599
600 600 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
601 601 auto text
602 602 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
603 603 impl->m_VerticalCursor->setLabelText(text);
604 604 }
605 605
606 606 void VisualizationGraphWidget::removeVerticalCursor()
607 607 {
608 608 impl->m_VerticalCursor->setVisible(false);
609 609 plot().replot(QCustomPlot::rpQueuedReplot);
610 610 }
611 611
612 612 void VisualizationGraphWidget::addHorizontalCursor(double value)
613 613 {
614 614 impl->m_HorizontalCursor->setPosition(value);
615 615 impl->m_HorizontalCursor->setVisible(true);
616 616 impl->m_HorizontalCursor->setLabelText(QString::number(value));
617 617 }
618 618
619 619 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
620 620 {
621 621 impl->m_HorizontalCursor->setAbsolutePosition(position);
622 622 impl->m_HorizontalCursor->setVisible(true);
623 623
624 624 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
625 625 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
626 626 }
627 627
628 628 void VisualizationGraphWidget::removeHorizontalCursor()
629 629 {
630 630 impl->m_HorizontalCursor->setVisible(false);
631 631 plot().replot(QCustomPlot::rpQueuedReplot);
632 632 }
633 633
634 634 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
635 635 {
636 636 Q_UNUSED(event);
637 637
638 638 for (auto i : impl->m_SelectionZones) {
639 639 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
640 640 }
641 641
642 642 // Prevents that all variables will be removed from graph when it will be closed
643 643 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
644 644 emit variableAboutToBeRemoved(variableEntry.first);
645 645 }
646 646 }
647 647
648 648 void VisualizationGraphWidget::enterEvent(QEvent *event)
649 649 {
650 650 Q_UNUSED(event);
651 651 impl->m_RenderingDelegate->showGraphOverlay(true);
652 652 }
653 653
654 654 void VisualizationGraphWidget::leaveEvent(QEvent *event)
655 655 {
656 656 Q_UNUSED(event);
657 657 impl->m_RenderingDelegate->showGraphOverlay(false);
658 658
659 659 if (auto parentZone = parentZoneWidget()) {
660 660 parentZone->notifyMouseLeaveGraph(this);
661 661 }
662 662 else {
663 663 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
664 664 }
665 665
666 666 if (impl->m_HoveredZone) {
667 667 impl->m_HoveredZone->setHovered(false);
668 668 impl->m_HoveredZone = nullptr;
669 669 }
670 670 }
671 671
672 672 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
673 673 {
674 674 return *ui->widget;
675 675 }
676 676
677 677 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
678 678 {
679 679 QMenu graphMenu{};
680 680
681 681 // Iterates on variables (unique keys)
682 682 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
683 683 end = impl->m_VariableToPlotMultiMap.cend();
684 684 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
685 685 // 'Remove variable' action
686 686 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
687 687 [ this, var = it->first ]() { removeVariable(var); });
688 688 }
689 689
690 690 if (!impl->m_ZoomStack.isEmpty()) {
691 691 if (!graphMenu.isEmpty()) {
692 692 graphMenu.addSeparator();
693 693 }
694 694
695 695 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
696 696 }
697 697
698 698 // Selection Zone Actions
699 699 auto selectionZoneItem = impl->selectionZoneAt(pos, plot());
700 700 if (selectionZoneItem) {
701 701 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
702 702 selectedItems.removeAll(selectionZoneItem);
703 703 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
704 704
705 705 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
706 706 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
707 707 graphMenu.addSeparator();
708 708 }
709 709
710 710 QHash<QString, QMenu *> subMenus;
711 711 QHash<QString, bool> subMenusEnabled;
712 712 QHash<QString, FilteringAction *> filteredMenu;
713 713
714 714 for (auto zoneAction : zoneActions) {
715 715
716 716 auto isEnabled = zoneAction->isEnabled(selectedItems);
717 717
718 718 auto menu = &graphMenu;
719 719 QString menuPath;
720 720 for (auto subMenuName : zoneAction->subMenuList()) {
721 721 menuPath += '/';
722 722 menuPath += subMenuName;
723 723
724 724 if (!subMenus.contains(menuPath)) {
725 725 menu = menu->addMenu(subMenuName);
726 726 subMenus[menuPath] = menu;
727 727 subMenusEnabled[menuPath] = isEnabled;
728 728 }
729 729 else {
730 730 menu = subMenus.value(menuPath);
731 731 if (isEnabled) {
732 732 // The sub menu is enabled if at least one of its actions is enabled
733 733 subMenusEnabled[menuPath] = true;
734 734 }
735 735 }
736 736 }
737 737
738 738 FilteringAction *filterAction = nullptr;
739 739 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList())) {
740 740 filterAction = filteredMenu.value(menuPath);
741 741 if (!filterAction) {
742 742 filterAction = new FilteringAction{this};
743 743 filteredMenu[menuPath] = filterAction;
744 744 menu->addAction(filterAction);
745 745 }
746 746 }
747 747
748 748 auto action = menu->addAction(zoneAction->name());
749 749 action->setEnabled(isEnabled);
750 750 action->setShortcut(zoneAction->displayedShortcut());
751 751 QObject::connect(action, &QAction::triggered,
752 752 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
753 753
754 754 if (filterAction && zoneAction->isFilteringAllowed()) {
755 755 filterAction->addActionToFilter(action);
756 756 }
757 757 }
758 758
759 759 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
760 760 it.value()->setEnabled(subMenusEnabled[it.key()]);
761 761 }
762 762 }
763 763
764 764 if (!graphMenu.isEmpty()) {
765 765 graphMenu.exec(QCursor::pos());
766 766 }
767 767 }
768 768
769 769 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
770 770 {
771 771 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
772 772 << QThread::currentThread()->objectName() << "DoAcqui"
773 773 << impl->m_Flags.testFlag(GraphFlag::EnableAcquisition);
774 774
775 775 auto graphRange = DateTimeRange{t1.lower, t1.upper};
776 776 auto oldGraphRange = DateTimeRange{t2.lower, t2.upper};
777 777
778 778 if (impl->m_Flags.testFlag(GraphFlag::EnableAcquisition)) {
779 779 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
780 780
781 781 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
782 782 end = impl->m_VariableToPlotMultiMap.end();
783 783 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
784 784 variableUnderGraphVector.push_back(it->first);
785 785 }
786 786 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
787 787 !impl->m_IsCalibration);
788 788 }
789 789
790 790 if (impl->m_Flags.testFlag(GraphFlag::EnableSynchronization) && !impl->m_IsCalibration) {
791 791 qCDebug(LOG_VisualizationGraphWidget())
792 792 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
793 793 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
794 794 emit synchronize(graphRange, oldGraphRange);
795 795 }
796 796
797 797 auto pos = mapFromGlobal(QCursor::pos());
798 798 auto axisPos = impl->posToAxisPos(pos, plot());
799 799 if (auto parentZone = parentZoneWidget()) {
800 800 if (impl->pointIsInAxisRect(axisPos, plot())) {
801 801 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
802 802 }
803 803 else {
804 804 parentZone->notifyMouseLeaveGraph(this);
805 805 }
806 806 }
807 807 else {
808 808 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
809 809 }
810 810
811 811 // Quits calibration
812 812 impl->m_IsCalibration = false;
813 813 }
814 814
815 815 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
816 816 {
817 817 impl->m_RenderingDelegate->onMouseDoubleClick(event);
818 818 }
819 819
820 820 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
821 821 {
822 822 // Handles plot rendering when mouse is moving
823 823 impl->m_RenderingDelegate->onMouseMove(event);
824 824
825 825 auto axisPos = impl->posToAxisPos(event->pos(), plot());
826 826
827 827 // Zoom box and zone drawing
828 828 if (impl->m_DrawingZoomRect) {
829 829 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
830 830 }
831 831 else if (impl->m_DrawingZone) {
832 832 impl->m_DrawingZone->setEnd(axisPos.x());
833 833 }
834 834
835 835 // Cursor
836 836 if (auto parentZone = parentZoneWidget()) {
837 837 if (impl->pointIsInAxisRect(axisPos, plot())) {
838 838 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
839 839 }
840 840 else {
841 841 parentZone->notifyMouseLeaveGraph(this);
842 842 }
843 843 }
844 844 else {
845 845 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
846 846 }
847 847
848 848 // Search for the selection zone under the mouse
849 849 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
850 850 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
851 851 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
852 852
853 853 // Sets the appropriate cursor shape
854 854 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
855 855 setCursor(cursorShape);
856 856
857 857 // Manages the hovered zone
858 858 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
859 859 if (impl->m_HoveredZone) {
860 860 impl->m_HoveredZone->setHovered(false);
861 861 }
862 862 selectionZoneItemUnderCursor->setHovered(true);
863 863 impl->m_HoveredZone = selectionZoneItemUnderCursor;
864 864 plot().replot(QCustomPlot::rpQueuedReplot);
865 865 }
866 866 }
867 867 else {
868 868 // There is no zone under the mouse or the interaction mode is not "selection zones"
869 869 if (impl->m_HoveredZone) {
870 870 impl->m_HoveredZone->setHovered(false);
871 871 impl->m_HoveredZone = nullptr;
872 872 }
873 873
874 874 setCursor(Qt::ArrowCursor);
875 875 }
876 876
877 877 impl->m_HasMovedMouse = true;
878 878 VisualizationDragWidget::mouseMoveEvent(event);
879 879 }
880 880
881 881 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
882 882 {
883 883 // Processes event only if the wheel occurs on axis rect
884 884 if (!dynamic_cast<QCPAxisRect *>(ui->widget->layoutElementAt(event->posF()))) {
885 885 return;
886 886 }
887 887
888 888 auto value = event->angleDelta().x() + event->angleDelta().y();
889 889 if (value != 0) {
890 890
891 891 auto direction = value > 0 ? 1.0 : -1.0;
892 892 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
893 893 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
894 894 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
895 895
896 896 auto zoomOrientations = QFlags<Qt::Orientation>{};
897 897 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
898 898 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
899 899
900 900 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
901 901
902 902 if (!isZoomX && !isZoomY) {
903 903 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
904 904 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
905 905
906 906 axis->setRange(axis->range() + diff);
907 907
908 908 if (plot().noAntialiasingOnDrag()) {
909 909 plot().setNotAntialiasedElements(QCP::aeAll);
910 910 }
911 911
912 912 plot().replot(QCustomPlot::rpQueuedReplot);
913 913 }
914 914 }
915 915 }
916 916
917 917 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
918 918 {
919 919 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
920 920 auto isSelectionZoneMode
921 921 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
922 922 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
923 923
924 924 if (!isDragDropClick && isLeftClick) {
925 925 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
926 926 // Starts a zoom box
927 927 impl->startDrawingRect(event->pos(), plot());
928 928 }
929 929 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
930 930 // Starts a new selection zone
931 931 auto zoneAtPos = impl->selectionZoneAt(event->pos(), plot());
932 932 if (!zoneAtPos) {
933 933 impl->startDrawingZone(event->pos(), this);
934 934 }
935 935 }
936 936 }
937 937
938 938 // Allows mouse panning only in default mode
939 939 plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode()
940 940 == SqpApplication::PlotsInteractionMode::None
941 941 && !isDragDropClick);
942 942
943 943 // Allows zone edition only in selection zone mode without drag&drop
944 944 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
945 945
946 946 // Selection / Deselection
947 947 if (isSelectionZoneMode) {
948 948 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
949 949 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
950 950
951 951
952 952 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
953 953 && !isMultiSelectionClick) {
954 954 parentVisualizationWidget()->selectionZoneManager().select(
955 955 {selectionZoneItemUnderCursor});
956 956 }
957 957 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
958 958 parentVisualizationWidget()->selectionZoneManager().clearSelection();
959 959 }
960 960 else {
961 961 // No selection change
962 962 }
963 963
964 964 if (selectionZoneItemUnderCursor && isLeftClick) {
965 965 selectionZoneItemUnderCursor->setAssociatedEditedZones(
966 966 parentVisualizationWidget()->selectionZoneManager().selectedItems());
967 967 }
968 968 }
969 969
970 970
971 971 impl->m_HasMovedMouse = false;
972 972 VisualizationDragWidget::mousePressEvent(event);
973 973 }
974 974
975 975 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
976 976 {
977 977 if (impl->m_DrawingZoomRect) {
978 978
979 979 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
980 980 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
981 981
982 982 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
983 983 impl->m_DrawingZoomRect->bottomRight->coords().x()};
984 984
985 985 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
986 986 impl->m_DrawingZoomRect->bottomRight->coords().y()};
987 987
988 988 impl->removeDrawingRect(plot());
989 989
990 990 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
991 991 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
992 992 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
993 993 axisX->setRange(newAxisXRange);
994 994 axisY->setRange(newAxisYRange);
995 995
996 996 plot().replot(QCustomPlot::rpQueuedReplot);
997 997 }
998 998 }
999 999
1000 1000 impl->endDrawingZone(this);
1001 1001
1002 1002 // Selection / Deselection
1003 1003 auto isSelectionZoneMode
1004 1004 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1005 1005 if (isSelectionZoneMode) {
1006 1006 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1007 1007 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
1008 1008 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1009 1009 && !impl->m_HasMovedMouse) {
1010 1010
1011 1011 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1012 1012 if (zonesUnderCursor.count() > 1) {
1013 1013 // There are multiple zones under the mouse.
1014 1014 // Performs the selection with a selection dialog.
1015 1015 VisualizationMultiZoneSelectionDialog dialog{this};
1016 1016 dialog.setZones(zonesUnderCursor);
1017 1017 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1018 1018 dialog.activateWindow();
1019 1019 dialog.raise();
1020 1020 if (dialog.exec() == QDialog::Accepted) {
1021 1021 auto selection = dialog.selectedZones();
1022 1022
1023 1023 if (!isMultiSelectionClick) {
1024 1024 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1025 1025 }
1026 1026
1027 1027 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
1028 1028 auto zone = it.key();
1029 1029 auto isSelected = it.value();
1030 1030 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
1031 1031 isSelected);
1032 1032
1033 1033 if (isSelected) {
1034 1034 // Puts the zone on top of the stack so it can be moved or resized
1035 1035 impl->moveSelectionZoneOnTop(zone, plot());
1036 1036 }
1037 1037 }
1038 1038 }
1039 1039 }
1040 1040 else {
1041 1041 if (!isMultiSelectionClick) {
1042 1042 parentVisualizationWidget()->selectionZoneManager().select(
1043 1043 {selectionZoneItemUnderCursor});
1044 1044 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1045 1045 }
1046 1046 else {
1047 1047 parentVisualizationWidget()->selectionZoneManager().setSelected(
1048 1048 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
1049 1049 || event->button() == Qt::RightButton);
1050 1050 }
1051 1051 }
1052 1052 }
1053 1053 else {
1054 1054 // No selection change
1055 1055 }
1056 1056 }
1057 1057 }
1058 1058
1059 1059 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1060 1060 {
1061 1061 auto graphRange = ui->widget->xAxis->range();
1062 1062 auto dateTime = DateTimeRange{graphRange.lower, graphRange.upper};
1063 1063
1064 1064 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
1065 1065 auto variable = variableEntry.first;
1066 1066 qCDebug(LOG_VisualizationGraphWidget())
1067 1067 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1068 1068 qCDebug(LOG_VisualizationGraphWidget())
1069 1069 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1070 1070 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
1071 1071 impl->updateData(variableEntry.second, variable, variable->range());
1072 1072 }
1073 1073 }
1074 1074 }
1075 1075
1076 1076 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
1077 1077 const DateTimeRange &range)
1078 1078 {
1079 1079 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1080 1080 if (it != impl->m_VariableToPlotMultiMap.end()) {
1081 1081 impl->updateData(it->second, variable, range);
1082 1082 }
1083 1083 }
@@ -1,268 +1,267
1 1 #include "FuzzingOperations.h"
2 2 #include "FuzzingUtils.h"
3 3
4 4 #include <Data/IDataProvider.h>
5 5
6 6 #include <Variable/Variable.h>
7 #include <Variable/VariableController.h>
7 #include <Variable/VariableController2.h>
8 8
9 9 #include <QUuid>
10 10
11 11 #include <functional>
12 12
13 13 Q_LOGGING_CATEGORY(LOG_FuzzingOperations, "FuzzingOperations")
14 14
15 15 namespace {
16 16
17 17 struct CreateOperation : public IFuzzingOperation {
18 18 bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override
19 19 {
20 20 // A variable can be created only if it doesn't exist yet
21 21 return fuzzingState.variableState(variableId).m_Variable == nullptr;
22 22 }
23 23
24 24 void execute(VariableId variableId, FuzzingState &fuzzingState,
25 VariableController &variableController,
25 VariableController2 &variableController,
26 26 const Properties &properties) const override
27 27 {
28 28 // Retrieves metadata pool from properties, and choose one of the metadata entries to
29 29 // associate it with the variable
30 30 auto metaDataPool = properties.value(METADATA_POOL_PROPERTY).value<MetadataPool>();
31 31 auto variableMetadata = RandomGenerator::instance().randomChoice(metaDataPool);
32 32
33 33 // Retrieves provider
34 34 auto variableProvider
35 35 = properties.value(PROVIDER_PROPERTY).value<std::shared_ptr<IDataProvider> >();
36 36
37 37 auto variableName = QString{"Var_%1"}.arg(QUuid::createUuid().toString());
38 38 qCInfo(LOG_FuzzingOperations()).noquote() << "Creating variable" << variableName
39 39 << "(metadata:" << variableMetadata << ")...";
40 40
41 41 auto newVariable
42 42 = variableController.createVariable(variableName, variableMetadata, variableProvider, properties.value(INITIAL_RANGE_PROPERTY).value<DateTimeRange>());
43 43
44 44 // Updates variable's state
45 45 auto &variableState = fuzzingState.variableState(variableId);
46 46 variableState.m_Range = properties.value(INITIAL_RANGE_PROPERTY).value<DateTimeRange>();
47 47 std::swap(variableState.m_Variable, newVariable);
48 48 }
49 49 };
50 50
51 51 struct DeleteOperation : public IFuzzingOperation {
52 52 bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override
53 53 {
54 54 // A variable can be delete only if it exists
55 55 return fuzzingState.variableState(variableId).m_Variable != nullptr;
56 56 }
57 57
58 58 void execute(VariableId variableId, FuzzingState &fuzzingState,
59 VariableController &variableController, const Properties &) const override
59 VariableController2 &variableController, const Properties &) const override
60 60 {
61 61 auto &variableState = fuzzingState.variableState(variableId);
62 62
63 63 qCInfo(LOG_FuzzingOperations()).noquote() << "Deleting variable"
64 64 << variableState.m_Variable->name() << "...";
65 65 variableController.deleteVariable(variableState.m_Variable);
66 66
67 67 // Updates variable's state
68 68 variableState.m_Range = INVALID_RANGE;
69 69 variableState.m_Variable = nullptr;
70 70
71 71 // Desynchronizes the variable if it was in a sync group
72 72 auto syncGroupId = fuzzingState.syncGroupId(variableId);
73 73 fuzzingState.desynchronizeVariable(variableId, syncGroupId);
74 74 }
75 75 };
76 76
77 77 /**
78 78 * Defines a move operation through a range.
79 79 *
80 80 * A move operation is determined by three functions:
81 81 * - Two 'move' functions, used to indicate in which direction the beginning and the end of a range
82 82 * are going during the operation. These functions will be:
83 83 * -- {<- / <-} for pan left
84 84 * -- {-> / ->} for pan right
85 85 * -- {-> / <-} for zoom in
86 86 * -- {<- / ->} for zoom out
87 87 * - One 'max move' functions, used to compute the max delta at which the operation can move a
88 88 * range, according to a max range. For exemple, for a range of {1, 5} and a max range of {0, 10},
89 89 * max deltas will be:
90 90 * -- {0, 4} for pan left
91 91 * -- {6, 10} for pan right
92 92 * -- {3, 3} for zoom in
93 93 * -- {0, 6} for zoom out (same spacing left and right)
94 94 */
95 95 struct MoveOperation : public IFuzzingOperation {
96 96 using MoveFunction = std::function<double(double currentValue, double maxValue)>;
97 97 using MaxMoveFunction = std::function<double(const DateTimeRange &range, const DateTimeRange &maxRange)>;
98 98
99 99 explicit MoveOperation(MoveFunction rangeStartMoveFun, MoveFunction rangeEndMoveFun,
100 100 MaxMoveFunction maxMoveFun,
101 101 const QString &label = QStringLiteral("Move operation"))
102 102 : m_RangeStartMoveFun{std::move(rangeStartMoveFun)},
103 103 m_RangeEndMoveFun{std::move(rangeEndMoveFun)},
104 104 m_MaxMoveFun{std::move(maxMoveFun)},
105 105 m_Label{label}
106 106 {
107 107 }
108 108
109 109 bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override
110 110 {
111 111 return fuzzingState.variableState(variableId).m_Variable != nullptr;
112 112 }
113 113
114 114 void execute(VariableId variableId, FuzzingState &fuzzingState,
115 VariableController &variableController,
115 VariableController2 &variableController,
116 116 const Properties &properties) const override
117 117 {
118 118 auto &variableState = fuzzingState.variableState(variableId);
119 119 auto variable = variableState.m_Variable;
120 120
121 121 // Gets the max range defined
122 122 auto maxRange = properties.value(MAX_RANGE_PROPERTY, QVariant::fromValue(INVALID_RANGE))
123 123 .value<DateTimeRange>();
124 124 auto variableRange = variableState.m_Range;
125 125
126 126 if (maxRange == INVALID_RANGE || variableRange.m_TStart < maxRange.m_TStart
127 127 || variableRange.m_TEnd > maxRange.m_TEnd) {
128 128 qCWarning(LOG_FuzzingOperations()) << "Can't execute operation: invalid max range";
129 129 return;
130 130 }
131 131
132 132 // Computes the max delta at which the variable can move, up to the limits of the max range
133 133 auto deltaMax = m_MaxMoveFun(variableRange, maxRange);
134 134
135 135 // Generates random delta that will be used to move variable
136 136 auto delta = RandomGenerator::instance().generateDouble(0, deltaMax);
137 137
138 138 // Moves variable to its new range
139 139 auto isSynchronized = !fuzzingState.syncGroupId(variableId).isNull();
140 140 auto newVariableRange = DateTimeRange{m_RangeStartMoveFun(variableRange.m_TStart, delta),
141 141 m_RangeEndMoveFun(variableRange.m_TEnd, delta)};
142 142 qCInfo(LOG_FuzzingOperations()).noquote() << "Performing" << m_Label << "on"
143 143 << variable->name() << "(from" << variableRange
144 144 << "to" << newVariableRange << ")...";
145 variableController.onRequestDataLoading({variable}, newVariableRange, isSynchronized);
145 variableController.changeRange({variable}, newVariableRange);
146 146
147 147 // Updates state
148 148 fuzzingState.updateRanges(variableId, newVariableRange);
149 149 }
150 150
151 151 MoveFunction m_RangeStartMoveFun;
152 152 MoveFunction m_RangeEndMoveFun;
153 153 MaxMoveFunction m_MaxMoveFun;
154 154 QString m_Label;
155 155 };
156 156
157 157 struct SynchronizeOperation : public IFuzzingOperation {
158 158 bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override
159 159 {
160 160 auto variable = fuzzingState.variableState(variableId).m_Variable;
161 161 return variable != nullptr && !fuzzingState.m_SyncGroupsPool.empty()
162 162 && fuzzingState.syncGroupId(variableId).isNull();
163 163 }
164 164
165 165 void execute(VariableId variableId, FuzzingState &fuzzingState,
166 VariableController &variableController, const Properties &) const override
166 VariableController2 &variableController, const Properties &) const override
167 167 {
168 168 auto &variableState = fuzzingState.variableState(variableId);
169 169
170 170 // Chooses a random synchronization group and adds the variable into sync group
171 171 auto syncGroupId = RandomGenerator::instance().randomChoice(fuzzingState.syncGroupsIds());
172 172 qCInfo(LOG_FuzzingOperations()).noquote() << "Adding" << variableState.m_Variable->name()
173 173 << "into synchronization group" << syncGroupId
174 174 << "...";
175 variableController.onAddSynchronized(variableState.m_Variable, syncGroupId);
175 //variableController.onAddSynchronized(variableState.m_Variable, syncGroupId);
176 176
177 177 // Updates state
178 178 fuzzingState.synchronizeVariable(variableId, syncGroupId);
179 179
180 variableController.onRequestDataLoading({variableState.m_Variable}, variableState.m_Range,
181 false);
180 variableController.changeRange({variableState.m_Variable}, variableState.m_Range);
182 181 }
183 182 };
184 183
185 184 struct DesynchronizeOperation : public IFuzzingOperation {
186 185 bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const override
187 186 {
188 187 auto variable = fuzzingState.variableState(variableId).m_Variable;
189 188 return variable != nullptr && !fuzzingState.syncGroupId(variableId).isNull();
190 189 }
191 190
192 191 void execute(VariableId variableId, FuzzingState &fuzzingState,
193 VariableController &variableController, const Properties &) const override
192 VariableController2 &variableController, const Properties &) const override
194 193 {
195 194 auto &variableState = fuzzingState.variableState(variableId);
196 195
197 196 // Gets the sync group of the variable
198 197 auto syncGroupId = fuzzingState.syncGroupId(variableId);
199 198
200 199 qCInfo(LOG_FuzzingOperations()).noquote() << "Removing" << variableState.m_Variable->name()
201 200 << "from synchronization group" << syncGroupId
202 201 << "...";
203 variableController.desynchronize(variableState.m_Variable, syncGroupId);
202 //variableController.desynchronize(variableState.m_Variable, syncGroupId);
204 203
205 204 // Updates state
206 205 fuzzingState.desynchronizeVariable(variableId, syncGroupId);
207 206 }
208 207 };
209 208
210 209 struct UnknownOperation : public IFuzzingOperation {
211 210 bool canExecute(VariableId, const FuzzingState &) const override { return false; }
212 211
213 void execute(VariableId, FuzzingState &, VariableController &,
212 void execute(VariableId, FuzzingState &, VariableController2 &,
214 213 const Properties &) const override
215 214 {
216 215 }
217 216 };
218 217
219 218 } // namespace
220 219
221 220 std::unique_ptr<IFuzzingOperation> FuzzingOperationFactory::create(FuzzingOperationType type)
222 221 {
223 222 switch (type) {
224 223 case FuzzingOperationType::CREATE:
225 224 return std::make_unique<CreateOperation>();
226 225 case FuzzingOperationType::DELETE:
227 226 return std::make_unique<DeleteOperation>();
228 227 case FuzzingOperationType::PAN_LEFT:
229 228 return std::make_unique<MoveOperation>(
230 229 std::minus<double>(), std::minus<double>(),
231 230 [](const DateTimeRange &range, const DateTimeRange &maxRange) {
232 231 return range.m_TStart - maxRange.m_TStart;
233 232 },
234 233 QStringLiteral("Pan left operation"));
235 234 case FuzzingOperationType::PAN_RIGHT:
236 235 return std::make_unique<MoveOperation>(
237 236 std::plus<double>(), std::plus<double>(),
238 237 [](const DateTimeRange &range, const DateTimeRange &maxRange) {
239 238 return maxRange.m_TEnd - range.m_TEnd;
240 239 },
241 240 QStringLiteral("Pan right operation"));
242 241 case FuzzingOperationType::ZOOM_IN:
243 242 return std::make_unique<MoveOperation>(
244 243 std::plus<double>(), std::minus<double>(),
245 244 [](const DateTimeRange &range, const DateTimeRange &maxRange) {
246 245 Q_UNUSED(maxRange)
247 246 return range.m_TEnd - (range.m_TStart + range.m_TEnd) / 2.;
248 247 },
249 248 QStringLiteral("Zoom in operation"));
250 249 case FuzzingOperationType::ZOOM_OUT:
251 250 return std::make_unique<MoveOperation>(
252 251 std::minus<double>(), std::plus<double>(),
253 252 [](const DateTimeRange &range, const DateTimeRange &maxRange) {
254 253 return std::min(range.m_TStart - maxRange.m_TStart,
255 254 maxRange.m_TEnd - range.m_TEnd);
256 255 },
257 256 QStringLiteral("Zoom out operation"));
258 257 case FuzzingOperationType::SYNCHRONIZE:
259 258 return std::make_unique<SynchronizeOperation>();
260 259 case FuzzingOperationType::DESYNCHRONIZE:
261 260 return std::make_unique<DesynchronizeOperation>();
262 261 default:
263 262 // Default case returns unknown operation
264 263 break;
265 264 }
266 265
267 266 return std::make_unique<UnknownOperation>();
268 267 }
@@ -1,56 +1,57
1 1 #ifndef SCIQLOP_FUZZINGOPERATIONS_H
2 2 #define SCIQLOP_FUZZINGOPERATIONS_H
3 3
4 4 #include "FuzzingDefs.h"
5 5
6 6 #include <memory>
7 7 #include <set>
8 8
9 9 #include <QLoggingCategory>
10 10 #include <QMetaType>
11 #include <Variable/VariableController2.h>
11 12
12 13 Q_DECLARE_LOGGING_CATEGORY(LOG_FuzzingOperations)
13 14
14 15 class VariableController;
15 16
16 17 /**
17 18 * Enumeration of types of existing fuzzing operations
18 19 */
19 20 enum class FuzzingOperationType {
20 21 CREATE,
21 22 DELETE,
22 23 PAN_LEFT,
23 24 PAN_RIGHT,
24 25 ZOOM_IN,
25 26 ZOOM_OUT,
26 27 SYNCHRONIZE,
27 28 DESYNCHRONIZE
28 29 };
29 30
30 31 /// Interface that represents an operation that can be executed during a fuzzing test
31 32 struct IFuzzingOperation {
32 33 virtual ~IFuzzingOperation() noexcept = default;
33 34
34 35 /// Checks if the operation can be executed according to the current test's state for the
35 36 /// variable passed in parameter
36 37 virtual bool canExecute(VariableId variableId, const FuzzingState &fuzzingState) const = 0;
37 38 /// Executes the operation on the variable for which its identifier is passed in parameter
38 39 /// @param variableId the variable identifier
39 40 /// @param fuzzingState the current test's state on which to find the variable and execute the
40 41 /// operation
41 42 /// @param variableController the controller associated to the operation
42 43 /// @param properties properties that can be used to configure the operation
43 44 /// @remarks fuzzingState is passed as a reference because, according to the operation, it can
44 45 /// be modified (in/out parameter)
45 46 virtual void execute(VariableId variableId, FuzzingState &fuzzingState,
46 VariableController &variableController,
47 VariableController2 &variableController,
47 48 const Properties &properties = {}) const = 0;
48 49 };
49 50
50 51 /// Factory of @sa IFuzzingOperation
51 52 struct FuzzingOperationFactory {
52 53 /// Creates a fuzzing operation from a type
53 54 static std::unique_ptr<IFuzzingOperation> create(FuzzingOperationType type);
54 55 };
55 56
56 57 #endif // SCIQLOP_FUZZINGOPERATIONS_H
General Comments 0
You need to be logged in to leave comments. Login now