##// END OF EJS Templates
Merge branch 'feature/CatalogueGuiPart5' into develop
trabillard -
r1349:aa6b3e9d2907 merge
parent child
Show More
@@ -0,0 +1,26
1 #ifndef SCIQLOP_DATASERIESTYPE_H
2 #define SCIQLOP_DATASERIESTYPE_H
3
4 #include <QString>
5
6 enum class DataSeriesType { SCALAR, SPECTROGRAM, VECTOR, UNKNOWN };
7
8 struct DataSeriesTypeUtils {
9 static DataSeriesType fromString(const QString &type)
10 {
11 if (type == QStringLiteral("scalar")) {
12 return DataSeriesType::SCALAR;
13 }
14 else if (type == QStringLiteral("spectrogram")) {
15 return DataSeriesType::SPECTROGRAM;
16 }
17 else if (type == QStringLiteral("vector")) {
18 return DataSeriesType::VECTOR;
19 }
20 else {
21 return DataSeriesType::UNKNOWN;
22 }
23 }
24 };
25
26 #endif // SCIQLOP_DATASERIESTYPE_H
@@ -1,405 +1,405
1 1 /*------------------------------------------------------------------------------
2 2 -- This file is a part of the SciQLop Software
3 3 -- Copyright (C) 2017, Plasma Physics Laboratory - CNRS
4 4 --
5 5 -- This program is free software; you can redistribute it and/or modify
6 6 -- it under the terms of the GNU General Public License as published by
7 7 -- the Free Software Foundation; either version 2 of the License, or
8 8 -- (at your option) any later version.
9 9 --
10 10 -- This program is distributed in the hope that it will be useful,
11 11 -- but WITHOUT ANY WARRANTY; without even the implied warranty of
12 12 -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 13 -- GNU General Public License for more details.
14 14 --
15 15 -- You should have received a copy of the GNU General Public License
16 16 -- along with this program; if not, write to the Free Software
17 17 -- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 18 -------------------------------------------------------------------------------*/
19 19 /*-- Author : Alexis Jeandet
20 20 -- Mail : alexis.jeandet@member.fsf.org
21 21 ----------------------------------------------------------------------------*/
22 22 #include "MainWindow.h"
23 23 #include "ui_MainWindow.h"
24 24
25 25 #include <Catalogue/CatalogueController.h>
26 26 #include <Catalogue/CatalogueExplorer.h>
27 27 #include <DataSource/DataSourceController.h>
28 28 #include <DataSource/DataSourceWidget.h>
29 29 #include <Settings/SqpSettingsDialog.h>
30 30 #include <Settings/SqpSettingsGeneralWidget.h>
31 31 #include <SidePane/SqpSidePane.h>
32 32 #include <SqpApplication.h>
33 33 #include <Time/TimeController.h>
34 34 #include <TimeWidget/TimeWidget.h>
35 35 #include <Variable/Variable.h>
36 36 #include <Variable/VariableController.h>
37 37 #include <Visualization/VisualizationController.h>
38 38
39 39 #include <QAction>
40 40 #include <QCloseEvent>
41 41 #include <QDate>
42 42 #include <QDir>
43 43 #include <QFileDialog>
44 44 #include <QMessageBox>
45 45 #include <QToolBar>
46 46 #include <QToolButton>
47 47 #include <memory.h>
48 48
49 49 #include "iostream"
50 50
51 51 Q_LOGGING_CATEGORY(LOG_MainWindow, "MainWindow")
52 52
53 53 namespace {
54 54 const auto LEFTMAININSPECTORWIDGETSPLITTERINDEX = 0;
55 55 const auto LEFTINSPECTORSIDEPANESPLITTERINDEX = 1;
56 56 const auto VIEWPLITTERINDEX = 2;
57 57 const auto RIGHTINSPECTORSIDEPANESPLITTERINDEX = 3;
58 58 const auto RIGHTMAININSPECTORWIDGETSPLITTERINDEX = 4;
59 59 }
60 60
61 61 class MainWindow::MainWindowPrivate {
62 62 public:
63 63 explicit MainWindowPrivate(MainWindow *mainWindow)
64 64 : m_LastOpenLeftInspectorSize{},
65 65 m_LastOpenRightInspectorSize{},
66 66 m_GeneralSettingsWidget{new SqpSettingsGeneralWidget{mainWindow}},
67 67 m_SettingsDialog{new SqpSettingsDialog{mainWindow}},
68 68 m_CatalogExplorer{new CatalogueExplorer{mainWindow}}
69 69 {
70 70 }
71 71
72 72 QSize m_LastOpenLeftInspectorSize;
73 73 QSize m_LastOpenRightInspectorSize;
74 74 /// General settings widget. MainWindow has the ownership
75 75 SqpSettingsGeneralWidget *m_GeneralSettingsWidget;
76 76 /// Settings dialog. MainWindow has the ownership
77 77 SqpSettingsDialog *m_SettingsDialog;
78 78 /// Catalogue dialog. MainWindow has the ownership
79 79 CatalogueExplorer *m_CatalogExplorer;
80 80
81 81 bool checkDataToSave(QWidget *parentWidget);
82 82 };
83 83
84 84 MainWindow::MainWindow(QWidget *parent)
85 85 : QMainWindow{parent},
86 86 m_Ui{new Ui::MainWindow},
87 87 impl{spimpl::make_unique_impl<MainWindowPrivate>(this)}
88 88 {
89 89 m_Ui->setupUi(this);
90 90
91 91 m_Ui->splitter->setCollapsible(LEFTINSPECTORSIDEPANESPLITTERINDEX, false);
92 92 m_Ui->splitter->setCollapsible(RIGHTINSPECTORSIDEPANESPLITTERINDEX, false);
93 93
94 94 impl->m_CatalogExplorer->setVisualizationWidget(m_Ui->view);
95 95
96 96
97 97 auto leftSidePane = m_Ui->leftInspectorSidePane->sidePane();
98 98 auto openLeftInspectorAction = new QAction{QIcon{
99 99 ":/icones/previous.png",
100 100 },
101 101 tr("Show/hide the left inspector"), this};
102 102
103 103
104 104 auto spacerLeftTop = new QWidget{};
105 105 spacerLeftTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
106 106
107 107 auto spacerLeftBottom = new QWidget{};
108 108 spacerLeftBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
109 109
110 110 leftSidePane->addWidget(spacerLeftTop);
111 111 leftSidePane->addAction(openLeftInspectorAction);
112 112 leftSidePane->addWidget(spacerLeftBottom);
113 113
114 114
115 115 auto rightSidePane = m_Ui->rightInspectorSidePane->sidePane();
116 116 auto openRightInspectorAction = new QAction{QIcon{
117 117 ":/icones/next.png",
118 118 },
119 119 tr("Show/hide the right inspector"), this};
120 120
121 121 auto spacerRightTop = new QWidget{};
122 122 spacerRightTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
123 123
124 124 auto spacerRightBottom = new QWidget{};
125 125 spacerRightBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
126 126
127 127 rightSidePane->addWidget(spacerRightTop);
128 128 rightSidePane->addAction(openRightInspectorAction);
129 129 rightSidePane->addWidget(spacerRightBottom);
130 130
131 131 openLeftInspectorAction->setCheckable(true);
132 132 openRightInspectorAction->setCheckable(true);
133 133
134 134 auto openInspector = [this](bool checked, bool right, auto action) {
135 135
136 136 action->setIcon(QIcon{(checked xor right) ? ":/icones/next.png" : ":/icones/previous.png"});
137 137
138 138 auto &lastInspectorSize
139 139 = right ? impl->m_LastOpenRightInspectorSize : impl->m_LastOpenLeftInspectorSize;
140 140
141 141 auto nextInspectorSize = right ? m_Ui->rightMainInspectorWidget->size()
142 142 : m_Ui->leftMainInspectorWidget->size();
143 143
144 144 // Update of the last opened geometry
145 145 if (checked) {
146 146 lastInspectorSize = nextInspectorSize;
147 147 }
148 148
149 149 auto startSize = lastInspectorSize;
150 150 auto endSize = startSize;
151 151 endSize.setWidth(0);
152 152
153 153 auto splitterInspectorIndex
154 154 = right ? RIGHTMAININSPECTORWIDGETSPLITTERINDEX : LEFTMAININSPECTORWIDGETSPLITTERINDEX;
155 155
156 156 auto currentSizes = m_Ui->splitter->sizes();
157 157 if (checked) {
158 158 // adjust sizes individually here, e.g.
159 159 currentSizes[splitterInspectorIndex] -= lastInspectorSize.width();
160 160 currentSizes[VIEWPLITTERINDEX] += lastInspectorSize.width();
161 161 m_Ui->splitter->setSizes(currentSizes);
162 162 }
163 163 else {
164 164 // adjust sizes individually here, e.g.
165 165 currentSizes[splitterInspectorIndex] += lastInspectorSize.width();
166 166 currentSizes[VIEWPLITTERINDEX] -= lastInspectorSize.width();
167 167 m_Ui->splitter->setSizes(currentSizes);
168 168 }
169 169
170 170 };
171 171
172 172
173 173 connect(openLeftInspectorAction, &QAction::triggered,
174 174 [openInspector, openLeftInspectorAction](bool checked) {
175 175 openInspector(checked, false, openLeftInspectorAction);
176 176 });
177 177 connect(openRightInspectorAction, &QAction::triggered,
178 178 [openInspector, openRightInspectorAction](bool checked) {
179 179 openInspector(checked, true, openRightInspectorAction);
180 180 });
181 181
182 182 // //////////////// //
183 183 // Menu and Toolbar //
184 184 // //////////////// //
185 185 this->menuBar()->addAction(tr("File"));
186 186 auto toolsMenu = this->menuBar()->addMenu(tr("Tools"));
187 187 toolsMenu->addAction(tr("Settings..."), [this]() {
188 188 // Loads settings
189 189 impl->m_SettingsDialog->loadSettings();
190 190
191 191 // Open settings dialog and save settings if the dialog is accepted
192 192 if (impl->m_SettingsDialog->exec() == QDialog::Accepted) {
193 193 impl->m_SettingsDialog->saveSettings();
194 194 }
195 195
196 196 });
197 197
198 198 auto mainToolBar = this->addToolBar(QStringLiteral("MainToolBar"));
199 199
200 200 auto timeWidget = new TimeWidget{};
201 201 mainToolBar->addWidget(timeWidget);
202 202
203 203 // Interaction modes
204 204 auto actionPointerMode = new QAction{QIcon(":/icones/pointer.png"), "Move", this};
205 205 actionPointerMode->setCheckable(true);
206 206 actionPointerMode->setChecked(sqpApp->plotsInteractionMode()
207 207 == SqpApplication::PlotsInteractionMode::None);
208 208 connect(actionPointerMode, &QAction::triggered,
209 209 []() { sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::None); });
210 210
211 211 auto actionZoomMode = new QAction{QIcon(":/icones/zoom.png"), "Zoom", this};
212 212 actionZoomMode->setCheckable(true);
213 213 actionZoomMode->setChecked(sqpApp->plotsInteractionMode()
214 214 == SqpApplication::PlotsInteractionMode::ZoomBox);
215 215 connect(actionZoomMode, &QAction::triggered, []() {
216 216 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::ZoomBox);
217 217 });
218 218
219 219 auto actionOrganisationMode = new QAction{QIcon(":/icones/drag.png"), "Organize", this};
220 220 actionOrganisationMode->setCheckable(true);
221 221 actionOrganisationMode->setChecked(sqpApp->plotsInteractionMode()
222 222 == SqpApplication::PlotsInteractionMode::DragAndDrop);
223 223 connect(actionOrganisationMode, &QAction::triggered, []() {
224 224 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::DragAndDrop);
225 225 });
226 226
227 227 auto actionZonesMode = new QAction{QIcon(":/icones/rectangle.png"), "Zones", this};
228 228 actionZonesMode->setCheckable(true);
229 229 actionZonesMode->setChecked(sqpApp->plotsInteractionMode()
230 230 == SqpApplication::PlotsInteractionMode::SelectionZones);
231 231 connect(actionZonesMode, &QAction::triggered, []() {
232 232 sqpApp->setPlotsInteractionMode(SqpApplication::PlotsInteractionMode::SelectionZones);
233 233 });
234 234
235 235 auto modeActionGroup = new QActionGroup{this};
236 236 modeActionGroup->addAction(actionZoomMode);
237 237 modeActionGroup->addAction(actionZonesMode);
238 238 modeActionGroup->addAction(actionOrganisationMode);
239 239 modeActionGroup->addAction(actionPointerMode);
240 240 modeActionGroup->setExclusive(true);
241 241
242 242 mainToolBar->addSeparator();
243 243 mainToolBar->addAction(actionPointerMode);
244 244 mainToolBar->addAction(actionZoomMode);
245 245 mainToolBar->addAction(actionOrganisationMode);
246 246 mainToolBar->addAction(actionZonesMode);
247 247 mainToolBar->addSeparator();
248 248
249 249 // Cursors
250 250 auto btnCursor = new QToolButton{this};
251 251 btnCursor->setIcon(QIcon(":/icones/cursor.png"));
252 252 btnCursor->setText("Cursor");
253 253 btnCursor->setToolTip("Cursor");
254 254 btnCursor->setPopupMode(QToolButton::InstantPopup);
255 255 auto cursorMenu = new QMenu("CursorMenu", this);
256 256 btnCursor->setMenu(cursorMenu);
257 257
258 258 auto noCursorAction = cursorMenu->addAction("No Cursor");
259 259 noCursorAction->setCheckable(true);
260 260 noCursorAction->setChecked(sqpApp->plotsCursorMode()
261 261 == SqpApplication::PlotsCursorMode::NoCursor);
262 262 connect(noCursorAction, &QAction::triggered,
263 263 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::NoCursor); });
264 264
265 265 cursorMenu->addSeparator();
266 266 auto verticalCursorAction = cursorMenu->addAction("Vertical Cursor");
267 267 verticalCursorAction->setCheckable(true);
268 268 verticalCursorAction->setChecked(sqpApp->plotsCursorMode()
269 269 == SqpApplication::PlotsCursorMode::Vertical);
270 270 connect(verticalCursorAction, &QAction::triggered,
271 271 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Vertical); });
272 272
273 273 auto temporalCursorAction = cursorMenu->addAction("Temporal Cursor");
274 274 temporalCursorAction->setCheckable(true);
275 275 temporalCursorAction->setChecked(sqpApp->plotsCursorMode()
276 276 == SqpApplication::PlotsCursorMode::Temporal);
277 277 connect(temporalCursorAction, &QAction::triggered,
278 278 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Temporal); });
279 279
280 280 auto horizontalCursorAction = cursorMenu->addAction("Horizontal Cursor");
281 281 horizontalCursorAction->setCheckable(true);
282 282 horizontalCursorAction->setChecked(sqpApp->plotsCursorMode()
283 283 == SqpApplication::PlotsCursorMode::Horizontal);
284 284 connect(horizontalCursorAction, &QAction::triggered,
285 285 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Horizontal); });
286 286
287 287 auto crossCursorAction = cursorMenu->addAction("Cross Cursor");
288 288 crossCursorAction->setCheckable(true);
289 289 crossCursorAction->setChecked(sqpApp->plotsCursorMode()
290 290 == SqpApplication::PlotsCursorMode::Cross);
291 291 connect(crossCursorAction, &QAction::triggered,
292 292 []() { sqpApp->setPlotsCursorMode(SqpApplication::PlotsCursorMode::Cross); });
293 293
294 294 mainToolBar->addWidget(btnCursor);
295 295
296 296 auto cursorModeActionGroup = new QActionGroup{this};
297 297 cursorModeActionGroup->setExclusive(true);
298 298 cursorModeActionGroup->addAction(noCursorAction);
299 299 cursorModeActionGroup->addAction(verticalCursorAction);
300 300 cursorModeActionGroup->addAction(temporalCursorAction);
301 301 cursorModeActionGroup->addAction(horizontalCursorAction);
302 302 cursorModeActionGroup->addAction(crossCursorAction);
303 303
304 304 // Catalog
305 305 mainToolBar->addSeparator();
306 306 mainToolBar->addAction(QIcon(":/icones/catalogue.png"), "Catalogues",
307 307 [this]() { impl->m_CatalogExplorer->show(); });
308 308
309 309 // //////// //
310 310 // Settings //
311 311 // //////// //
312 312
313 313 // Registers "general settings" widget to the settings dialog
314 314 impl->m_SettingsDialog->registerWidget(QStringLiteral("General"),
315 315 impl->m_GeneralSettingsWidget);
316 316
317 317 // /////////// //
318 318 // Connections //
319 319 // /////////// //
320 320
321 321 // Controllers / controllers connections
322 322 connect(&sqpApp->timeController(), SIGNAL(timeUpdated(SqpRange)), &sqpApp->variableController(),
323 323 SLOT(onDateTimeOnSelection(SqpRange)));
324 324
325 325 // Widgets / controllers connections
326 326
327 327 // DataSource
328 328 connect(&sqpApp->dataSourceController(), SIGNAL(dataSourceItemSet(DataSourceItem *)),
329 329 m_Ui->dataSourceWidget, SLOT(addDataSource(DataSourceItem *)));
330 330
331 331 // Time
332 332 connect(timeWidget, SIGNAL(timeUpdated(SqpRange)), &sqpApp->timeController(),
333 333 SLOT(onTimeToUpdate(SqpRange)));
334 334
335 335 // Visualization
336 336 connect(&sqpApp->visualizationController(),
337 337 SIGNAL(variableAboutToBeDeleted(std::shared_ptr<Variable>)), m_Ui->view,
338 338 SLOT(onVariableAboutToBeDeleted(std::shared_ptr<Variable>)));
339 339
340 340 connect(&sqpApp->visualizationController(),
341 341 SIGNAL(rangeChanged(std::shared_ptr<Variable>, const SqpRange &)), m_Ui->view,
342 342 SLOT(onRangeChanged(std::shared_ptr<Variable>, const SqpRange &)));
343 343
344 344 // Widgets / widgets connections
345 345
346 346 // For the following connections, we use DirectConnection to allow each widget that can
347 347 // potentially attach a menu to the variable's menu to do so before this menu is displayed.
348 348 // The order of connections is also important, since it determines the order in which each
349 349 // widget will attach its menu
350 350 connect(
351 351 m_Ui->variableInspectorWidget,
352 352 SIGNAL(tableMenuAboutToBeDisplayed(QMenu *, const QVector<std::shared_ptr<Variable> > &)),
353 353 m_Ui->view, SLOT(attachVariableMenu(QMenu *, const QVector<std::shared_ptr<Variable> > &)),
354 354 Qt::DirectConnection);
355 355 }
356 356
357 357 MainWindow::~MainWindow()
358 358 {
359 359 }
360 360
361 361 void MainWindow::changeEvent(QEvent *e)
362 362 {
363 363 QMainWindow::changeEvent(e);
364 364 switch (e->type()) {
365 365 case QEvent::LanguageChange:
366 366 m_Ui->retranslateUi(this);
367 367 break;
368 368 default:
369 369 break;
370 370 }
371 371 }
372 372
373 373 void MainWindow::closeEvent(QCloseEvent *event)
374 374 {
375 375 if (!impl->checkDataToSave(this)) {
376 376 event->ignore();
377 377 }
378 378 else {
379 379 event->accept();
380 380 }
381 381 }
382 382
383 383 bool MainWindow::MainWindowPrivate::checkDataToSave(QWidget *parentWidget)
384 384 {
385 385 auto hasChanges = sqpApp->catalogueController().hasChanges();
386 386 if (hasChanges) {
387 387 // There are some unsaved changes
388 388 switch (QMessageBox::question(
389 parentWidget, "Save changes",
390 tr("The catalogue controller unsaved changes.\nDo you want to save them ?"),
389 parentWidget, tr("Save changes"),
390 tr("The catalogue controller has unsaved changes.\nDo you want to save them ?"),
391 391 QMessageBox::SaveAll | QMessageBox::Discard | QMessageBox::Cancel,
392 392 QMessageBox::SaveAll)) {
393 393 case QMessageBox::SaveAll:
394 394 sqpApp->catalogueController().saveAll();
395 395 break;
396 396 case QMessageBox::Discard:
397 397 break;
398 398 case QMessageBox::Cancel:
399 399 default:
400 400 return false;
401 401 }
402 402 }
403 403
404 404 return true;
405 405 }
@@ -1,102 +1,106
1 1 #ifndef SCIQLOP_DATASOURCECONTROLLER_H
2 2 #define SCIQLOP_DATASOURCECONTROLLER_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <QLoggingCategory>
7 7 #include <QObject>
8 8 #include <QUuid>
9 9
10 10 #include <Common/spimpl.h>
11 11
12 12 Q_DECLARE_LOGGING_CATEGORY(LOG_DataSourceController)
13 13
14 14 class DataSourceItem;
15 15 class IDataProvider;
16 16
17 17 /**
18 18 * @brief The DataSourceController class aims to make the link between SciQlop and its plugins. This
19 19 * is the intermediate class that SciQlop has to use in the way to connect a data source. Please
20 20 * first use register method to initialize a plugin specified by its metadata name (JSON plugin
21 21 * source) then others specifics method will be able to access it. You can load a data source driver
22 22 * plugin then create a data source.
23 23 */
24 24 class SCIQLOP_CORE_EXPORT DataSourceController : public QObject {
25 25 Q_OBJECT
26 26 public:
27 27 explicit DataSourceController(QObject *parent = 0);
28 28 virtual ~DataSourceController();
29 29
30 30 /**
31 31 * Registers a data source. The method delivers a unique id that can be used afterwards to
32 32 * access to the data source properties (structure, connection parameters, data provider, etc.)
33 33 * @param dataSourceName the name of the data source
34 34 * @return the unique id with which the data source has been registered
35 35 */
36 36 QUuid registerDataSource(const QString &dataSourceName) noexcept;
37 37
38 38 /**
39 39 * Sets the structure of a data source. The controller takes ownership of the structure.
40 40 * @param dataSourceUid the unique id with which the data source has been registered into the
41 41 * controller. If it is invalid, the method has no effect.
42 42 * @param dataSourceItem the structure of the data source. It must be not null to be registered
43 43 * @sa registerDataSource()
44 44 */
45 45 void setDataSourceItem(const QUuid &dataSourceUid,
46 46 std::unique_ptr<DataSourceItem> dataSourceItem) noexcept;
47 47
48 48 /**
49 49 * Sets the data provider used to retrieve data from of a data source. The controller takes
50 50 * ownership of the provider.
51 51 * @param dataSourceUid the unique id with which the data source has been registered into the
52 52 * controller. If it is invalid, the method has no effect.
53 53 * @param dataProvider the provider of the data source
54 54 * @sa registerDataSource()
55 55 */
56 56 void setDataProvider(const QUuid &dataSourceUid,
57 57 std::unique_ptr<IDataProvider> dataProvider) noexcept;
58 58
59 59 /**
60 60 * Loads an item (product) as a variable in SciQlop
61 61 * @param dataSourceUid the unique id of the data source containing the item. It is used to get
62 62 * the data provider associated to the data source, and pass it to for the variable creation
63 63 * @param productItem the item to load
64 64 */
65 65 void loadProductItem(const QUuid &dataSourceUid, const DataSourceItem &productItem) noexcept;
66 66
67 67 /// Returns the MIME data associated to a list of product meta data
68 68 static QByteArray mimeDataForProductsData(const QVariantList &productsData);
69 69
70 70 /// Returns the list of meta data contained in a MIME data
71 71 static QVariantList productsDataForMimeData(const QByteArray &mimeData);
72 72
73 73 public slots:
74 74 /// Manage init/end of the controller
75 75 void initialize();
76 76 void finalize();
77 77
78 /// Request the creation of a variable from the ID_DATA_KEY of a product
79 void requestVariableFromProductIdKey(const QString &datasourceIdKey);
80
81 /// Request the creation of a variable from metadata of a product
78 82 void requestVariable(const QVariantHash &productData);
79 83
80 84 signals:
81 85 /// Signal emitted when a structure has been set for a data source
82 86 void dataSourceItemSet(DataSourceItem *dataSourceItem);
83 87
84 88 /**
85 89 * Signal emitted when a variable creation is asked for a product
86 90 * @param variableName the name of the variable
87 91 * @param variableMetadata the metadata of the variable
88 92 * @param variableProvider the provider that will be used to retrieve the data of the variable
89 93 * (can be null)
90 94 */
91 95 void variableCreationRequested(const QString &variableName,
92 96 const QVariantHash &variableMetadata,
93 97 std::shared_ptr<IDataProvider> variableProvider);
94 98
95 99 private:
96 100 void waitForFinish();
97 101
98 102 class DataSourceControllerPrivate;
99 103 spimpl::unique_impl_ptr<DataSourceControllerPrivate> impl;
100 104 };
101 105
102 106 #endif // SCIQLOP_DATASOURCECONTROLLER_H
@@ -1,156 +1,164
1 1 #ifndef SCIQLOP_DATASOURCEITEM_H
2 2 #define SCIQLOP_DATASOURCEITEM_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Common/spimpl.h>
7 7
8 8 #include <QVariant>
9 9 #include <QVector>
10 10
11 11 class DataSourceItemAction;
12 12
13 13 /**
14 14 * Possible types of an item
15 15 */
16 16 enum class DataSourceItemType { NODE, PRODUCT, COMPONENT };
17 17
18 18 /**
19 19 * @brief The DataSourceItem class aims to represent a structure element of a data source.
20 20 * A data source has a tree structure that is made up of a main DataSourceItem object (root)
21 21 * containing other DataSourceItem objects (children).
22 22 * For each DataSourceItem can be associated a set of data representing it.
23 23 */
24 24 class SCIQLOP_CORE_EXPORT DataSourceItem {
25 25 public:
26 26 /// Key associated with the name of the item
27 27 static const QString NAME_DATA_KEY;
28 28 /// Key associated with the plugin of the item
29 29 static const QString PLUGIN_DATA_KEY;
30 30 /// Key associated with a unique id of the plugin
31 31 static const QString ID_DATA_KEY;
32 32
33 33 explicit DataSourceItem(DataSourceItemType type, const QString &name);
34 34 explicit DataSourceItem(DataSourceItemType type, QVariantHash data = {});
35 35
36 36 std::unique_ptr<DataSourceItem> clone() const;
37 37
38 38 /// @return the actions of the item as a vector
39 39 QVector<DataSourceItemAction *> actions() const noexcept;
40 40
41 41 /**
42 42 * Adds an action to the item. The item takes ownership of the action, and the action is
43 43 * automatically associated to the item
44 44 * @param action the action to add
45 45 */
46 46 void addAction(std::unique_ptr<DataSourceItemAction> action) noexcept;
47 47
48 48 /**
49 49 * Adds a child to the item. The item takes ownership of the child.
50 50 * @param child the child to add
51 51 */
52 52 void appendChild(std::unique_ptr<DataSourceItem> child) noexcept;
53 53
54 54 /**
55 55 * Returns the item's child associated to an index
56 56 * @param childIndex the index to search
57 57 * @return a pointer to the child if index is valid, nullptr otherwise
58 58 */
59 59 DataSourceItem *child(int childIndex) const noexcept;
60 60
61 61 int childCount() const noexcept;
62 62
63 63 /**
64 64 * Get the data associated to a key
65 65 * @param key the key to search
66 66 * @return the data found if key is valid, default QVariant otherwise
67 67 */
68 68 QVariant data(const QString &key) const noexcept;
69 69
70 70 /// Gets all data
71 71 QVariantHash data() const noexcept;
72 72
73 73 /**
74 74 * Merge in the item the source item passed as parameter.
75 75 *
76 76 * The merge is done by adding as child of the item the complete tree represented by the source
77 77 * item. If a part of the tree already exists in the item (based on the name of the nodes), it
78 78 * is merged by completing the existing tree by items "leaves" (products, components or nodes
79 79 * with no child).
80 80 *
81 81 * For example, with item representing the tree:
82 82 * R (root node)
83 83 * - N1 (node)
84 84 * -- N11 (node)
85 85 * --- P1 (product)
86 86 * --- P2 (product)
87 87 * - N2 (node)
88 88 *
89 89 * and the source item representing the tree:
90 90 * N1 (root node)
91 91 * - N11 (node)
92 92 * -- P3 (product)
93 93 * - N12 (node)
94 94 *
95 95 * The leaves of the source item to merge into the item are N1/N11/P3 and N1/N12 => we therefore
96 96 * have the following merge result:
97 97 * R
98 98 * - N1
99 99 * -- N11
100 100 * --- P1
101 101 * --- P2
102 102 * --- P3 (added leaf)
103 103 * -- N12 (added leaf)
104 104 *
105 105 * @param item the source item
106 106 * @remarks No control is performed on products or components that are merged into the same tree
107 107 * part (two products or components may have the same name)
108 108 * @remarks the merge is made by copy (source item is not changed and still exists after the
109 109 * operation)
110 110 */
111 111 void merge(const DataSourceItem &item);
112 112
113 113 bool isRoot() const noexcept;
114 114
115 115 QString name() const noexcept;
116 116
117 117 /**
118 118 * Get the item's parent
119 119 * @return a pointer to the parent if it exists, nullptr if the item is a root
120 120 */
121 121 DataSourceItem *parentItem() const noexcept;
122 122
123 123 /**
124 124 * Gets the item's root
125 125 * @return the top parent, the item itself if it's the root item
126 126 */
127 127 const DataSourceItem &rootItem() const noexcept;
128 128
129 129 /**
130 130 * Sets or appends a value to a key
131 131 * @param key the key
132 132 * @param value the value
133 133 * @param append if true, the value is added to the values already existing for the key,
134 134 * otherwise it replaces the existing values
135 135 */
136 136 void setData(const QString &key, const QVariant &value, bool append = false) noexcept;
137 137
138 138 DataSourceItemType type() const noexcept;
139 139
140 140 /**
141 141 * @brief Searches the first child matching the specified data.
142 142 * @param data The data to search.
143 143 * @param recursive So the search recursively.
144 144 * @return the item matching the data or nullptr if it was not found.
145 145 */
146 146 DataSourceItem *findItem(const QVariantHash &data, bool recursive);
147 147
148 /**
149 * @brief Searches the first child matching the specified \p ID_DATA_KEY in its metadata.
150 * @param id The id to search.
151 * @param recursive So the search recursively.
152 * @return the item matching the data or nullptr if it was not found.
153 */
154 DataSourceItem *findItem(const QString &datasourceIdKey, bool recursive);
155
148 156 bool operator==(const DataSourceItem &other);
149 157 bool operator!=(const DataSourceItem &other);
150 158
151 159 private:
152 160 class DataSourceItemPrivate;
153 161 spimpl::unique_impl_ptr<DataSourceItemPrivate> impl;
154 162 };
155 163
156 164 #endif // SCIQLOP_DATASOURCEITEMMODEL_H
@@ -1,89 +1,95
1 1 #ifndef SCIQLOP_VARIABLE_H
2 2 #define SCIQLOP_VARIABLE_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Data/DataSeriesIterator.h>
7 #include <Data/DataSeriesType.h>
7 8 #include <Data/SqpRange.h>
8 9
9 10 #include <QLoggingCategory>
10 11 #include <QObject>
11 12
12 13 #include <Common/MetaTypes.h>
13 14 #include <Common/spimpl.h>
14 15
15 16 Q_DECLARE_LOGGING_CATEGORY(LOG_Variable)
16 17
17 18 class IDataSeries;
18 19 class QString;
19 20
20 21 /**
21 22 * @brief The Variable class represents a variable in SciQlop.
22 23 */
23 24 class SCIQLOP_CORE_EXPORT Variable : public QObject {
24 25
25 26 Q_OBJECT
26 27
27 28 public:
28 29 explicit Variable(const QString &name, const QVariantHash &metadata = {});
29 30
30 31 /// Copy ctor
31 32 explicit Variable(const Variable &other);
32 33
33 34 std::shared_ptr<Variable> clone() const;
34 35
35 36 QString name() const noexcept;
36 37 void setName(const QString &name) noexcept;
37 38 SqpRange range() const noexcept;
38 39 void setRange(const SqpRange &range) noexcept;
39 40 SqpRange cacheRange() const noexcept;
40 41 void setCacheRange(const SqpRange &cacheRange) noexcept;
41 42
42 43 /// @return the number of points hold by the variable. The number of points is updated each time
43 44 /// the data series changes
44 45 int nbPoints() const noexcept;
45 46
46 47 /// Returns the real range of the variable, i.e. the min and max x-axis values of the data
47 48 /// series between the range of the variable. The real range is updated each time the variable
48 49 /// range or the data series changed
49 50 /// @return the real range, invalid range if the data series is null or empty
50 51 /// @sa setDataSeries()
51 52 /// @sa setRange()
52 53 SqpRange realRange() const noexcept;
53 54
54 55 /// @return the data of the variable, nullptr if there is no data
55 56 std::shared_ptr<IDataSeries> dataSeries() const noexcept;
56 57
58 /// @return the type of data that the variable holds
59 DataSeriesType type() const noexcept;
60
57 61 QVariantHash metadata() const noexcept;
58 62
59 63 bool contains(const SqpRange &range) const noexcept;
60 64 bool intersect(const SqpRange &range) const noexcept;
61 65 bool isInside(const SqpRange &range) const noexcept;
62 66
63 67 bool cacheContains(const SqpRange &range) const noexcept;
64 68 bool cacheIntersect(const SqpRange &range) const noexcept;
65 69 bool cacheIsInside(const SqpRange &range) const noexcept;
66 70
67 71 QVector<SqpRange> provideNotInCacheRangeList(const SqpRange &range) const noexcept;
68 72 QVector<SqpRange> provideInCacheRangeList(const SqpRange &range) const noexcept;
69 73 void mergeDataSeries(std::shared_ptr<IDataSeries> dataSeries) noexcept;
70 74
71 75 static QVector<SqpRange> provideNotInCacheRangeList(const SqpRange &oldRange,
72 76 const SqpRange &nextRange);
73 77
74 78 static QVector<SqpRange> provideInCacheRangeList(const SqpRange &oldRange,
75 79 const SqpRange &nextRange);
76 80
77 81 signals:
78 82 void updated();
83 /// Signal emitted when when the data series of the variable is loaded for the first time
84 void dataInitialized();
79 85
80 86 private:
81 87 class VariablePrivate;
82 88 spimpl::unique_impl_ptr<VariablePrivate> impl;
83 89 };
84 90
85 91 // Required for using shared_ptr in signals/slots
86 92 SCIQLOP_REGISTER_META_TYPE(VARIABLE_PTR_REGISTRY, std::shared_ptr<Variable>)
87 93 SCIQLOP_REGISTER_META_TYPE(VARIABLE_PTR_VECTOR_REGISTRY, QVector<std::shared_ptr<Variable> >)
88 94
89 95 #endif // SCIQLOP_VARIABLE_H
@@ -1,142 +1,144
1 1 #ifndef SCIQLOP_VARIABLECONTROLLER_H
2 2 #define SCIQLOP_VARIABLECONTROLLER_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Data/AcquisitionDataPacket.h>
7 7 #include <Data/SqpRange.h>
8 8
9 9 #include <QLoggingCategory>
10 10 #include <QObject>
11 11 #include <QUuid>
12 12
13 13 #include <Common/spimpl.h>
14 14
15 15 class IDataProvider;
16 16 class QItemSelectionModel;
17 17 class TimeController;
18 18 class Variable;
19 19 class VariableModel;
20 20
21 21 Q_DECLARE_LOGGING_CATEGORY(LOG_VariableController)
22 22
23 23
24 24 /**
25 25 * Possible types of zoom operation
26 26 */
27 27 enum class AcquisitionZoomType { ZoomOut, ZoomIn, PanRight, PanLeft, Unknown };
28 28
29 29
30 30 /**
31 31 * @brief The VariableController class aims to handle the variables in SciQlop.
32 32 */
33 33 class SCIQLOP_CORE_EXPORT VariableController : public QObject {
34 34 Q_OBJECT
35 35 public:
36 36 explicit VariableController(QObject *parent = 0);
37 37 virtual ~VariableController();
38 38
39 39 VariableModel *variableModel() noexcept;
40 40 QItemSelectionModel *variableSelectionModel() noexcept;
41 41
42 42 void setTimeController(TimeController *timeController) noexcept;
43 43
44 44 /**
45 45 * Clones the variable passed in parameter and adds the duplicate to the controller
46 46 * @param variable the variable to duplicate
47 47 * @return the duplicate created, nullptr if the variable couldn't be created
48 48 */
49 49 std::shared_ptr<Variable> cloneVariable(std::shared_ptr<Variable> variable) noexcept;
50 50
51 /**
52 * Deletes from the controller the variable passed in parameter.
53 *
54 * Delete a variable includes:
55 * - the deletion of the various references to the variable in SciQlop
56 * - the deletion of the model variable
57 * - the deletion of the provider associated with the variable
58 * - removing the cache associated with the variable
59 *
60 * @param variable the variable to delete from the controller.
61 */
62 void deleteVariable(std::shared_ptr<Variable> variable) noexcept;
63
64 /**
65 * Deletes from the controller the variables passed in parameter.
66 * @param variables the variables to delete from the controller.
67 * @sa deleteVariable()
68 */
69 void deleteVariables(const QVector<std::shared_ptr<Variable> > &variables) noexcept;
70
71 51 /// Returns the MIME data associated to a list of variables
72 52 QByteArray mimeDataForVariables(const QList<std::shared_ptr<Variable> > &variables) const;
73 53
74 54 /// Returns the list of variables contained in a MIME data
75 55 QList<std::shared_ptr<Variable> > variablesForMimeData(const QByteArray &mimeData) const;
76 56
77 57 static AcquisitionZoomType getZoomType(const SqpRange &range, const SqpRange &oldRange);
78 58 signals:
79 59 /// Signal emitted when a variable is about to be deleted from the controller
80 60 void variableAboutToBeDeleted(std::shared_ptr<Variable> variable);
81 61
82 62 /// Signal emitted when a data acquisition is requested on a range for a variable
83 63 void rangeChanged(std::shared_ptr<Variable> variable, const SqpRange &range);
84 64
85 65 /// Signal emitted when a sub range of the cacheRange of the variable can be displayed
86 66 void updateVarDisplaying(std::shared_ptr<Variable> variable, const SqpRange &range);
87 67
88 68 /// Signal emitted when all acquisitions related to the variables have been completed (whether
89 69 /// validated, canceled, or failed)
90 70 void acquisitionFinished();
91 71
72 void variableAdded(const std::shared_ptr<Variable> &variable);
73
92 74 public slots:
75 /**
76 * Deletes from the controller the variable passed in parameter.
77 *
78 * Delete a variable includes:
79 * - the deletion of the various references to the variable in SciQlop
80 * - the deletion of the model variable
81 * - the deletion of the provider associated with the variable
82 * - removing the cache associated with the variable
83 *
84 * @param variable the variable to delete from the controller.
85 */
86 void deleteVariable(std::shared_ptr<Variable> variable) noexcept;
87
88 /**
89 * Deletes from the controller the variables passed in parameter.
90 * @param variables the variables to delete from the controller.
91 * @sa deleteVariable()
92 */
93 void deleteVariables(const QVector<std::shared_ptr<Variable> > &variables) noexcept;
94
93 95 /// Request the data loading of the variable whithin range
94 96 void onRequestDataLoading(QVector<std::shared_ptr<Variable> > variables, const SqpRange &range,
95 97 bool synchronise);
96 98 /**
97 99 * Creates a new variable and adds it to the model
98 100 * @param name the name of the new variable
99 101 * @param metadata the metadata of the new variable
100 102 * @param provider the data provider for the new variable
101 103 * @return the pointer to the new variable or nullptr if the creation failed
102 104 */
103 105 std::shared_ptr<Variable> createVariable(const QString &name, const QVariantHash &metadata,
104 106 std::shared_ptr<IDataProvider> provider) noexcept;
105 107
106 108 /// Update the temporal parameters of every selected variable to dateTime
107 109 void onDateTimeOnSelection(const SqpRange &dateTime);
108 110
109 111 /// Update the temporal parameters of the specified variable
110 112 void onUpdateDateTime(std::shared_ptr<Variable> variable, const SqpRange &dateTime);
111 113
112 114
113 115 void onDataProvided(QUuid vIdentifier, const SqpRange &rangeRequested,
114 116 const SqpRange &cacheRangeRequested,
115 117 QVector<AcquisitionDataPacket> dataAcquired);
116 118
117 119 void onVariableRetrieveDataInProgress(QUuid identifier, double progress);
118 120
119 121 /// Cancel the current request for the variable
120 122 void onAbortProgressRequested(std::shared_ptr<Variable> variable);
121 123 void onAbortAcquisitionRequested(QUuid vIdentifier);
122 124
123 125 // synchronization group methods
124 126 void onAddSynchronizationGroupId(QUuid synchronizationGroupId);
125 127 void onRemoveSynchronizationGroupId(QUuid synchronizationGroupId);
126 128 void onAddSynchronized(std::shared_ptr<Variable> variable, QUuid synchronizationGroupId);
127 129
128 130 /// Desynchronizes the variable of the group whose identifier is passed in parameter
129 131 /// @remarks the method does nothing if the variable is not part of the group
130 132 void desynchronize(std::shared_ptr<Variable> variable, QUuid synchronizationGroupId);
131 133
132 134 void initialize();
133 135 void finalize();
134 136
135 137 private:
136 138 void waitForFinish();
137 139
138 140 class VariableControllerPrivate;
139 141 spimpl::unique_impl_ptr<VariableControllerPrivate> impl;
140 142 };
141 143
142 144 #endif // SCIQLOP_VARIABLECONTROLLER_H
@@ -1,169 +1,197
1 1 #include "DataSource/DataSourceController.h"
2 2 #include "DataSource/DataSourceItem.h"
3 3
4 4 #include <Data/IDataProvider.h>
5 5
6 6 #include <QMutex>
7 7 #include <QThread>
8 8
9 9 #include <QDataStream>
10 10 #include <QDir>
11 11 #include <QStandardPaths>
12 12
13 13 Q_LOGGING_CATEGORY(LOG_DataSourceController, "DataSourceController")
14 14
15 15 class DataSourceController::DataSourceControllerPrivate {
16 16 public:
17 17 QMutex m_WorkingMutex;
18 18 /// Data sources registered
19 19 QHash<QUuid, QString> m_DataSources;
20 20 /// Data sources structures
21 21 std::map<QUuid, std::unique_ptr<DataSourceItem> > m_DataSourceItems;
22 22 /// Data providers registered
23 23 /// @remarks Data providers are stored as shared_ptr as they can be sent to a variable and
24 24 /// continue to live without necessarily the data source controller
25 25 std::map<QUuid, std::shared_ptr<IDataProvider> > m_DataProviders;
26 26
27 27 // Search for the first datasource item matching the specified data
28 28 DataSourceItem *findDataSourceItem(const QVariantHash &data)
29 29 {
30 30 DataSourceItem *sourceItem = nullptr;
31 31 for (const auto &item : m_DataSourceItems) {
32 32 sourceItem = item.second->findItem(data, true);
33 33 if (sourceItem) {
34 34 break;
35 35 }
36 36 }
37 37
38 38 return sourceItem;
39 39 }
40
41 // Search for the first datasource item matching the specified ID_DATA_KEY
42 DataSourceItem *findDataSourceItem(const QString &datasourceIdKey)
43 {
44 DataSourceItem *sourceItem = nullptr;
45 for (const auto &item : m_DataSourceItems) {
46 sourceItem = item.second->findItem(datasourceIdKey, true);
47 if (sourceItem) {
48 break;
49 }
50 }
51
52 return sourceItem;
53 }
40 54 };
41 55
42 56 DataSourceController::DataSourceController(QObject *parent)
43 57 : impl{spimpl::make_unique_impl<DataSourceControllerPrivate>()}
44 58 {
45 59 qCDebug(LOG_DataSourceController()) << tr("DataSourceController construction")
46 60 << QThread::currentThread();
47 61 }
48 62
49 63 DataSourceController::~DataSourceController()
50 64 {
51 65 qCDebug(LOG_DataSourceController()) << tr("DataSourceController destruction")
52 66 << QThread::currentThread();
53 67 this->waitForFinish();
54 68 }
55 69
56 70 QUuid DataSourceController::registerDataSource(const QString &dataSourceName) noexcept
57 71 {
58 72 auto dataSourceUid = QUuid::createUuid();
59 73 impl->m_DataSources.insert(dataSourceUid, dataSourceName);
60 74
61 75 return dataSourceUid;
62 76 }
63 77
64 78 void DataSourceController::setDataSourceItem(
65 79 const QUuid &dataSourceUid, std::unique_ptr<DataSourceItem> dataSourceItem) noexcept
66 80 {
67 81 if (!dataSourceItem) {
68 82 qCWarning(LOG_DataSourceController())
69 83 << tr("Data source item can't be registered (null item)");
70 84 return;
71 85 }
72 86
73 87 if (impl->m_DataSources.contains(dataSourceUid)) {
74 88 // The data provider is implicitly converted to a shared_ptr
75 89 impl->m_DataSourceItems.insert(std::make_pair(dataSourceUid, std::move(dataSourceItem)));
76 90
77 91 // Retrieves the data source item to emit the signal with it
78 92 auto it = impl->m_DataSourceItems.find(dataSourceUid);
79 93 if (it != impl->m_DataSourceItems.end()) {
80 94 emit dataSourceItemSet(it->second.get());
81 95 }
82 96 }
83 97 else {
84 98 qCWarning(LOG_DataSourceController()) << tr("Can't set data source item for uid %1 : no "
85 99 "data source has been registered with the uid")
86 100 .arg(dataSourceUid.toString());
87 101 }
88 102 }
89 103
90 104 void DataSourceController::setDataProvider(const QUuid &dataSourceUid,
91 105 std::unique_ptr<IDataProvider> dataProvider) noexcept
92 106 {
93 107 if (impl->m_DataSources.contains(dataSourceUid)) {
94 108 impl->m_DataProviders.insert(std::make_pair(dataSourceUid, std::move(dataProvider)));
95 109 }
96 110 else {
97 111 qCWarning(LOG_DataSourceController()) << tr("Can't set data provider for uid %1 : no data "
98 112 "source has been registered with the uid")
99 113 .arg(dataSourceUid.toString());
100 114 }
101 115 }
102 116
103 117 void DataSourceController::loadProductItem(const QUuid &dataSourceUid,
104 118 const DataSourceItem &productItem) noexcept
105 119 {
106 120 if (productItem.type() == DataSourceItemType::PRODUCT
107 121 || productItem.type() == DataSourceItemType::COMPONENT) {
108 122 /// Retrieves the data provider of the data source (if any)
109 123 auto it = impl->m_DataProviders.find(dataSourceUid);
110 124 auto dataProvider = (it != impl->m_DataProviders.end()) ? it->second : nullptr;
111 125
112 126 emit variableCreationRequested(productItem.name(), productItem.data(), dataProvider);
113 127 }
114 128 else {
115 129 qCWarning(LOG_DataSourceController()) << tr("Can't load an item that is not a product");
116 130 }
117 131 }
118 132
119 133 QByteArray DataSourceController::mimeDataForProductsData(const QVariantList &productsData)
120 134 {
121 135 QByteArray encodedData;
122 136 QDataStream stream{&encodedData, QIODevice::WriteOnly};
123 137
124 138 stream << productsData;
125 139
126 140 return encodedData;
127 141 }
128 142
129 143 QVariantList DataSourceController::productsDataForMimeData(const QByteArray &mimeData)
130 144 {
131 145 QDataStream stream{mimeData};
132 146
133 147 QVariantList productList;
134 148 stream >> productList;
135 149
136 150 return productList;
137 151 }
138 152
139 153 void DataSourceController::initialize()
140 154 {
141 155 qCDebug(LOG_DataSourceController()) << tr("DataSourceController init")
142 156 << QThread::currentThread();
143 157 impl->m_WorkingMutex.lock();
144 158 qCDebug(LOG_DataSourceController()) << tr("DataSourceController init END");
145 159 }
146 160
147 161 void DataSourceController::finalize()
148 162 {
149 163 impl->m_WorkingMutex.unlock();
150 164 }
151 165
166 void DataSourceController::requestVariableFromProductIdKey(const QString &datasourceIdKey)
167 {
168 auto sourceItem = impl->findDataSourceItem(datasourceIdKey);
169
170 if (sourceItem) {
171 auto sourceName = sourceItem->rootItem().name();
172 auto sourceId = impl->m_DataSources.key(sourceName);
173 loadProductItem(sourceId, *sourceItem);
174 }
175 else {
176 qCWarning(LOG_DataSourceController()) << tr("requestVariable, product data not found");
177 }
178 }
179
152 180 void DataSourceController::requestVariable(const QVariantHash &productData)
153 181 {
154 182 auto sourceItem = impl->findDataSourceItem(productData);
155 183
156 184 if (sourceItem) {
157 185 auto sourceName = sourceItem->rootItem().name();
158 186 auto sourceId = impl->m_DataSources.key(sourceName);
159 187 loadProductItem(sourceId, *sourceItem);
160 188 }
161 189 else {
162 190 qCWarning(LOG_DataSourceController()) << tr("requestVariable, product data not found");
163 191 }
164 192 }
165 193
166 194 void DataSourceController::waitForFinish()
167 195 {
168 196 QMutexLocker locker{&impl->m_WorkingMutex};
169 197 }
@@ -1,187 +1,205
1 1 #include <DataSource/DataSourceItem.h>
2 2 #include <DataSource/DataSourceItemAction.h>
3 3 #include <DataSource/DataSourceItemMergeHelper.h>
4 4
5 5 #include <QVector>
6 6
7 7 const QString DataSourceItem::NAME_DATA_KEY = QStringLiteral("name");
8 8 const QString DataSourceItem::PLUGIN_DATA_KEY = QStringLiteral("plugin");
9 9 const QString DataSourceItem::ID_DATA_KEY = QStringLiteral("uuid");
10 10
11 11 struct DataSourceItem::DataSourceItemPrivate {
12 12 explicit DataSourceItemPrivate(DataSourceItemType type, QVariantHash data)
13 13 : m_Parent{nullptr}, m_Children{}, m_Type{type}, m_Data{std::move(data)}, m_Actions{}
14 14 {
15 15 }
16 16
17 17 DataSourceItem *m_Parent;
18 18 std::vector<std::unique_ptr<DataSourceItem> > m_Children;
19 19 DataSourceItemType m_Type;
20 20 QVariantHash m_Data;
21 21 std::vector<std::unique_ptr<DataSourceItemAction> > m_Actions;
22 22 };
23 23
24 24 DataSourceItem::DataSourceItem(DataSourceItemType type, const QString &name)
25 25 : DataSourceItem{type, QVariantHash{{NAME_DATA_KEY, name}}}
26 26 {
27 27 }
28 28
29 29 DataSourceItem::DataSourceItem(DataSourceItemType type, QVariantHash data)
30 30 : impl{spimpl::make_unique_impl<DataSourceItemPrivate>(type, std::move(data))}
31 31 {
32 32 }
33 33
34 34 std::unique_ptr<DataSourceItem> DataSourceItem::clone() const
35 35 {
36 36 auto result = std::make_unique<DataSourceItem>(impl->m_Type, impl->m_Data);
37 37
38 38 // Clones children
39 39 for (const auto &child : impl->m_Children) {
40 40 result->appendChild(std::move(child->clone()));
41 41 }
42 42
43 43 // Clones actions
44 44 for (const auto &action : impl->m_Actions) {
45 45 result->addAction(std::move(action->clone()));
46 46 }
47 47
48 48 return result;
49 49 }
50 50
51 51 QVector<DataSourceItemAction *> DataSourceItem::actions() const noexcept
52 52 {
53 53 auto result = QVector<DataSourceItemAction *>{};
54 54
55 55 std::transform(std::cbegin(impl->m_Actions), std::cend(impl->m_Actions),
56 56 std::back_inserter(result), [](const auto &action) { return action.get(); });
57 57
58 58 return result;
59 59 }
60 60
61 61 void DataSourceItem::addAction(std::unique_ptr<DataSourceItemAction> action) noexcept
62 62 {
63 63 action->setDataSourceItem(this);
64 64 impl->m_Actions.push_back(std::move(action));
65 65 }
66 66
67 67 void DataSourceItem::appendChild(std::unique_ptr<DataSourceItem> child) noexcept
68 68 {
69 69 child->impl->m_Parent = this;
70 70 impl->m_Children.push_back(std::move(child));
71 71 }
72 72
73 73 DataSourceItem *DataSourceItem::child(int childIndex) const noexcept
74 74 {
75 75 if (childIndex < 0 || childIndex >= childCount()) {
76 76 return nullptr;
77 77 }
78 78 else {
79 79 return impl->m_Children.at(childIndex).get();
80 80 }
81 81 }
82 82
83 83 int DataSourceItem::childCount() const noexcept
84 84 {
85 85 return impl->m_Children.size();
86 86 }
87 87
88 88 QVariant DataSourceItem::data(const QString &key) const noexcept
89 89 {
90 90 return impl->m_Data.value(key);
91 91 }
92 92
93 93 QVariantHash DataSourceItem::data() const noexcept
94 94 {
95 95 return impl->m_Data;
96 96 }
97 97
98 98 void DataSourceItem::merge(const DataSourceItem &item)
99 99 {
100 100 DataSourceItemMergeHelper::merge(item, *this);
101 101 }
102 102
103 103 bool DataSourceItem::isRoot() const noexcept
104 104 {
105 105 return impl->m_Parent == nullptr;
106 106 }
107 107
108 108 QString DataSourceItem::name() const noexcept
109 109 {
110 110 return data(NAME_DATA_KEY).toString();
111 111 }
112 112
113 113 DataSourceItem *DataSourceItem::parentItem() const noexcept
114 114 {
115 115 return impl->m_Parent;
116 116 }
117 117
118 118 const DataSourceItem &DataSourceItem::rootItem() const noexcept
119 119 {
120 120 return isRoot() ? *this : parentItem()->rootItem();
121 121 }
122 122
123 123 void DataSourceItem::setData(const QString &key, const QVariant &value, bool append) noexcept
124 124 {
125 125 auto it = impl->m_Data.constFind(key);
126 126 if (append && it != impl->m_Data.constEnd()) {
127 127 // Case of an existing value to which we want to add to the new value
128 128 if (it->canConvert<QVariantList>()) {
129 129 auto variantList = it->value<QVariantList>();
130 130 variantList.append(value);
131 131
132 132 impl->m_Data.insert(key, variantList);
133 133 }
134 134 else {
135 135 impl->m_Data.insert(key, QVariantList{*it, value});
136 136 }
137 137 }
138 138 else {
139 139 // Other cases :
140 140 // - new value in map OR
141 141 // - replacement of an existing value (not appending)
142 142 impl->m_Data.insert(key, value);
143 143 }
144 144 }
145 145
146 146 DataSourceItemType DataSourceItem::type() const noexcept
147 147 {
148 148 return impl->m_Type;
149 149 }
150 150
151 151 DataSourceItem *DataSourceItem::findItem(const QVariantHash &data, bool recursive)
152 152 {
153 153 for (const auto &child : impl->m_Children) {
154 154 if (child->impl->m_Data == data) {
155 155 return child.get();
156 156 }
157 157
158 158 if (recursive) {
159 159 if (auto foundItem = child->findItem(data, true)) {
160 160 return foundItem;
161 161 }
162 162 }
163 163 }
164 164
165 165 return nullptr;
166 166 }
167 167
168 DataSourceItem *DataSourceItem::findItem(const QString &datasourceIdKey, bool recursive)
169 {
170 for (const auto &child : impl->m_Children) {
171 auto childId = child->impl->m_Data.value(ID_DATA_KEY);
172 if (childId == datasourceIdKey) {
173 return child.get();
174 }
175
176 if (recursive) {
177 if (auto foundItem = child->findItem(datasourceIdKey, true)) {
178 return foundItem;
179 }
180 }
181 }
182
183 return nullptr;
184 }
185
168 186 bool DataSourceItem::operator==(const DataSourceItem &other)
169 187 {
170 188 // Compares items' attributes
171 189 if (std::tie(impl->m_Type, impl->m_Data) == std::tie(other.impl->m_Type, other.impl->m_Data)) {
172 190 // Compares contents of items' children
173 191 return std::equal(std::cbegin(impl->m_Children), std::cend(impl->m_Children),
174 192 std::cbegin(other.impl->m_Children), std::cend(other.impl->m_Children),
175 193 [](const auto &itemChild, const auto &otherChild) {
176 194 return *itemChild == *otherChild;
177 195 });
178 196 }
179 197 else {
180 198 return false;
181 199 }
182 200 }
183 201
184 202 bool DataSourceItem::operator!=(const DataSourceItem &other)
185 203 {
186 204 return !(*this == other);
187 205 }
@@ -1,388 +1,430
1 1 #include "Variable/Variable.h"
2 2
3 3 #include <Data/IDataSeries.h>
4 4 #include <Data/SqpRange.h>
5 5
6 6 #include <QMutex>
7 7 #include <QReadWriteLock>
8 8 #include <QThread>
9 9
10 10 Q_LOGGING_CATEGORY(LOG_Variable, "Variable")
11 11
12 namespace {
13
14 /**
15 * Searches in metadata for a value that can be converted to DataSeriesType
16 * @param metadata the metadata where to search
17 * @return the value converted to a DataSeriesType if it was found, UNKNOWN type otherwise
18 * @sa DataSeriesType
19 */
20 DataSeriesType findDataSeriesType(const QVariantHash &metadata)
21 {
22 auto dataSeriesType = DataSeriesType::UNKNOWN;
23
24 // Go through the metadata and stop at the first value that could be converted to DataSeriesType
25 for (auto it = metadata.cbegin(), end = metadata.cend();
26 it != end && dataSeriesType == DataSeriesType::UNKNOWN; ++it) {
27 dataSeriesType = DataSeriesTypeUtils::fromString(it.value().toString());
28 }
29
30 return dataSeriesType;
31 }
32
33 } // namespace
34
12 35 struct Variable::VariablePrivate {
13 36 explicit VariablePrivate(const QString &name, const QVariantHash &metadata)
14 37 : m_Name{name},
15 38 m_Range{INVALID_RANGE},
16 39 m_CacheRange{INVALID_RANGE},
17 40 m_Metadata{metadata},
18 41 m_DataSeries{nullptr},
19 42 m_RealRange{INVALID_RANGE},
20 m_NbPoints{0}
43 m_NbPoints{0},
44 m_Type{findDataSeriesType(m_Metadata)}
21 45 {
22 46 }
23 47
24 48 VariablePrivate(const VariablePrivate &other)
25 49 : m_Name{other.m_Name},
26 50 m_Range{other.m_Range},
27 51 m_CacheRange{other.m_CacheRange},
28 52 m_Metadata{other.m_Metadata},
29 53 m_DataSeries{other.m_DataSeries != nullptr ? other.m_DataSeries->clone() : nullptr},
30 54 m_RealRange{other.m_RealRange},
31 m_NbPoints{other.m_NbPoints}
55 m_NbPoints{other.m_NbPoints},
56 m_Type{findDataSeriesType(m_Metadata)}
32 57 {
33 58 }
34 59
35 60 void lockRead() { m_Lock.lockForRead(); }
36 61 void lockWrite() { m_Lock.lockForWrite(); }
37 62 void unlock() { m_Lock.unlock(); }
38 63
39 64 void purgeDataSeries()
40 65 {
41 66 if (m_DataSeries) {
42 67 m_DataSeries->purge(m_CacheRange.m_TStart, m_CacheRange.m_TEnd);
43 68 }
44 69 updateRealRange();
45 70 updateNbPoints();
46 71 }
47 72
48 73 void updateNbPoints() { m_NbPoints = m_DataSeries ? m_DataSeries->nbPoints() : 0; }
49 74
50 75 /// Updates real range according to current variable range and data series
51 76 void updateRealRange()
52 77 {
53 78 if (m_DataSeries) {
54 79 m_DataSeries->lockRead();
55 80 auto end = m_DataSeries->cend();
56 81 auto minXAxisIt = m_DataSeries->minXAxisData(m_Range.m_TStart);
57 82 auto maxXAxisIt = m_DataSeries->maxXAxisData(m_Range.m_TEnd);
58 83
59 84 m_RealRange
60 85 = (minXAxisIt != end && maxXAxisIt != end && minXAxisIt->x() <= maxXAxisIt->x())
61 86 ? SqpRange{minXAxisIt->x(), maxXAxisIt->x()}
62 87 : INVALID_RANGE;
63 88 m_DataSeries->unlock();
64 89 }
65 90 else {
66 91 m_RealRange = INVALID_RANGE;
67 92 }
68 93 }
69 94
70 95 QString m_Name;
71 96
72 97 SqpRange m_Range;
73 98 SqpRange m_CacheRange;
74 99 QVariantHash m_Metadata;
75 100 std::shared_ptr<IDataSeries> m_DataSeries;
76 101 SqpRange m_RealRange;
77 102 int m_NbPoints;
103 DataSeriesType m_Type;
78 104
79 105 QReadWriteLock m_Lock;
80 106 };
81 107
82 108 Variable::Variable(const QString &name, const QVariantHash &metadata)
83 109 : impl{spimpl::make_unique_impl<VariablePrivate>(name, metadata)}
84 110 {
85 111 }
86 112
87 113 Variable::Variable(const Variable &other)
88 114 : impl{spimpl::make_unique_impl<VariablePrivate>(*other.impl)}
89 115 {
90 116 }
91 117
92 118 std::shared_ptr<Variable> Variable::clone() const
93 119 {
94 120 return std::make_shared<Variable>(*this);
95 121 }
96 122
97 123 QString Variable::name() const noexcept
98 124 {
99 125 impl->lockRead();
100 126 auto name = impl->m_Name;
101 127 impl->unlock();
102 128 return name;
103 129 }
104 130
105 131 void Variable::setName(const QString &name) noexcept
106 132 {
107 133 impl->lockWrite();
108 134 impl->m_Name = name;
109 135 impl->unlock();
110 136 }
111 137
112 138 SqpRange Variable::range() const noexcept
113 139 {
114 140 impl->lockRead();
115 141 auto range = impl->m_Range;
116 142 impl->unlock();
117 143 return range;
118 144 }
119 145
120 146 void Variable::setRange(const SqpRange &range) noexcept
121 147 {
122 148 impl->lockWrite();
123 149 impl->m_Range = range;
124 150 impl->updateRealRange();
125 151 impl->unlock();
126 152 }
127 153
128 154 SqpRange Variable::cacheRange() const noexcept
129 155 {
130 156 impl->lockRead();
131 157 auto cacheRange = impl->m_CacheRange;
132 158 impl->unlock();
133 159 return cacheRange;
134 160 }
135 161
136 162 void Variable::setCacheRange(const SqpRange &cacheRange) noexcept
137 163 {
138 164 impl->lockWrite();
139 165 if (cacheRange != impl->m_CacheRange) {
140 166 impl->m_CacheRange = cacheRange;
141 167 }
142 168 impl->unlock();
143 169 }
144 170
145 171 int Variable::nbPoints() const noexcept
146 172 {
147 173 return impl->m_NbPoints;
148 174 }
149 175
150 176 SqpRange Variable::realRange() const noexcept
151 177 {
152 178 return impl->m_RealRange;
153 179 }
154 180
155 181 void Variable::mergeDataSeries(std::shared_ptr<IDataSeries> dataSeries) noexcept
156 182 {
157 183 qCDebug(LOG_Variable()) << "TORM Variable::mergeDataSeries"
158 184 << QThread::currentThread()->objectName();
159 185 if (!dataSeries) {
160 186 /// @todo ALX : log
161 187 return;
162 188 }
163 189
190 auto dataInit = false;
191
164 192 // Add or merge the data
165 193 impl->lockWrite();
166 194 if (!impl->m_DataSeries) {
167 195 impl->m_DataSeries = dataSeries->clone();
196 dataInit = true;
168 197 }
169 198 else {
170 199 impl->m_DataSeries->merge(dataSeries.get());
171 200 }
172 201 impl->purgeDataSeries();
173 202 impl->unlock();
203
204 if (dataInit) {
205 emit dataInitialized();
206 }
174 207 }
175 208
176 209
177 210 std::shared_ptr<IDataSeries> Variable::dataSeries() const noexcept
178 211 {
179 212 impl->lockRead();
180 213 auto dataSeries = impl->m_DataSeries;
181 214 impl->unlock();
182 215
183 216 return dataSeries;
184 217 }
185 218
219 DataSeriesType Variable::type() const noexcept
220 {
221 impl->lockRead();
222 auto type = impl->m_Type;
223 impl->unlock();
224
225 return type;
226 }
227
186 228 QVariantHash Variable::metadata() const noexcept
187 229 {
188 230 impl->lockRead();
189 231 auto metadata = impl->m_Metadata;
190 232 impl->unlock();
191 233 return metadata;
192 234 }
193 235
194 236 bool Variable::contains(const SqpRange &range) const noexcept
195 237 {
196 238 impl->lockRead();
197 239 auto res = impl->m_Range.contains(range);
198 240 impl->unlock();
199 241 return res;
200 242 }
201 243
202 244 bool Variable::intersect(const SqpRange &range) const noexcept
203 245 {
204 246
205 247 impl->lockRead();
206 248 auto res = impl->m_Range.intersect(range);
207 249 impl->unlock();
208 250 return res;
209 251 }
210 252
211 253 bool Variable::isInside(const SqpRange &range) const noexcept
212 254 {
213 255 impl->lockRead();
214 256 auto res = range.contains(SqpRange{impl->m_Range.m_TStart, impl->m_Range.m_TEnd});
215 257 impl->unlock();
216 258 return res;
217 259 }
218 260
219 261 bool Variable::cacheContains(const SqpRange &range) const noexcept
220 262 {
221 263 impl->lockRead();
222 264 auto res = impl->m_CacheRange.contains(range);
223 265 impl->unlock();
224 266 return res;
225 267 }
226 268
227 269 bool Variable::cacheIntersect(const SqpRange &range) const noexcept
228 270 {
229 271 impl->lockRead();
230 272 auto res = impl->m_CacheRange.intersect(range);
231 273 impl->unlock();
232 274 return res;
233 275 }
234 276
235 277 bool Variable::cacheIsInside(const SqpRange &range) const noexcept
236 278 {
237 279 impl->lockRead();
238 280 auto res = range.contains(SqpRange{impl->m_CacheRange.m_TStart, impl->m_CacheRange.m_TEnd});
239 281 impl->unlock();
240 282 return res;
241 283 }
242 284
243 285
244 286 QVector<SqpRange> Variable::provideNotInCacheRangeList(const SqpRange &range) const noexcept
245 287 {
246 288 // This code assume that cach in contigue. Can return 0, 1 or 2 SqpRange
247 289 auto notInCache = QVector<SqpRange>{};
248 290 if (impl->m_CacheRange != INVALID_RANGE) {
249 291
250 292 if (!this->cacheContains(range)) {
251 293 if (range.m_TEnd <= impl->m_CacheRange.m_TStart
252 294 || range.m_TStart >= impl->m_CacheRange.m_TEnd) {
253 295 notInCache << range;
254 296 }
255 297 else if (range.m_TStart < impl->m_CacheRange.m_TStart
256 298 && range.m_TEnd <= impl->m_CacheRange.m_TEnd) {
257 299 notInCache << SqpRange{range.m_TStart, impl->m_CacheRange.m_TStart};
258 300 }
259 301 else if (range.m_TStart < impl->m_CacheRange.m_TStart
260 302 && range.m_TEnd > impl->m_CacheRange.m_TEnd) {
261 303 notInCache << SqpRange{range.m_TStart, impl->m_CacheRange.m_TStart}
262 304 << SqpRange{impl->m_CacheRange.m_TEnd, range.m_TEnd};
263 305 }
264 306 else if (range.m_TStart < impl->m_CacheRange.m_TEnd) {
265 307 notInCache << SqpRange{impl->m_CacheRange.m_TEnd, range.m_TEnd};
266 308 }
267 309 else {
268 310 qCCritical(LOG_Variable()) << tr("Detection of unknown case.")
269 311 << QThread::currentThread();
270 312 }
271 313 }
272 314 }
273 315 else {
274 316 notInCache << range;
275 317 }
276 318
277 319 return notInCache;
278 320 }
279 321
280 322 QVector<SqpRange> Variable::provideInCacheRangeList(const SqpRange &range) const noexcept
281 323 {
282 324 // This code assume that cach in contigue. Can return 0 or 1 SqpRange
283 325
284 326 auto inCache = QVector<SqpRange>{};
285 327
286 328 if (impl->m_CacheRange != INVALID_RANGE) {
287 329
288 330 if (this->cacheIntersect(range)) {
289 331 if (range.m_TStart <= impl->m_CacheRange.m_TStart
290 332 && range.m_TEnd >= impl->m_CacheRange.m_TStart
291 333 && range.m_TEnd < impl->m_CacheRange.m_TEnd) {
292 334 inCache << SqpRange{impl->m_CacheRange.m_TStart, range.m_TEnd};
293 335 }
294 336
295 337 else if (range.m_TStart >= impl->m_CacheRange.m_TStart
296 338 && range.m_TEnd <= impl->m_CacheRange.m_TEnd) {
297 339 inCache << range;
298 340 }
299 341 else if (range.m_TStart > impl->m_CacheRange.m_TStart
300 342 && range.m_TEnd > impl->m_CacheRange.m_TEnd) {
301 343 inCache << SqpRange{range.m_TStart, impl->m_CacheRange.m_TEnd};
302 344 }
303 345 else if (range.m_TStart <= impl->m_CacheRange.m_TStart
304 346 && range.m_TEnd >= impl->m_CacheRange.m_TEnd) {
305 347 inCache << impl->m_CacheRange;
306 348 }
307 349 else {
308 350 qCCritical(LOG_Variable()) << tr("Detection of unknown case.")
309 351 << QThread::currentThread();
310 352 }
311 353 }
312 354 }
313 355
314 356 return inCache;
315 357 }
316 358
317 359
318 360 QVector<SqpRange> Variable::provideNotInCacheRangeList(const SqpRange &oldRange,
319 361 const SqpRange &nextRange)
320 362 {
321 363
322 364 // This code assume that cach in contigue. Can return 0, 1 or 2 SqpRange
323 365 auto notInCache = QVector<SqpRange>{};
324 366 if (oldRange != INVALID_RANGE) {
325 367
326 368 if (!oldRange.contains(nextRange)) {
327 369 if (nextRange.m_TEnd <= oldRange.m_TStart || nextRange.m_TStart >= oldRange.m_TEnd) {
328 370 notInCache << nextRange;
329 371 }
330 372 else if (nextRange.m_TStart < oldRange.m_TStart
331 373 && nextRange.m_TEnd <= oldRange.m_TEnd) {
332 374 notInCache << SqpRange{nextRange.m_TStart, oldRange.m_TStart};
333 375 }
334 376 else if (nextRange.m_TStart < oldRange.m_TStart && nextRange.m_TEnd > oldRange.m_TEnd) {
335 377 notInCache << SqpRange{nextRange.m_TStart, oldRange.m_TStart}
336 378 << SqpRange{oldRange.m_TEnd, nextRange.m_TEnd};
337 379 }
338 380 else if (nextRange.m_TStart < oldRange.m_TEnd) {
339 381 notInCache << SqpRange{oldRange.m_TEnd, nextRange.m_TEnd};
340 382 }
341 383 else {
342 384 qCCritical(LOG_Variable()) << tr("Detection of unknown case.")
343 385 << QThread::currentThread();
344 386 }
345 387 }
346 388 }
347 389 else {
348 390 notInCache << nextRange;
349 391 }
350 392
351 393 return notInCache;
352 394 }
353 395
354 396 QVector<SqpRange> Variable::provideInCacheRangeList(const SqpRange &oldRange,
355 397 const SqpRange &nextRange)
356 398 {
357 399 // This code assume that cach is contigue. Can return 0 or 1 SqpRange
358 400
359 401 auto inCache = QVector<SqpRange>{};
360 402
361 403 if (oldRange != INVALID_RANGE) {
362 404
363 405 if (oldRange.intersect(nextRange)) {
364 406 if (nextRange.m_TStart <= oldRange.m_TStart && nextRange.m_TEnd >= oldRange.m_TStart
365 407 && nextRange.m_TEnd < oldRange.m_TEnd) {
366 408 inCache << SqpRange{oldRange.m_TStart, nextRange.m_TEnd};
367 409 }
368 410
369 411 else if (nextRange.m_TStart >= oldRange.m_TStart
370 412 && nextRange.m_TEnd <= oldRange.m_TEnd) {
371 413 inCache << nextRange;
372 414 }
373 415 else if (nextRange.m_TStart > oldRange.m_TStart && nextRange.m_TEnd > oldRange.m_TEnd) {
374 416 inCache << SqpRange{nextRange.m_TStart, oldRange.m_TEnd};
375 417 }
376 418 else if (nextRange.m_TStart <= oldRange.m_TStart
377 419 && nextRange.m_TEnd >= oldRange.m_TEnd) {
378 420 inCache << oldRange;
379 421 }
380 422 else {
381 423 qCCritical(LOG_Variable()) << tr("Detection of unknown case.")
382 424 << QThread::currentThread();
383 425 }
384 426 }
385 427 }
386 428
387 429 return inCache;
388 430 }
@@ -1,1102 +1,1104
1 1 #include <Variable/Variable.h>
2 2 #include <Variable/VariableAcquisitionWorker.h>
3 3 #include <Variable/VariableCacheStrategy.h>
4 4 #include <Variable/VariableCacheStrategyFactory.h>
5 5 #include <Variable/VariableController.h>
6 6 #include <Variable/VariableModel.h>
7 7 #include <Variable/VariableSynchronizationGroup.h>
8 8
9 9 #include <Data/DataProviderParameters.h>
10 10 #include <Data/IDataProvider.h>
11 11 #include <Data/IDataSeries.h>
12 12 #include <Data/VariableRequest.h>
13 13 #include <Time/TimeController.h>
14 14
15 15 #include <QDataStream>
16 16 #include <QMutex>
17 17 #include <QThread>
18 18 #include <QUuid>
19 19 #include <QtCore/QItemSelectionModel>
20 20
21 21 #include <deque>
22 22 #include <set>
23 23 #include <unordered_map>
24 24
25 25 Q_LOGGING_CATEGORY(LOG_VariableController, "VariableController")
26 26
27 27 namespace {
28 28
29 29 SqpRange computeSynchroRangeRequested(const SqpRange &varRange, const SqpRange &graphRange,
30 30 const SqpRange &oldGraphRange)
31 31 {
32 32 auto zoomType = VariableController::getZoomType(graphRange, oldGraphRange);
33 33
34 34 auto varRangeRequested = varRange;
35 35 switch (zoomType) {
36 36 case AcquisitionZoomType::ZoomIn: {
37 37 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
38 38 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
39 39 varRangeRequested.m_TStart += deltaLeft;
40 40 varRangeRequested.m_TEnd -= deltaRight;
41 41 break;
42 42 }
43 43
44 44 case AcquisitionZoomType::ZoomOut: {
45 45 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
46 46 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
47 47 varRangeRequested.m_TStart -= deltaLeft;
48 48 varRangeRequested.m_TEnd += deltaRight;
49 49 break;
50 50 }
51 51 case AcquisitionZoomType::PanRight: {
52 52 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
53 53 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
54 54 varRangeRequested.m_TStart += deltaLeft;
55 55 varRangeRequested.m_TEnd += deltaRight;
56 56 break;
57 57 }
58 58 case AcquisitionZoomType::PanLeft: {
59 59 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
60 60 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
61 61 varRangeRequested.m_TStart -= deltaLeft;
62 62 varRangeRequested.m_TEnd -= deltaRight;
63 63 break;
64 64 }
65 65 case AcquisitionZoomType::Unknown: {
66 66 qCCritical(LOG_VariableController())
67 67 << VariableController::tr("Impossible to synchronize: zoom type unknown");
68 68 break;
69 69 }
70 70 default:
71 71 qCCritical(LOG_VariableController()) << VariableController::tr(
72 72 "Impossible to synchronize: zoom type not take into account");
73 73 // No action
74 74 break;
75 75 }
76 76
77 77 return varRangeRequested;
78 78 }
79 79 }
80 80
81 81 enum class VariableRequestHandlerState { OFF, RUNNING, PENDING };
82 82
83 83 struct VariableRequestHandler {
84 84
85 85 VariableRequestHandler()
86 86 {
87 87 m_CanUpdate = false;
88 88 m_State = VariableRequestHandlerState::OFF;
89 89 }
90 90
91 91 QUuid m_VarId;
92 92 VariableRequest m_RunningVarRequest;
93 93 VariableRequest m_PendingVarRequest;
94 94 VariableRequestHandlerState m_State;
95 95 bool m_CanUpdate;
96 96 };
97 97
98 98 struct VariableController::VariableControllerPrivate {
99 99 explicit VariableControllerPrivate(VariableController *parent)
100 100 : m_WorkingMutex{},
101 101 m_VariableModel{new VariableModel{parent}},
102 102 m_VariableSelectionModel{new QItemSelectionModel{m_VariableModel, parent}},
103 103 // m_VariableCacheStrategy{std::make_unique<VariableCacheStrategy>()},
104 104 m_VariableCacheStrategy{VariableCacheStrategyFactory::createCacheStrategy(
105 105 CacheStrategy::SingleThreshold)},
106 106 m_VariableAcquisitionWorker{std::make_unique<VariableAcquisitionWorker>()},
107 107 q{parent}
108 108 {
109 109
110 110 m_VariableAcquisitionWorker->moveToThread(&m_VariableAcquisitionWorkerThread);
111 111 m_VariableAcquisitionWorkerThread.setObjectName("VariableAcquisitionWorkerThread");
112 112 }
113 113
114 114
115 115 virtual ~VariableControllerPrivate()
116 116 {
117 117 qCDebug(LOG_VariableController()) << tr("VariableControllerPrivate destruction");
118 118 m_VariableAcquisitionWorkerThread.quit();
119 119 m_VariableAcquisitionWorkerThread.wait();
120 120 }
121 121
122 122
123 123 void processRequest(std::shared_ptr<Variable> var, const SqpRange &rangeRequested,
124 124 QUuid varRequestId);
125 125
126 126 std::shared_ptr<Variable> findVariable(QUuid vIdentifier);
127 127 std::shared_ptr<IDataSeries>
128 128 retrieveDataSeries(const QVector<AcquisitionDataPacket> acqDataPacketVector);
129 129
130 130 void registerProvider(std::shared_ptr<IDataProvider> provider);
131 131
132 132 void storeVariableRequest(QUuid varId, QUuid varRequestId, const VariableRequest &varRequest);
133 133 QUuid acceptVariableRequest(QUuid varId, std::shared_ptr<IDataSeries> dataSeries);
134 134 void updateVariables(QUuid varRequestId);
135 135 void updateVariableRequest(QUuid varRequestId);
136 136 void cancelVariableRequest(QUuid varRequestId);
137 137 void executeVarRequest(std::shared_ptr<Variable> var, VariableRequest &varRequest);
138 138
139 139 template <typename VariableIterator>
140 140 void desynchronize(VariableIterator variableIt, const QUuid &syncGroupId);
141 141
142 142 QMutex m_WorkingMutex;
143 143 /// Variable model. The VariableController has the ownership
144 144 VariableModel *m_VariableModel;
145 145 QItemSelectionModel *m_VariableSelectionModel;
146 146
147 147
148 148 TimeController *m_TimeController{nullptr};
149 149 std::unique_ptr<VariableCacheStrategy> m_VariableCacheStrategy;
150 150 std::unique_ptr<VariableAcquisitionWorker> m_VariableAcquisitionWorker;
151 151 QThread m_VariableAcquisitionWorkerThread;
152 152
153 153 std::unordered_map<std::shared_ptr<Variable>, std::shared_ptr<IDataProvider> >
154 154 m_VariableToProviderMap;
155 155 std::unordered_map<std::shared_ptr<Variable>, QUuid> m_VariableToIdentifierMap;
156 156 std::map<QUuid, std::shared_ptr<VariableSynchronizationGroup> >
157 157 m_GroupIdToVariableSynchronizationGroupMap;
158 158 std::map<QUuid, QUuid> m_VariableIdGroupIdMap;
159 159 std::set<std::shared_ptr<IDataProvider> > m_ProviderSet;
160 160
161 161 std::map<QUuid, std::list<QUuid> > m_VarGroupIdToVarIds;
162 162 std::map<QUuid, std::unique_ptr<VariableRequestHandler> > m_VarIdToVarRequestHandler;
163 163
164 164 VariableController *q;
165 165 };
166 166
167 167
168 168 VariableController::VariableController(QObject *parent)
169 169 : QObject{parent}, impl{spimpl::make_unique_impl<VariableControllerPrivate>(this)}
170 170 {
171 171 qCDebug(LOG_VariableController()) << tr("VariableController construction")
172 172 << QThread::currentThread();
173 173
174 174 connect(impl->m_VariableModel, &VariableModel::abortProgessRequested, this,
175 175 &VariableController::onAbortProgressRequested);
176 176
177 177 connect(impl->m_VariableAcquisitionWorker.get(),
178 178 &VariableAcquisitionWorker::variableCanceledRequested, this,
179 179 &VariableController::onAbortAcquisitionRequested);
180 180
181 181 connect(impl->m_VariableAcquisitionWorker.get(), &VariableAcquisitionWorker::dataProvided, this,
182 182 &VariableController::onDataProvided);
183 183 connect(impl->m_VariableAcquisitionWorker.get(),
184 184 &VariableAcquisitionWorker::variableRequestInProgress, this,
185 185 &VariableController::onVariableRetrieveDataInProgress);
186 186
187 187
188 188 connect(&impl->m_VariableAcquisitionWorkerThread, &QThread::started,
189 189 impl->m_VariableAcquisitionWorker.get(), &VariableAcquisitionWorker::initialize);
190 190 connect(&impl->m_VariableAcquisitionWorkerThread, &QThread::finished,
191 191 impl->m_VariableAcquisitionWorker.get(), &VariableAcquisitionWorker::finalize);
192 192
193 193 connect(impl->m_VariableModel, &VariableModel::requestVariableRangeUpdate, this,
194 194 &VariableController::onUpdateDateTime);
195 195
196 196 impl->m_VariableAcquisitionWorkerThread.start();
197 197 }
198 198
199 199 VariableController::~VariableController()
200 200 {
201 201 qCDebug(LOG_VariableController()) << tr("VariableController destruction")
202 202 << QThread::currentThread();
203 203 this->waitForFinish();
204 204 }
205 205
206 206 VariableModel *VariableController::variableModel() noexcept
207 207 {
208 208 return impl->m_VariableModel;
209 209 }
210 210
211 211 QItemSelectionModel *VariableController::variableSelectionModel() noexcept
212 212 {
213 213 return impl->m_VariableSelectionModel;
214 214 }
215 215
216 216 void VariableController::setTimeController(TimeController *timeController) noexcept
217 217 {
218 218 impl->m_TimeController = timeController;
219 219 }
220 220
221 221 std::shared_ptr<Variable>
222 222 VariableController::cloneVariable(std::shared_ptr<Variable> variable) noexcept
223 223 {
224 224 if (impl->m_VariableModel->containsVariable(variable)) {
225 225 // Clones variable
226 226 auto duplicate = variable->clone();
227 227
228 228 // Adds clone to model
229 229 impl->m_VariableModel->addVariable(duplicate);
230 230
231 231 // Generates clone identifier
232 232 impl->m_VariableToIdentifierMap[duplicate] = QUuid::createUuid();
233 233
234 234 // Registers provider
235 235 auto variableProvider = impl->m_VariableToProviderMap.at(variable);
236 236 auto duplicateProvider = variableProvider != nullptr ? variableProvider->clone() : nullptr;
237 237
238 238 impl->m_VariableToProviderMap[duplicate] = duplicateProvider;
239 239 if (duplicateProvider) {
240 240 impl->registerProvider(duplicateProvider);
241 241 }
242 242
243 243 return duplicate;
244 244 }
245 245 else {
246 246 qCCritical(LOG_VariableController())
247 247 << tr("Can't create duplicate of variable %1: variable not registered in the model")
248 248 .arg(variable->name());
249 249 return nullptr;
250 250 }
251 251 }
252 252
253 253 void VariableController::deleteVariable(std::shared_ptr<Variable> variable) noexcept
254 254 {
255 255 if (!variable) {
256 256 qCCritical(LOG_VariableController()) << "Can't delete variable: variable is null";
257 257 return;
258 258 }
259 259
260 260 // Spreads in SciQlop that the variable will be deleted, so that potential receivers can
261 261 // make some treatments before the deletion
262 262 emit variableAboutToBeDeleted(variable);
263 263
264 264 auto variableIt = impl->m_VariableToIdentifierMap.find(variable);
265 265 Q_ASSERT(variableIt != impl->m_VariableToIdentifierMap.cend());
266 266
267 267 auto variableId = variableIt->second;
268 268
269 269 // Removes variable's handler
270 270 impl->m_VarIdToVarRequestHandler.erase(variableId);
271 271
272 272 // Desynchronizes variable (if the variable is in a sync group)
273 273 auto syncGroupIt = impl->m_VariableIdGroupIdMap.find(variableId);
274 274 if (syncGroupIt != impl->m_VariableIdGroupIdMap.cend()) {
275 275 impl->desynchronize(variableIt, syncGroupIt->second);
276 276 }
277 277
278 278 // Deletes identifier
279 279 impl->m_VariableToIdentifierMap.erase(variableIt);
280 280
281 281 // Deletes provider
282 282 auto nbProvidersDeleted = impl->m_VariableToProviderMap.erase(variable);
283 283 qCDebug(LOG_VariableController())
284 284 << tr("Number of providers deleted for variable %1: %2")
285 285 .arg(variable->name(), QString::number(nbProvidersDeleted));
286 286
287 287
288 288 // Deletes from model
289 289 impl->m_VariableModel->deleteVariable(variable);
290 290 }
291 291
292 292 void VariableController::deleteVariables(
293 293 const QVector<std::shared_ptr<Variable> > &variables) noexcept
294 294 {
295 295 for (auto variable : qAsConst(variables)) {
296 296 deleteVariable(variable);
297 297 }
298 298 }
299 299
300 300 QByteArray
301 301 VariableController::mimeDataForVariables(const QList<std::shared_ptr<Variable> > &variables) const
302 302 {
303 303 auto encodedData = QByteArray{};
304 304
305 305 QVariantList ids;
306 306 for (auto &var : variables) {
307 307 auto itVar = impl->m_VariableToIdentifierMap.find(var);
308 308 if (itVar == impl->m_VariableToIdentifierMap.cend()) {
309 309 qCCritical(LOG_VariableController())
310 310 << tr("Impossible to find the data for an unknown variable.");
311 311 }
312 312
313 313 ids << itVar->second.toByteArray();
314 314 }
315 315
316 316 QDataStream stream{&encodedData, QIODevice::WriteOnly};
317 317 stream << ids;
318 318
319 319 return encodedData;
320 320 }
321 321
322 322 QList<std::shared_ptr<Variable> >
323 323 VariableController::variablesForMimeData(const QByteArray &mimeData) const
324 324 {
325 325 auto variables = QList<std::shared_ptr<Variable> >{};
326 326 QDataStream stream{mimeData};
327 327
328 328 QVariantList ids;
329 329 stream >> ids;
330 330
331 331 for (auto id : ids) {
332 332 auto uuid = QUuid{id.toByteArray()};
333 333 auto var = impl->findVariable(uuid);
334 334 variables << var;
335 335 }
336 336
337 337 return variables;
338 338 }
339 339
340 340 std::shared_ptr<Variable>
341 341 VariableController::createVariable(const QString &name, const QVariantHash &metadata,
342 342 std::shared_ptr<IDataProvider> provider) noexcept
343 343 {
344 344 if (!impl->m_TimeController) {
345 345 qCCritical(LOG_VariableController())
346 346 << tr("Impossible to create variable: The time controller is null");
347 347 return nullptr;
348 348 }
349 349
350 350 auto range = impl->m_TimeController->dateTime();
351 351
352 352 if (auto newVariable = impl->m_VariableModel->createVariable(name, metadata)) {
353 353 auto varId = QUuid::createUuid();
354 354
355 355 // Create the handler
356 356 auto varRequestHandler = std::make_unique<VariableRequestHandler>();
357 357 varRequestHandler->m_VarId = varId;
358 358
359 359 impl->m_VarIdToVarRequestHandler.insert(
360 360 std::make_pair(varId, std::move(varRequestHandler)));
361 361
362 362 // store the provider
363 363 impl->registerProvider(provider);
364 364
365 365 // Associate the provider
366 366 impl->m_VariableToProviderMap[newVariable] = provider;
367 367 impl->m_VariableToIdentifierMap[newVariable] = varId;
368 368
369 369 this->onRequestDataLoading(QVector<std::shared_ptr<Variable> >{newVariable}, range, false);
370 370
371 371 // auto varRequestId = QUuid::createUuid();
372 372 // qCInfo(LOG_VariableController()) << "createVariable: " << varId << varRequestId;
373 373 // impl->processRequest(newVariable, range, varRequestId);
374 374 // impl->updateVariableRequest(varRequestId);
375 375
376 emit variableAdded(newVariable);
377
376 378 return newVariable;
377 379 }
378 380
379 381 qCCritical(LOG_VariableController()) << tr("Impossible to create variable");
380 382 return nullptr;
381 383 }
382 384
383 385 void VariableController::onDateTimeOnSelection(const SqpRange &dateTime)
384 386 {
385 387 // NOTE: Even if acquisition request is aborting, the graphe range will be changed
386 388 qCDebug(LOG_VariableController()) << "VariableController::onDateTimeOnSelection"
387 389 << QThread::currentThread()->objectName();
388 390 auto selectedRows = impl->m_VariableSelectionModel->selectedRows();
389 391
390 392 // NOTE we only permit the time modification for one variable
391 393 // DEPRECATED
392 394 // auto variables = QVector<std::shared_ptr<Variable> >{};
393 395 // for (const auto &selectedRow : qAsConst(selectedRows)) {
394 396 // if (auto selectedVariable =
395 397 // impl->m_VariableModel->variable(selectedRow.row())) {
396 398 // variables << selectedVariable;
397 399
398 400 // // notify that rescale operation has to be done
399 401 // emit rangeChanged(selectedVariable, dateTime);
400 402 // }
401 403 // }
402 404 // if (!variables.isEmpty()) {
403 405 // this->onRequestDataLoading(variables, dateTime, synchro);
404 406 // }
405 407 if (selectedRows.size() == 1) {
406 408
407 409 if (auto selectedVariable
408 410 = impl->m_VariableModel->variable(qAsConst(selectedRows).first().row())) {
409 411
410 412 onUpdateDateTime(selectedVariable, dateTime);
411 413 }
412 414 }
413 415 else if (selectedRows.size() > 1) {
414 416 qCCritical(LOG_VariableController())
415 417 << tr("Impossible to set time for more than 1 variable in the same time");
416 418 }
417 419 else {
418 420 qCWarning(LOG_VariableController())
419 421 << tr("There is no variable selected to set the time one");
420 422 }
421 423 }
422 424
423 425 void VariableController::onUpdateDateTime(std::shared_ptr<Variable> variable,
424 426 const SqpRange &dateTime)
425 427 {
426 428 auto itVar = impl->m_VariableToIdentifierMap.find(variable);
427 429 if (itVar == impl->m_VariableToIdentifierMap.cend()) {
428 430 qCCritical(LOG_VariableController())
429 431 << tr("Impossible to onDateTimeOnSelection request for unknown variable");
430 432 return;
431 433 }
432 434
433 435 // notify that rescale operation has to be done
434 436 emit rangeChanged(variable, dateTime);
435 437
436 438 auto synchro
437 439 = impl->m_VariableIdGroupIdMap.find(itVar->second) != impl->m_VariableIdGroupIdMap.cend();
438 440
439 441 this->onRequestDataLoading(QVector<std::shared_ptr<Variable> >{variable}, dateTime, synchro);
440 442 }
441 443
442 444 void VariableController::onDataProvided(QUuid vIdentifier, const SqpRange &rangeRequested,
443 445 const SqpRange &cacheRangeRequested,
444 446 QVector<AcquisitionDataPacket> dataAcquired)
445 447 {
446 448 qCDebug(LOG_VariableController()) << tr("onDataProvided") << QThread::currentThread();
447 449 auto retrievedDataSeries = impl->retrieveDataSeries(dataAcquired);
448 450 auto varRequestId = impl->acceptVariableRequest(vIdentifier, retrievedDataSeries);
449 451 if (!varRequestId.isNull()) {
450 452 impl->updateVariables(varRequestId);
451 453 }
452 454 }
453 455
454 456 void VariableController::onVariableRetrieveDataInProgress(QUuid identifier, double progress)
455 457 {
456 458 qCDebug(LOG_VariableController())
457 459 << "TORM: variableController::onVariableRetrieveDataInProgress"
458 460 << QThread::currentThread()->objectName() << progress;
459 461 if (auto var = impl->findVariable(identifier)) {
460 462 impl->m_VariableModel->setDataProgress(var, progress);
461 463 }
462 464 else {
463 465 qCCritical(LOG_VariableController())
464 466 << tr("Impossible to notify progression of a null variable");
465 467 }
466 468 }
467 469
468 470 void VariableController::onAbortProgressRequested(std::shared_ptr<Variable> variable)
469 471 {
470 472 qCDebug(LOG_VariableController()) << "TORM: variableController::onAbortProgressRequested"
471 473 << QThread::currentThread()->objectName() << variable->name();
472 474
473 475 auto itVar = impl->m_VariableToIdentifierMap.find(variable);
474 476 if (itVar == impl->m_VariableToIdentifierMap.cend()) {
475 477 qCCritical(LOG_VariableController())
476 478 << tr("Impossible to onAbortProgressRequested request for unknown variable");
477 479 return;
478 480 }
479 481
480 482 auto varId = itVar->second;
481 483
482 484 auto itVarHandler = impl->m_VarIdToVarRequestHandler.find(varId);
483 485 if (itVarHandler == impl->m_VarIdToVarRequestHandler.cend()) {
484 486 qCCritical(LOG_VariableController())
485 487 << tr("Impossible to onAbortProgressRequested for variable with unknown handler");
486 488 return;
487 489 }
488 490
489 491 auto varHandler = itVarHandler->second.get();
490 492
491 493 // case where a variable has a running request
492 494 if (varHandler->m_State != VariableRequestHandlerState::OFF) {
493 495 impl->cancelVariableRequest(varHandler->m_RunningVarRequest.m_VariableGroupId);
494 496 }
495 497 }
496 498
497 499 void VariableController::onAbortAcquisitionRequested(QUuid vIdentifier)
498 500 {
499 501 qCDebug(LOG_VariableController()) << "TORM: variableController::onAbortAcquisitionRequested"
500 502 << QThread::currentThread()->objectName() << vIdentifier;
501 503
502 504 if (auto var = impl->findVariable(vIdentifier)) {
503 505 this->onAbortProgressRequested(var);
504 506 }
505 507 else {
506 508 qCCritical(LOG_VariableController())
507 509 << tr("Impossible to abort Acquisition Requestof a null variable");
508 510 }
509 511 }
510 512
511 513 void VariableController::onAddSynchronizationGroupId(QUuid synchronizationGroupId)
512 514 {
513 515 qCDebug(LOG_VariableController()) << "TORM: VariableController::onAddSynchronizationGroupId"
514 516 << QThread::currentThread()->objectName()
515 517 << synchronizationGroupId;
516 518 auto vSynchroGroup = std::make_shared<VariableSynchronizationGroup>();
517 519 impl->m_GroupIdToVariableSynchronizationGroupMap.insert(
518 520 std::make_pair(synchronizationGroupId, vSynchroGroup));
519 521 }
520 522
521 523 void VariableController::onRemoveSynchronizationGroupId(QUuid synchronizationGroupId)
522 524 {
523 525 impl->m_GroupIdToVariableSynchronizationGroupMap.erase(synchronizationGroupId);
524 526 }
525 527
526 528 void VariableController::onAddSynchronized(std::shared_ptr<Variable> variable,
527 529 QUuid synchronizationGroupId)
528 530
529 531 {
530 532 qCDebug(LOG_VariableController()) << "TORM: VariableController::onAddSynchronized"
531 533 << synchronizationGroupId;
532 534 auto varToVarIdIt = impl->m_VariableToIdentifierMap.find(variable);
533 535 if (varToVarIdIt != impl->m_VariableToIdentifierMap.cend()) {
534 536 auto groupIdToVSGIt
535 537 = impl->m_GroupIdToVariableSynchronizationGroupMap.find(synchronizationGroupId);
536 538 if (groupIdToVSGIt != impl->m_GroupIdToVariableSynchronizationGroupMap.cend()) {
537 539 impl->m_VariableIdGroupIdMap.insert(
538 540 std::make_pair(varToVarIdIt->second, synchronizationGroupId));
539 541 groupIdToVSGIt->second->addVariableId(varToVarIdIt->second);
540 542 }
541 543 else {
542 544 qCCritical(LOG_VariableController())
543 545 << tr("Impossible to synchronize a variable with an unknown sycnhronization group")
544 546 << variable->name();
545 547 }
546 548 }
547 549 else {
548 550 qCCritical(LOG_VariableController())
549 551 << tr("Impossible to synchronize a variable with no identifier") << variable->name();
550 552 }
551 553 }
552 554
553 555 void VariableController::desynchronize(std::shared_ptr<Variable> variable,
554 556 QUuid synchronizationGroupId)
555 557 {
556 558 // Gets variable id
557 559 auto variableIt = impl->m_VariableToIdentifierMap.find(variable);
558 560 if (variableIt == impl->m_VariableToIdentifierMap.cend()) {
559 561 qCCritical(LOG_VariableController())
560 562 << tr("Can't desynchronize variable %1: variable identifier not found")
561 563 .arg(variable->name());
562 564 return;
563 565 }
564 566
565 567 impl->desynchronize(variableIt, synchronizationGroupId);
566 568 }
567 569
568 570 void VariableController::onRequestDataLoading(QVector<std::shared_ptr<Variable> > variables,
569 571 const SqpRange &range, bool synchronise)
570 572 {
571 573 // variables is assumed synchronized
572 574 // TODO: Asser variables synchronization
573 575 // we want to load data of the variable for the dateTime.
574 576 if (variables.isEmpty()) {
575 577 return;
576 578 }
577 579
578 580 auto varRequestId = QUuid::createUuid();
579 581 qCDebug(LOG_VariableController()) << "VariableController::onRequestDataLoading"
580 582 << QThread::currentThread()->objectName() << varRequestId
581 583 << range << synchronise;
582 584
583 585 if (!synchronise) {
584 586 auto varIds = std::list<QUuid>{};
585 587 for (const auto &var : variables) {
586 588 auto vId = impl->m_VariableToIdentifierMap.at(var);
587 589 varIds.push_back(vId);
588 590 }
589 591 impl->m_VarGroupIdToVarIds.insert(std::make_pair(varRequestId, varIds));
590 592 for (const auto &var : variables) {
591 593 qCDebug(LOG_VariableController()) << "processRequest for" << var->name() << varRequestId
592 594 << varIds.size();
593 595 impl->processRequest(var, range, varRequestId);
594 596 }
595 597 }
596 598 else {
597 599 auto vId = impl->m_VariableToIdentifierMap.at(variables.first());
598 600 auto varIdToGroupIdIt = impl->m_VariableIdGroupIdMap.find(vId);
599 601 if (varIdToGroupIdIt != impl->m_VariableIdGroupIdMap.cend()) {
600 602 auto groupId = varIdToGroupIdIt->second;
601 603
602 604 auto vSynchronizationGroup
603 605 = impl->m_GroupIdToVariableSynchronizationGroupMap.at(groupId);
604 606 auto vSyncIds = vSynchronizationGroup->getIds();
605 607
606 608 auto varIds = std::list<QUuid>{};
607 609 for (auto vId : vSyncIds) {
608 610 varIds.push_back(vId);
609 611 }
610 612 impl->m_VarGroupIdToVarIds.insert(std::make_pair(varRequestId, varIds));
611 613
612 614 for (auto vId : vSyncIds) {
613 615 auto var = impl->findVariable(vId);
614 616
615 617 // Don't process already processed var
616 618 if (var != nullptr) {
617 619 qCDebug(LOG_VariableController()) << "processRequest synchro for" << var->name()
618 620 << varRequestId;
619 621 auto vSyncRangeRequested
620 622 = variables.contains(var)
621 623 ? range
622 624 : computeSynchroRangeRequested(var->range(), range,
623 625 variables.first()->range());
624 626 qCDebug(LOG_VariableController()) << "synchro RR" << vSyncRangeRequested;
625 627 impl->processRequest(var, vSyncRangeRequested, varRequestId);
626 628 }
627 629 else {
628 630 qCCritical(LOG_VariableController())
629 631
630 632 << tr("Impossible to synchronize a null variable");
631 633 }
632 634 }
633 635 }
634 636 }
635 637
636 638 impl->updateVariables(varRequestId);
637 639 }
638 640
639 641
640 642 void VariableController::initialize()
641 643 {
642 644 qCDebug(LOG_VariableController()) << tr("VariableController init") << QThread::currentThread();
643 645 impl->m_WorkingMutex.lock();
644 646 qCDebug(LOG_VariableController()) << tr("VariableController init END");
645 647 }
646 648
647 649 void VariableController::finalize()
648 650 {
649 651 impl->m_WorkingMutex.unlock();
650 652 }
651 653
652 654 void VariableController::waitForFinish()
653 655 {
654 656 QMutexLocker locker{&impl->m_WorkingMutex};
655 657 }
656 658
657 659 AcquisitionZoomType VariableController::getZoomType(const SqpRange &range, const SqpRange &oldRange)
658 660 {
659 661 // t1.m_TStart <= t2.m_TStart && t2.m_TEnd <= t1.m_TEnd
660 662 auto zoomType = AcquisitionZoomType::Unknown;
661 663 if (range.m_TStart <= oldRange.m_TStart && oldRange.m_TEnd <= range.m_TEnd) {
662 664 qCDebug(LOG_VariableController()) << "zoomtype: ZoomOut";
663 665 zoomType = AcquisitionZoomType::ZoomOut;
664 666 }
665 667 else if (range.m_TStart > oldRange.m_TStart && range.m_TEnd > oldRange.m_TEnd) {
666 668 qCDebug(LOG_VariableController()) << "zoomtype: PanRight";
667 669 zoomType = AcquisitionZoomType::PanRight;
668 670 }
669 671 else if (range.m_TStart < oldRange.m_TStart && range.m_TEnd < oldRange.m_TEnd) {
670 672 qCDebug(LOG_VariableController()) << "zoomtype: PanLeft";
671 673 zoomType = AcquisitionZoomType::PanLeft;
672 674 }
673 675 else if (range.m_TStart >= oldRange.m_TStart && oldRange.m_TEnd >= range.m_TEnd) {
674 676 qCDebug(LOG_VariableController()) << "zoomtype: ZoomIn";
675 677 zoomType = AcquisitionZoomType::ZoomIn;
676 678 }
677 679 else {
678 680 qCDebug(LOG_VariableController()) << "getZoomType: Unknown type detected";
679 681 }
680 682 return zoomType;
681 683 }
682 684
683 685 void VariableController::VariableControllerPrivate::processRequest(std::shared_ptr<Variable> var,
684 686 const SqpRange &rangeRequested,
685 687 QUuid varRequestId)
686 688 {
687 689 auto itVar = m_VariableToIdentifierMap.find(var);
688 690 if (itVar == m_VariableToIdentifierMap.cend()) {
689 691 qCCritical(LOG_VariableController())
690 692 << tr("Impossible to process request for unknown variable");
691 693 return;
692 694 }
693 695
694 696 auto varId = itVar->second;
695 697
696 698 auto itVarHandler = m_VarIdToVarRequestHandler.find(varId);
697 699 if (itVarHandler == m_VarIdToVarRequestHandler.cend()) {
698 700 qCCritical(LOG_VariableController())
699 701 << tr("Impossible to process request for variable with unknown handler");
700 702 return;
701 703 }
702 704
703 705 auto oldRange = var->range();
704 706
705 707 auto varHandler = itVarHandler->second.get();
706 708
707 709 if (varHandler->m_State != VariableRequestHandlerState::OFF) {
708 710 oldRange = varHandler->m_RunningVarRequest.m_RangeRequested;
709 711 }
710 712
711 713 auto varRequest = VariableRequest{};
712 714 varRequest.m_VariableGroupId = varRequestId;
713 715 auto varStrategyRangesRequested
714 716 = m_VariableCacheStrategy->computeRange(oldRange, rangeRequested);
715 717 varRequest.m_RangeRequested = varStrategyRangesRequested.first;
716 718 varRequest.m_CacheRangeRequested = varStrategyRangesRequested.second;
717 719
718 720 switch (varHandler->m_State) {
719 721 case VariableRequestHandlerState::OFF: {
720 722 qCDebug(LOG_VariableController()) << tr("Process Request OFF")
721 723 << varRequest.m_RangeRequested
722 724 << varRequest.m_CacheRangeRequested;
723 725 varHandler->m_RunningVarRequest = varRequest;
724 726 varHandler->m_State = VariableRequestHandlerState::RUNNING;
725 727 executeVarRequest(var, varRequest);
726 728 break;
727 729 }
728 730 case VariableRequestHandlerState::RUNNING: {
729 731 qCDebug(LOG_VariableController()) << tr("Process Request RUNNING")
730 732 << varRequest.m_RangeRequested
731 733 << varRequest.m_CacheRangeRequested;
732 734 varHandler->m_State = VariableRequestHandlerState::PENDING;
733 735 varHandler->m_PendingVarRequest = varRequest;
734 736 break;
735 737 }
736 738 case VariableRequestHandlerState::PENDING: {
737 739 qCDebug(LOG_VariableController()) << tr("Process Request PENDING")
738 740 << varRequest.m_RangeRequested
739 741 << varRequest.m_CacheRangeRequested;
740 742 auto variableGroupIdToCancel = varHandler->m_PendingVarRequest.m_VariableGroupId;
741 743 cancelVariableRequest(variableGroupIdToCancel);
742 744 // Cancel variable can make state downgrade
743 745 varHandler->m_State = VariableRequestHandlerState::PENDING;
744 746 varHandler->m_PendingVarRequest = varRequest;
745 747
746 748 break;
747 749 }
748 750 default:
749 751 qCCritical(LOG_VariableController())
750 752 << QObject::tr("Unknown VariableRequestHandlerState");
751 753 }
752 754 }
753 755
754 756 std::shared_ptr<Variable>
755 757 VariableController::VariableControllerPrivate::findVariable(QUuid vIdentifier)
756 758 {
757 759 std::shared_ptr<Variable> var;
758 760 auto findReply = [vIdentifier](const auto &entry) { return vIdentifier == entry.second; };
759 761
760 762 auto end = m_VariableToIdentifierMap.cend();
761 763 auto it = std::find_if(m_VariableToIdentifierMap.cbegin(), end, findReply);
762 764 if (it != end) {
763 765 var = it->first;
764 766 }
765 767 else {
766 768 qCCritical(LOG_VariableController())
767 769 << tr("Impossible to find the variable with the identifier: ") << vIdentifier;
768 770 }
769 771
770 772 return var;
771 773 }
772 774
773 775 std::shared_ptr<IDataSeries> VariableController::VariableControllerPrivate::retrieveDataSeries(
774 776 const QVector<AcquisitionDataPacket> acqDataPacketVector)
775 777 {
776 778 qCDebug(LOG_VariableController()) << tr("TORM: retrieveDataSeries acqDataPacketVector size")
777 779 << acqDataPacketVector.size();
778 780 std::shared_ptr<IDataSeries> dataSeries;
779 781 if (!acqDataPacketVector.isEmpty()) {
780 782 dataSeries = acqDataPacketVector[0].m_DateSeries;
781 783 for (int i = 1; i < acqDataPacketVector.size(); ++i) {
782 784 dataSeries->merge(acqDataPacketVector[i].m_DateSeries.get());
783 785 }
784 786 }
785 787 qCDebug(LOG_VariableController()) << tr("TORM: retrieveDataSeries acqDataPacketVector size END")
786 788 << acqDataPacketVector.size();
787 789 return dataSeries;
788 790 }
789 791
790 792 void VariableController::VariableControllerPrivate::registerProvider(
791 793 std::shared_ptr<IDataProvider> provider)
792 794 {
793 795 if (m_ProviderSet.find(provider) == m_ProviderSet.end()) {
794 796 qCDebug(LOG_VariableController()) << tr("Registering of a new provider")
795 797 << provider->objectName();
796 798 m_ProviderSet.insert(provider);
797 799 connect(provider.get(), &IDataProvider::dataProvided, m_VariableAcquisitionWorker.get(),
798 800 &VariableAcquisitionWorker::onVariableDataAcquired);
799 801 connect(provider.get(), &IDataProvider::dataProvidedProgress,
800 802 m_VariableAcquisitionWorker.get(),
801 803 &VariableAcquisitionWorker::onVariableRetrieveDataInProgress);
802 804 connect(provider.get(), &IDataProvider::dataProvidedFailed,
803 805 m_VariableAcquisitionWorker.get(),
804 806 &VariableAcquisitionWorker::onVariableAcquisitionFailed);
805 807 }
806 808 else {
807 809 qCDebug(LOG_VariableController()) << tr("Cannot register provider, it already exists ");
808 810 }
809 811 }
810 812
811 813 QUuid VariableController::VariableControllerPrivate::acceptVariableRequest(
812 814 QUuid varId, std::shared_ptr<IDataSeries> dataSeries)
813 815 {
814 816 auto itVarHandler = m_VarIdToVarRequestHandler.find(varId);
815 817 if (itVarHandler == m_VarIdToVarRequestHandler.cend()) {
816 818 return QUuid();
817 819 }
818 820
819 821 auto varHandler = itVarHandler->second.get();
820 822 if (varHandler->m_State == VariableRequestHandlerState::OFF) {
821 823 qCCritical(LOG_VariableController())
822 824 << tr("acceptVariableRequest impossible on a variable with OFF state");
823 825 }
824 826
825 827 varHandler->m_RunningVarRequest.m_DataSeries = dataSeries;
826 828 varHandler->m_CanUpdate = true;
827 829
828 830 // Element traité, on a déjà toutes les données necessaires
829 831 auto varGroupId = varHandler->m_RunningVarRequest.m_VariableGroupId;
830 832 qCDebug(LOG_VariableController()) << "Variable::acceptVariableRequest" << varGroupId
831 833 << m_VarGroupIdToVarIds.size();
832 834
833 835 return varHandler->m_RunningVarRequest.m_VariableGroupId;
834 836 }
835 837
836 838 void VariableController::VariableControllerPrivate::updateVariables(QUuid varRequestId)
837 839 {
838 840 qCDebug(LOG_VariableController()) << "VariableControllerPrivate::updateVariables"
839 841 << QThread::currentThread()->objectName() << varRequestId;
840 842
841 843 auto varGroupIdToVarIdsIt = m_VarGroupIdToVarIds.find(varRequestId);
842 844 if (varGroupIdToVarIdsIt == m_VarGroupIdToVarIds.end()) {
843 845 qCWarning(LOG_VariableController())
844 846 << tr("Impossible to updateVariables of unknown variables") << varRequestId;
845 847 return;
846 848 }
847 849
848 850 auto &varIds = varGroupIdToVarIdsIt->second;
849 851 auto varIdsEnd = varIds.end();
850 852 bool processVariableUpdate = true;
851 853 qCDebug(LOG_VariableController()) << "VariableControllerPrivate::updateVariables"
852 854 << varRequestId << varIds.size();
853 855 for (auto varIdsIt = varIds.begin(); (varIdsIt != varIdsEnd) && processVariableUpdate;
854 856 ++varIdsIt) {
855 857 auto itVarHandler = m_VarIdToVarRequestHandler.find(*varIdsIt);
856 858 if (itVarHandler != m_VarIdToVarRequestHandler.cend()) {
857 859 processVariableUpdate &= itVarHandler->second->m_CanUpdate;
858 860 }
859 861 }
860 862
861 863 if (processVariableUpdate) {
862 864 qCDebug(LOG_VariableController()) << "Final update OK for the var request" << varIds.size();
863 865 for (auto varIdsIt = varIds.begin(); varIdsIt != varIdsEnd; ++varIdsIt) {
864 866 auto itVarHandler = m_VarIdToVarRequestHandler.find(*varIdsIt);
865 867 if (itVarHandler != m_VarIdToVarRequestHandler.cend()) {
866 868 if (auto var = findVariable(*varIdsIt)) {
867 869 auto &varRequest = itVarHandler->second->m_RunningVarRequest;
868 870 var->setRange(varRequest.m_RangeRequested);
869 871 var->setCacheRange(varRequest.m_CacheRangeRequested);
870 872 qCDebug(LOG_VariableController()) << tr("1: onDataProvided")
871 873 << varRequest.m_RangeRequested
872 874 << varRequest.m_CacheRangeRequested;
873 875 qCDebug(LOG_VariableController()) << tr("2: onDataProvided var points before")
874 876 << var->nbPoints()
875 877 << varRequest.m_DataSeries->nbPoints();
876 878 var->mergeDataSeries(varRequest.m_DataSeries);
877 879 qCDebug(LOG_VariableController()) << tr("3: onDataProvided var points after")
878 880 << var->nbPoints();
879 881
880 882 emit var->updated();
881 883 qCDebug(LOG_VariableController()) << tr("Update OK");
882 884 }
883 885 else {
884 886 qCCritical(LOG_VariableController())
885 887 << tr("Impossible to update data to a null variable");
886 888 }
887 889 }
888 890 }
889 891 updateVariableRequest(varRequestId);
890 892
891 893 // cleaning varRequestId
892 894 qCDebug(LOG_VariableController()) << tr("m_VarGroupIdToVarIds erase") << varRequestId;
893 895 m_VarGroupIdToVarIds.erase(varRequestId);
894 896 if (m_VarGroupIdToVarIds.empty()) {
895 897 emit q->acquisitionFinished();
896 898 }
897 899 }
898 900 }
899 901
900 902
901 903 void VariableController::VariableControllerPrivate::updateVariableRequest(QUuid varRequestId)
902 904 {
903 905 auto varGroupIdToVarIdsIt = m_VarGroupIdToVarIds.find(varRequestId);
904 906 if (varGroupIdToVarIdsIt == m_VarGroupIdToVarIds.end()) {
905 907 qCCritical(LOG_VariableController()) << QObject::tr(
906 908 "Impossible to updateVariableRequest since varGroupdId isn't here anymore");
907 909
908 910 return;
909 911 }
910 912
911 913 auto &varIds = varGroupIdToVarIdsIt->second;
912 914 auto varIdsEnd = varIds.end();
913 915 for (auto varIdsIt = varIds.begin(); (varIdsIt != varIdsEnd); ++varIdsIt) {
914 916 auto itVarHandler = m_VarIdToVarRequestHandler.find(*varIdsIt);
915 917 if (itVarHandler != m_VarIdToVarRequestHandler.cend()) {
916 918
917 919 auto varHandler = itVarHandler->second.get();
918 920 varHandler->m_CanUpdate = false;
919 921
920 922
921 923 switch (varHandler->m_State) {
922 924 case VariableRequestHandlerState::OFF: {
923 925 qCCritical(LOG_VariableController())
924 926 << QObject::tr("Impossible to update a variable with handler in OFF state");
925 927 } break;
926 928 case VariableRequestHandlerState::RUNNING: {
927 929 varHandler->m_State = VariableRequestHandlerState::OFF;
928 930 varHandler->m_RunningVarRequest = VariableRequest{};
929 931 break;
930 932 }
931 933 case VariableRequestHandlerState::PENDING: {
932 934 varHandler->m_State = VariableRequestHandlerState::RUNNING;
933 935 varHandler->m_RunningVarRequest = varHandler->m_PendingVarRequest;
934 936 varHandler->m_PendingVarRequest = VariableRequest{};
935 937 auto var = findVariable(itVarHandler->first);
936 938 executeVarRequest(var, varHandler->m_RunningVarRequest);
937 939 updateVariables(varHandler->m_RunningVarRequest.m_VariableGroupId);
938 940 break;
939 941 }
940 942 default:
941 943 qCCritical(LOG_VariableController())
942 944 << QObject::tr("Unknown VariableRequestHandlerState");
943 945 }
944 946 }
945 947 }
946 948 }
947 949
948 950
949 951 void VariableController::VariableControllerPrivate::cancelVariableRequest(QUuid varRequestId)
950 952 {
951 953 qCDebug(LOG_VariableController()) << tr("cancelVariableRequest") << varRequestId;
952 954
953 955 auto varGroupIdToVarIdsIt = m_VarGroupIdToVarIds.find(varRequestId);
954 956 if (varGroupIdToVarIdsIt == m_VarGroupIdToVarIds.end()) {
955 957 qCCritical(LOG_VariableController())
956 958 << tr("Impossible to cancelVariableRequest for unknown varGroupdId") << varRequestId;
957 959 return;
958 960 }
959 961
960 962 auto &varIds = varGroupIdToVarIdsIt->second;
961 963 auto varIdsEnd = varIds.end();
962 964 for (auto varIdsIt = varIds.begin(); (varIdsIt != varIdsEnd); ++varIdsIt) {
963 965 auto itVarHandler = m_VarIdToVarRequestHandler.find(*varIdsIt);
964 966 if (itVarHandler != m_VarIdToVarRequestHandler.cend()) {
965 967
966 968 auto varHandler = itVarHandler->second.get();
967 969 varHandler->m_VarId = QUuid{};
968 970 switch (varHandler->m_State) {
969 971 case VariableRequestHandlerState::OFF: {
970 972 qCWarning(LOG_VariableController())
971 973 << QObject::tr("Impossible to cancel a variable with no running request");
972 974 break;
973 975 }
974 976 case VariableRequestHandlerState::RUNNING: {
975 977
976 978 if (varHandler->m_RunningVarRequest.m_VariableGroupId == varRequestId) {
977 979 auto var = findVariable(itVarHandler->first);
978 980 auto varProvider = m_VariableToProviderMap.at(var);
979 981 if (varProvider != nullptr) {
980 982 m_VariableAcquisitionWorker->abortProgressRequested(
981 983 itVarHandler->first);
982 984 }
983 985 m_VariableModel->setDataProgress(var, 0.0);
984 986 varHandler->m_CanUpdate = false;
985 987 varHandler->m_State = VariableRequestHandlerState::OFF;
986 988 varHandler->m_RunningVarRequest = VariableRequest{};
987 989 }
988 990 else {
989 991 // TODO: log Impossible to cancel the running variable request beacause its
990 992 // varRequestId isn't not the canceled one
991 993 }
992 994 break;
993 995 }
994 996 case VariableRequestHandlerState::PENDING: {
995 997 if (varHandler->m_RunningVarRequest.m_VariableGroupId == varRequestId) {
996 998 auto var = findVariable(itVarHandler->first);
997 999 auto varProvider = m_VariableToProviderMap.at(var);
998 1000 if (varProvider != nullptr) {
999 1001 m_VariableAcquisitionWorker->abortProgressRequested(
1000 1002 itVarHandler->first);
1001 1003 }
1002 1004 m_VariableModel->setDataProgress(var, 0.0);
1003 1005 varHandler->m_CanUpdate = false;
1004 1006 varHandler->m_State = VariableRequestHandlerState::RUNNING;
1005 1007 varHandler->m_RunningVarRequest = varHandler->m_PendingVarRequest;
1006 1008 varHandler->m_PendingVarRequest = VariableRequest{};
1007 1009 executeVarRequest(var, varHandler->m_RunningVarRequest);
1008 1010 }
1009 1011 else if (varHandler->m_PendingVarRequest.m_VariableGroupId == varRequestId) {
1010 1012 varHandler->m_State = VariableRequestHandlerState::RUNNING;
1011 1013 varHandler->m_PendingVarRequest = VariableRequest{};
1012 1014 }
1013 1015 else {
1014 1016 // TODO: log Impossible to cancel the variable request beacause its
1015 1017 // varRequestId isn't not the canceled one
1016 1018 }
1017 1019 break;
1018 1020 }
1019 1021 default:
1020 1022 qCCritical(LOG_VariableController())
1021 1023 << QObject::tr("Unknown VariableRequestHandlerState");
1022 1024 }
1023 1025 }
1024 1026 }
1025 1027 qCDebug(LOG_VariableController()) << tr("cancelVariableRequest: erase") << varRequestId;
1026 1028 m_VarGroupIdToVarIds.erase(varRequestId);
1027 1029 if (m_VarGroupIdToVarIds.empty()) {
1028 1030 emit q->acquisitionFinished();
1029 1031 }
1030 1032 }
1031 1033
1032 1034 void VariableController::VariableControllerPrivate::executeVarRequest(std::shared_ptr<Variable> var,
1033 1035 VariableRequest &varRequest)
1034 1036 {
1035 1037 qCDebug(LOG_VariableController()) << tr("TORM: executeVarRequest");
1036 1038
1037 1039 auto varIdIt = m_VariableToIdentifierMap.find(var);
1038 1040 if (varIdIt == m_VariableToIdentifierMap.cend()) {
1039 1041 qCWarning(LOG_VariableController()) << tr(
1040 1042 "Can't execute request of a variable that is not registered (may has been deleted)");
1041 1043 return;
1042 1044 }
1043 1045
1044 1046 auto varId = varIdIt->second;
1045 1047
1046 1048 auto varCacheRange = var->cacheRange();
1047 1049 auto varCacheRangeRequested = varRequest.m_CacheRangeRequested;
1048 1050 auto notInCacheRangeList
1049 1051 = Variable::provideNotInCacheRangeList(varCacheRange, varCacheRangeRequested);
1050 1052 auto inCacheRangeList
1051 1053 = Variable::provideInCacheRangeList(varCacheRange, varCacheRangeRequested);
1052 1054
1053 1055 if (!notInCacheRangeList.empty()) {
1054 1056
1055 1057 auto varProvider = m_VariableToProviderMap.at(var);
1056 1058 if (varProvider != nullptr) {
1057 1059 qCDebug(LOG_VariableController()) << "executeVarRequest " << varRequest.m_RangeRequested
1058 1060 << varRequest.m_CacheRangeRequested;
1059 1061 m_VariableAcquisitionWorker->pushVariableRequest(
1060 1062 varRequest.m_VariableGroupId, varId, varRequest.m_RangeRequested,
1061 1063 varRequest.m_CacheRangeRequested,
1062 1064 DataProviderParameters{std::move(notInCacheRangeList), var->metadata()},
1063 1065 varProvider);
1064 1066 }
1065 1067 else {
1066 1068 qCCritical(LOG_VariableController())
1067 1069 << "Impossible to provide data with a null provider";
1068 1070 }
1069 1071
1070 1072 if (!inCacheRangeList.empty()) {
1071 1073 emit q->updateVarDisplaying(var, inCacheRangeList.first());
1072 1074 }
1073 1075 }
1074 1076 else {
1075 1077 acceptVariableRequest(varId,
1076 1078 var->dataSeries()->subDataSeries(varRequest.m_CacheRangeRequested));
1077 1079 }
1078 1080 }
1079 1081
1080 1082 template <typename VariableIterator>
1081 1083 void VariableController::VariableControllerPrivate::desynchronize(VariableIterator variableIt,
1082 1084 const QUuid &syncGroupId)
1083 1085 {
1084 1086 const auto &variable = variableIt->first;
1085 1087 const auto &variableId = variableIt->second;
1086 1088
1087 1089 // Gets synchronization group
1088 1090 auto groupIt = m_GroupIdToVariableSynchronizationGroupMap.find(syncGroupId);
1089 1091 if (groupIt == m_GroupIdToVariableSynchronizationGroupMap.cend()) {
1090 1092 qCCritical(LOG_VariableController())
1091 1093 << tr("Can't desynchronize variable %1: unknown synchronization group")
1092 1094 .arg(variable->name());
1093 1095 return;
1094 1096 }
1095 1097
1096 1098 // Removes variable from synchronization group
1097 1099 auto synchronizationGroup = groupIt->second;
1098 1100 synchronizationGroup->removeVariableId(variableId);
1099 1101
1100 1102 // Removes link between variable and synchronization group
1101 1103 m_VariableIdGroupIdMap.erase(variableId);
1102 1104 }
@@ -1,58 +1,64
1 1 #ifndef SCIQLOP_CATALOGUEEVENTSWIDGET_H
2 2 #define SCIQLOP_CATALOGUEEVENTSWIDGET_H
3 3
4 4 #include <Common/spimpl.h>
5 5 #include <QLoggingCategory>
6 6 #include <QWidget>
7 7
8 8 class DBCatalogue;
9 9 class DBEvent;
10 10 class DBEventProduct;
11 11 class VisualizationWidget;
12 class VisualizationSelectionZoneItem;
12 13
13 14 namespace Ui {
14 15 class CatalogueEventsWidget;
15 16 }
16 17
17 18 Q_DECLARE_LOGGING_CATEGORY(LOG_CatalogueEventsWidget)
18 19
19 20 class CatalogueEventsWidget : public QWidget {
20 21 Q_OBJECT
21 22
22 23 signals:
23 24 void eventsSelected(const QVector<std::shared_ptr<DBEvent> > &event);
25 void eventsRemoved(const QVector<std::shared_ptr<DBEvent> > &event);
24 26 void eventProductsSelected(
25 27 const QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > >
26 28 &eventproducts);
27 29 void selectionCleared();
30 void selectionZoneAdded(const std::shared_ptr<DBEvent> &event, const QString &productId,
31 VisualizationSelectionZoneItem *selectionZone);
28 32
29 33 public:
30 34 explicit CatalogueEventsWidget(QWidget *parent = 0);
31 35 virtual ~CatalogueEventsWidget();
32 36
33 37 void setVisualizationWidget(VisualizationWidget *visualization);
34 38
35 39 void addEvent(const std::shared_ptr<DBEvent> &event);
36 40 void setEventChanges(const std::shared_ptr<DBEvent> &event, bool hasChanges);
37 41
38 42 QVector<std::shared_ptr<DBCatalogue> > displayedCatalogues() const;
39 43 bool isAllEventsDisplayed() const;
40 44 bool isEventDisplayed(const std::shared_ptr<DBEvent> &event) const;
41 45
46 void refreshEvent(const std::shared_ptr<DBEvent> &event);
47
42 48 public slots:
43 49 void populateWithCatalogues(const QVector<std::shared_ptr<DBCatalogue> > &catalogues);
44 50 void populateWithAllEvents();
45 51 void clear();
46 52 void refresh();
47 53
48 54 private:
49 55 Ui::CatalogueEventsWidget *ui;
50 56
51 57 class CatalogueEventsWidgetPrivate;
52 58 spimpl::unique_impl_ptr<CatalogueEventsWidgetPrivate> impl;
53 59
54 60 private slots:
55 61 void emitSelection();
56 62 };
57 63
58 64 #endif // SCIQLOP_CATALOGUEEVENTSWIDGET_H
@@ -1,35 +1,43
1 1 #ifndef SCIQLOP_CATALOGUEEXPLORER_H
2 2 #define SCIQLOP_CATALOGUEEXPLORER_H
3 3
4 4 #include <Common/spimpl.h>
5 5 #include <QDialog>
6 6
7 7 namespace Ui {
8 8 class CatalogueExplorer;
9 9 }
10 10
11 11 class CatalogueEventsWidget;
12 12 class CatalogueSideBarWidget;
13 13
14 14 class VisualizationWidget;
15 class VisualizationSelectionZoneItem;
16
17 class DBEvent;
18
15 19
16 20 class CatalogueExplorer : public QDialog {
17 21 Q_OBJECT
18 22
19 23 public:
20 24 explicit CatalogueExplorer(QWidget *parent = 0);
21 25 virtual ~CatalogueExplorer();
22 26
23 27 void setVisualizationWidget(VisualizationWidget *visualization);
24 28
25 29 CatalogueEventsWidget &eventsWidget() const;
26 30 CatalogueSideBarWidget &sideBarWidget() const;
27 31
32 void clearSelectionZones();
33 void addSelectionZoneItem(const std::shared_ptr<DBEvent> &event, const QString &productId,
34 VisualizationSelectionZoneItem *selectionZone);
35
28 36 private:
29 37 Ui::CatalogueExplorer *ui;
30 38
31 39 class CatalogueExplorerPrivate;
32 40 spimpl::unique_impl_ptr<CatalogueExplorerPrivate> impl;
33 41 };
34 42
35 43 #endif // SCIQLOP_CATALOGUEEXPLORER_H
@@ -1,49 +1,51
1 1 #ifndef SCIQLOP_CATALOGUEINSPECTORWIDGET_H
2 2 #define SCIQLOP_CATALOGUEINSPECTORWIDGET_H
3 3
4 4 #include <Common/spimpl.h>
5 5 #include <QWidget>
6 6 #include <memory>
7 7
8 8 namespace Ui {
9 9 class CatalogueInspectorWidget;
10 10 }
11 11
12 12 class DBCatalogue;
13 13 class DBEvent;
14 14 class DBEventProduct;
15 15
16 16 class CatalogueInspectorWidget : public QWidget {
17 17 Q_OBJECT
18 18
19 19 signals:
20 20 void catalogueUpdated(const std::shared_ptr<DBCatalogue> &catalogue);
21 21 void eventUpdated(const std::shared_ptr<DBEvent> &event);
22 22 void eventProductUpdated(const std::shared_ptr<DBEvent> &event,
23 23 const std::shared_ptr<DBEventProduct> &eventProduct);
24 24
25 25 public:
26 26 explicit CatalogueInspectorWidget(QWidget *parent = 0);
27 27 virtual ~CatalogueInspectorWidget();
28 28
29 29 /// Enum matching the pages inside the stacked widget
30 30 enum class Page { Empty, CatalogueProperties, EventProperties };
31 31
32 32 Page currentPage() const;
33 33
34 34 void setEvent(const std::shared_ptr<DBEvent> &event);
35 35 void setEventProduct(const std::shared_ptr<DBEvent> &event,
36 36 const std::shared_ptr<DBEventProduct> &eventProduct);
37 37 void setCatalogue(const std::shared_ptr<DBCatalogue> &catalogue);
38 38
39 void refresh();
40
39 41 public slots:
40 42 void showPage(Page page);
41 43
42 44 private:
43 45 Ui::CatalogueInspectorWidget *ui;
44 46
45 47 class CatalogueInspectorWidgetPrivate;
46 48 spimpl::unique_impl_ptr<CatalogueInspectorWidgetPrivate> impl;
47 49 };
48 50
49 51 #endif // SCIQLOP_CATALOGUEINSPECTORWIDGET_H
@@ -1,37 +1,44
1 1 #ifndef SCIQLOP_AXISRENDERINGUTILS_H
2 2 #define SCIQLOP_AXISRENDERINGUTILS_H
3 3
4 4 #include <memory>
5 5
6 6 #include <QtCore/QLoggingCategory>
7 7 #include <QtCore/QString>
8 8
9 9 Q_DECLARE_LOGGING_CATEGORY(LOG_AxisRenderingUtils)
10 10
11 11 class IDataSeries;
12 12 class QCPAxis;
13 13 class QCustomPlot;
14 14 class SqpColorScale;
15 class Variable;
15 16
16 17 /// Formats a data value according to the axis on which it is present
17 18 QString formatValue(double value, const QCPAxis &axis);
18 19
19 20 /**
20 21 * Helper used to handle axes rendering
21 22 */
22 23 struct IAxisHelper {
23 24 virtual ~IAxisHelper() noexcept = default;
24 25
25 26 /// Set properties of the plot's axes and the color scale associated to plot passed as
26 27 /// parameters
27 28 /// @param plot the plot for which to set axe properties
28 29 /// @param colorScale the color scale for which to set properties
29 30 virtual void setProperties(QCustomPlot &plot, SqpColorScale &colorScale) = 0;
31
32 /// Set the units of the plot's axes and the color scale associated to plot passed as
33 /// parameters
34 /// @param plot the plot for which to set axe units
35 /// @param colorScale the color scale for which to set unit
36 virtual void setUnits(QCustomPlot &plot, SqpColorScale &colorScale) = 0;
30 37 };
31 38
32 39 struct IAxisHelperFactory {
33 /// Creates IAxisHelper according to a data series
34 static std::unique_ptr<IAxisHelper> create(std::shared_ptr<IDataSeries> dataSeries) noexcept;
40 /// Creates IPlottablesHelper according to the type of data series a variable holds
41 static std::unique_ptr<IAxisHelper> create(const Variable &variable) noexcept;
35 42 };
36 43
37 44 #endif // SCIQLOP_AXISRENDERINGUTILS_H
@@ -1,33 +1,34
1 1 #ifndef SCIQLOP_PLOTTABLESRENDERINGUTILS_H
2 2 #define SCIQLOP_PLOTTABLESRENDERINGUTILS_H
3 3
4 #include <Data/DataSeriesType.h>
5
4 6 #include <Visualization/VisualizationDefs.h>
5 7
6 8 #include <memory>
7 9
8 10 #include <QtCore/QLoggingCategory>
9 11
10 12 Q_DECLARE_LOGGING_CATEGORY(LOG_PlottablesRenderingUtils)
11 13
12 class IDataSeries;
13 14 class QCPColorScale;
14 15 class QCustomPlot;
16 class Variable;
15 17
16 18 /**
17 19 * Helper used to handle plottables rendering
18 20 */
19 21 struct IPlottablesHelper {
20 22 virtual ~IPlottablesHelper() noexcept = default;
21 23
22 24 /// Set properties of the plottables passed as parameter
23 25 /// @param plottables the plottables for which to set properties
24 26 virtual void setProperties(PlottablesMap &plottables) = 0;
25 27 };
26 28
27 29 struct IPlottablesHelperFactory {
28 /// Creates IPlottablesHelper according to a data series
29 static std::unique_ptr<IPlottablesHelper>
30 create(std::shared_ptr<IDataSeries> dataSeries) noexcept;
30 /// Creates IPlottablesHelper according to the type of data series a variable holds
31 static std::unique_ptr<IPlottablesHelper> create(const Variable &variable) noexcept;
31 32 };
32 33
33 34 #endif // SCIQLOP_PLOTTABLESRENDERINGUTILS_H
@@ -1,41 +1,41
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHHELPER_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHHELPER_H
3 3
4 4 #include "Visualization/VisualizationDefs.h"
5 5
6 6 #include <Data/SqpRange.h>
7 7
8 8 #include <QLoggingCategory>
9 9 #include <QVector>
10 10
11 11 #include <memory>
12 12
13 13 Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationGraphHelper)
14 14
15 15 class IDataSeries;
16 16 class QCPAbstractPlottable;
17 17 class QCustomPlot;
18 18 class Variable;
19 19
20 20 /**
21 21 * @brief The VisualizationGraphHelper class aims to create the QCustomPlot components relative to a
22 22 * variable, depending on the data series of this variable
23 23 */
24 24 struct VisualizationGraphHelper {
25 25 /**
26 26 * Creates (if possible) the QCustomPlot components relative to the variable passed in
27 27 * parameter, and adds these to the plot passed in parameter.
28 28 * @param variable the variable for which to create the components
29 29 * @param plot the plot in which to add the created components. It takes ownership of these
30 30 * components.
31 31 * @return the list of the components created
32 32 */
33 33 static PlottablesMap create(std::shared_ptr<Variable> variable, QCustomPlot &plot) noexcept;
34 34
35 static void updateData(PlottablesMap &plottables, std::shared_ptr<IDataSeries> dataSeries,
35 static void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
36 36 const SqpRange &dateTime);
37 37
38 38 static void setYAxisRange(std::shared_ptr<Variable> variable, QCustomPlot &plot) noexcept;
39 39 };
40 40
41 41 #endif // SCIQLOP_VISUALIZATIONGRAPHHELPER_H
@@ -1,42 +1,44
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
3 3
4 4 #include <Common/spimpl.h>
5 5
6 6 #include <Visualization/VisualizationDefs.h>
7 7
8 8 class IDataSeries;
9 9 class QCustomPlot;
10 10 class QMouseEvent;
11 11 class Unit;
12 class Variable;
12 13 class VisualizationGraphWidget;
13 14
14 15 class VisualizationGraphRenderingDelegate {
15 16 public:
16 17 /// Ctor
17 18 /// @param graphWidget the graph widget to which the delegate is associated
18 19 /// @remarks the graph widget must exist throughout the life cycle of the delegate
19 20 explicit VisualizationGraphRenderingDelegate(VisualizationGraphWidget &graphWidget);
20 21
21 22 void onMouseDoubleClick(QMouseEvent *event) noexcept;
22 23 void onMouseMove(QMouseEvent *event) noexcept;
23 24 /// Updates rendering when data of plot changed
24 25 void onPlotUpdated() noexcept;
25 26
26 /// Sets properties of the plot's axes from the data series passed as parameter
27 void setAxesProperties(std::shared_ptr<IDataSeries> dataSeries) noexcept;
27 /// Sets units of the plot's axes according to the properties of the variable passed as
28 /// parameter
29 void setAxesUnits(const Variable &variable) noexcept;
28 30
29 /// Sets rendering properties of the plottables passed as parameter, from the data series that
31 /// Sets graph properties of the plottables passed as parameter, from the variable that
30 32 /// generated these
31 void setPlottablesProperties(std::shared_ptr<IDataSeries> dataSeries,
32 PlottablesMap &plottables) noexcept;
33 void setGraphProperties(const Variable &variable, PlottablesMap &plottables) noexcept;
34
33 35
34 36 /// Shows or hides graph overlay (name, close button, etc.)
35 37 void showGraphOverlay(bool show) noexcept;
36 38
37 39 private:
38 40 class VisualizationGraphRenderingDelegatePrivate;
39 41 spimpl::unique_impl_ptr<VisualizationGraphRenderingDelegatePrivate> impl;
40 42 };
41 43
42 44 #endif // SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
@@ -1,155 +1,158
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
3 3
4 4 #include "Visualization/IVisualizationWidget.h"
5 5 #include "Visualization/VisualizationDragWidget.h"
6 6
7 7 #include <QLoggingCategory>
8 8 #include <QWidget>
9 9
10 10 #include <memory>
11 11
12 12 #include <Common/spimpl.h>
13 13
14 14 Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationGraphWidget)
15 15
16 16 class QCPRange;
17 17 class QCustomPlot;
18 18 class SqpRange;
19 19 class Variable;
20 20 class VisualizationWidget;
21 21 class VisualizationZoneWidget;
22 22 class VisualizationSelectionZoneItem;
23 23
24 24 namespace Ui {
25 25 class VisualizationGraphWidget;
26 26 } // namespace Ui
27 27
28 28 /// Defines options that can be associated with the graph
29 29 enum GraphFlag {
30 30 DisableAll = 0x0, ///< Disables acquisition and synchronization
31 31 EnableAcquisition = 0x1, ///< When this flag is set, the change of the graph's range leads to
32 32 /// the acquisition of data
33 33 EnableSynchronization = 0x2, ///< When this flag is set, the change of the graph's range causes
34 34 /// the call to the synchronization of the graphs contained in the
35 35 /// same zone of this graph
36 36 EnableAll = ~DisableAll ///< Enables acquisition and synchronization
37 37 };
38 38
39 39 Q_DECLARE_FLAGS(GraphFlags, GraphFlag)
40 40 Q_DECLARE_OPERATORS_FOR_FLAGS(GraphFlags)
41 41
42 42 class VisualizationGraphWidget : public VisualizationDragWidget, public IVisualizationWidget {
43 43 Q_OBJECT
44 44
45 45 friend class QCustomPlotSynchronizer;
46 46 friend class VisualizationGraphRenderingDelegate;
47 47
48 48 public:
49 49 explicit VisualizationGraphWidget(const QString &name = {}, QWidget *parent = 0);
50 50 virtual ~VisualizationGraphWidget();
51 51
52 52 /// Returns the VisualizationZoneWidget which contains the graph or nullptr
53 53 VisualizationZoneWidget *parentZoneWidget() const noexcept;
54 54
55 55 /// Returns the main VisualizationWidget which contains the graph or nullptr
56 56 VisualizationWidget *parentVisualizationWidget() const;
57 57
58 58 /// Sets graph options
59 59 void setFlags(GraphFlags flags);
60 60
61 61 void addVariable(std::shared_ptr<Variable> variable, SqpRange range);
62 62
63 63 /// Removes a variable from the graph
64 64 void removeVariable(std::shared_ptr<Variable> variable) noexcept;
65 65
66 66 /// Returns the list of all variables used in the graph
67 67 QList<std::shared_ptr<Variable> > variables() const;
68 68
69 69 /// Sets the y-axis range based on the data of a variable
70 70 void setYRange(std::shared_ptr<Variable> variable);
71 71 SqpRange graphRange() const noexcept;
72 void setGraphRange(const SqpRange &range);
72 void setGraphRange(const SqpRange &range, bool calibration = false);
73 void setAutoRangeOnVariableInitialization(bool value);
73 74
74 75 // Zones
75 76 /// Returns the ranges of all the selection zones on the graph
76 77 QVector<SqpRange> selectionZoneRanges() const;
77 78 /// Adds new selection zones in the graph
78 79 void addSelectionZones(const QVector<SqpRange> &ranges);
80 /// Adds a new selection zone in the graph
81 VisualizationSelectionZoneItem *addSelectionZone(const QString &name, const SqpRange &range);
79 82 /// Removes the specified selection zone
80 83 void removeSelectionZone(VisualizationSelectionZoneItem *selectionZone);
81 84
82 85 /// Undo the last zoom done with a zoom box
83 86 void undoZoom();
84 87
85 88 // IVisualizationWidget interface
86 89 void accept(IVisualizationWidgetVisitor *visitor) override;
87 90 bool canDrop(const Variable &variable) const override;
88 91 bool contains(const Variable &variable) const override;
89 92 QString name() const override;
90 93
91 94 // VisualisationDragWidget
92 95 QMimeData *mimeData(const QPoint &position) const override;
93 96 QPixmap customDragPixmap(const QPoint &dragPosition) override;
94 97 bool isDragAllowed() const override;
95 98 void highlightForMerge(bool highlighted) override;
96 99
97 100 // Cursors
98 101 /// Adds or moves the vertical cursor at the specified value on the x-axis
99 102 void addVerticalCursor(double time);
100 103 /// Adds or moves the vertical cursor at the specified value on the x-axis
101 104 void addVerticalCursorAtViewportPosition(double position);
102 105 void removeVerticalCursor();
103 106 /// Adds or moves the vertical cursor at the specified value on the y-axis
104 107 void addHorizontalCursor(double value);
105 108 /// Adds or moves the vertical cursor at the specified value on the y-axis
106 109 void addHorizontalCursorAtViewportPosition(double position);
107 110 void removeHorizontalCursor();
108 111
109 112 signals:
110 113 void synchronize(const SqpRange &range, const SqpRange &oldRange);
111 114 void requestDataLoading(QVector<std::shared_ptr<Variable> > variable, const SqpRange &range,
112 115 bool synchronise);
113 116
114 117 /// Signal emitted when the variable is about to be removed from the graph
115 118 void variableAboutToBeRemoved(std::shared_ptr<Variable> var);
116 119 /// Signal emitted when the variable has been added to the graph
117 120 void variableAdded(std::shared_ptr<Variable> var);
118 121
119 122 protected:
120 123 void closeEvent(QCloseEvent *event) override;
121 124 void enterEvent(QEvent *event) override;
122 125 void leaveEvent(QEvent *event) override;
123 126
124 127 QCustomPlot &plot() const noexcept;
125 128
126 129 private:
127 130 Ui::VisualizationGraphWidget *ui;
128 131
129 132 class VisualizationGraphWidgetPrivate;
130 133 spimpl::unique_impl_ptr<VisualizationGraphWidgetPrivate> impl;
131 134
132 135 private slots:
133 136 /// Slot called when right clicking on the graph (displays a menu)
134 137 void onGraphMenuRequested(const QPoint &pos) noexcept;
135 138
136 139 /// Rescale the X axe to range parameter
137 140 void onRangeChanged(const QCPRange &t1, const QCPRange &t2);
138 141
139 142 /// Slot called when a mouse double click was made
140 143 void onMouseDoubleClick(QMouseEvent *event) noexcept;
141 144 /// Slot called when a mouse move was made
142 145 void onMouseMove(QMouseEvent *event) noexcept;
143 146 /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done
144 147 void onMouseWheel(QWheelEvent *event) noexcept;
145 148 /// Slot called when a mouse press was made, to activate the calibration of a graph
146 149 void onMousePress(QMouseEvent *event) noexcept;
147 150 /// Slot called when a mouse release was made, to deactivate the calibration of a graph
148 151 void onMouseRelease(QMouseEvent *event) noexcept;
149 152
150 153 void onDataCacheVariableUpdated();
151 154
152 155 void onUpdateVarDisplaying(std::shared_ptr<Variable> variable, const SqpRange &range);
153 156 };
154 157
155 158 #endif // SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
@@ -1,68 +1,73
1 1 #ifndef SCIQLOP_VISUALIZATIONSELECTIONZONEITEM_H
2 2 #define SCIQLOP_VISUALIZATIONSELECTIONZONEITEM_H
3 3
4 4 #include <Common/spimpl.h>
5 5 #include <Data/SqpRange.h>
6 6 #include <Visualization/qcustomplot.h>
7 7
8 8 class VisualizationGraphWidget;
9 9
10 10 class VisualizationSelectionZoneItem : public QCPItemRect {
11 Q_OBJECT
12
13 signals:
14 /// Signal emitted when the zone range is edited manually
15 void rangeEdited(const SqpRange &range);
11 16
12 17 public:
13 18 VisualizationSelectionZoneItem(QCustomPlot *plot);
14 19 virtual ~VisualizationSelectionZoneItem();
15 20
16 21 VisualizationGraphWidget *parentGraphWidget() const noexcept;
17 22
18 23 void setName(const QString &name);
19 24 QString name() const;
20 25
21 26 SqpRange range() const;
22 27 void setRange(double tstart, double tend);
23 28 void setStart(double tstart);
24 29 void setEnd(double tend);
25 30
26 31 void setColor(const QColor &color);
27 32
28 33 void setEditionEnabled(bool value);
29 34 bool isEditionEnabled() const;
30 35
31 36 /// Moves the item at the top of its QCPLayer. It will then receive the mouse events if multiple
32 37 /// items are stacked on top of each others.
33 38 void moveToTop();
34 39
35 40 Qt::CursorShape curshorShapeForPosition(const QPoint &position) const;
36 41 void setHovered(bool value);
37 42
38 43 /// Sets the zones which should be moved or reisized together with this zone
39 44 void setAssociatedEditedZones(const QVector<VisualizationSelectionZoneItem *> &associatedZones);
40 45
41 46 /// Align the specified zones with this one, vertically with the left border
42 47 bool alignZonesVerticallyOnLeft(const QVector<VisualizationSelectionZoneItem *> &zonesToAlign,
43 48 bool allowResize);
44 49 /// Align the specified zones with this one, vertically with the right border
45 50 bool alignZonesVerticallyOnRight(const QVector<VisualizationSelectionZoneItem *> &zonesToAlign,
46 51 bool allowResize);
47 52 /// Align the specified zones with this one, temporally with the left border
48 53 bool alignZonesTemporallyOnLeft(const QVector<VisualizationSelectionZoneItem *> &zonesToAlign,
49 54 bool allowResize);
50 55 /// Align the specified zones with this one, temporally with the right border
51 56 bool alignZonesTemporallyOnRight(const QVector<VisualizationSelectionZoneItem *> &zonesToAlign,
52 57 bool allowResize);
53 58
54 59 protected:
55 60 void mousePressEvent(QMouseEvent *event, const QVariant &details) override;
56 61 void mouseMoveEvent(QMouseEvent *event, const QPointF &startPos) override;
57 62 void mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos) override;
58 63
59 64 void resizeLeft(double pixelDiff);
60 65 void resizeRight(double pixelDiff);
61 66 void move(double pixelDiff);
62 67
63 68 private:
64 69 class VisualizationSelectionZoneItemPrivate;
65 70 spimpl::unique_impl_ptr<VisualizationSelectionZoneItemPrivate> impl;
66 71 };
67 72
68 73 #endif // SCIQLOP_VISUALIZATIONSELECTIONZONEITEM_H
@@ -1,147 +1,148
1 1 qxorm_dep = dependency('QxOrm', required : true, fallback:['QxOrm','qxorm_dep'])
2 2 catalogueapi_dep = dependency('CatalogueAPI', required : true, fallback:['CatalogueAPI','CatalogueAPI_dep'])
3 3
4 4 gui_moc_headers = [
5 5 'include/DataSource/DataSourceWidget.h',
6 6 'include/Settings/SqpSettingsDialog.h',
7 7 'include/Settings/SqpSettingsGeneralWidget.h',
8 8 'include/SidePane/SqpSidePane.h',
9 9 'include/SqpApplication.h',
10 10 'include/DragAndDrop/DragDropScroller.h',
11 11 'include/DragAndDrop/DragDropTabSwitcher.h',
12 12 'include/TimeWidget/TimeWidget.h',
13 13 'include/Variable/VariableInspectorWidget.h',
14 14 'include/Variable/RenameVariableDialog.h',
15 15 'include/Visualization/qcustomplot.h',
16 16 'include/Visualization/VisualizationGraphWidget.h',
17 17 'include/Visualization/VisualizationTabWidget.h',
18 18 'include/Visualization/VisualizationWidget.h',
19 19 'include/Visualization/VisualizationZoneWidget.h',
20 20 'include/Visualization/VisualizationDragDropContainer.h',
21 21 'include/Visualization/VisualizationDragWidget.h',
22 22 'include/Visualization/ColorScaleEditor.h',
23 'include/Visualization/VisualizationSelectionZoneItem.h',
23 24 'include/Actions/SelectionZoneAction.h',
24 25 'include/Visualization/VisualizationMultiZoneSelectionDialog.h',
25 26 'include/Catalogue/CatalogueExplorer.h',
26 27 'include/Catalogue/CatalogueEventsWidget.h',
27 28 'include/Catalogue/CatalogueSideBarWidget.h',
28 29 'include/Catalogue/CatalogueInspectorWidget.h',
29 30 'include/Catalogue/CatalogueEventsModel.h',
30 31 'include/Catalogue/CreateEventDialog.h',
31 32 'include/Catalogue/CatalogueTreeModel.h'
32 33 ]
33 34
34 35 gui_ui_files = [
35 36 'ui/DataSource/DataSourceWidget.ui',
36 37 'ui/Settings/SqpSettingsDialog.ui',
37 38 'ui/Settings/SqpSettingsGeneralWidget.ui',
38 39 'ui/SidePane/SqpSidePane.ui',
39 40 'ui/TimeWidget/TimeWidget.ui',
40 41 'ui/Variable/VariableInspectorWidget.ui',
41 42 'ui/Variable/RenameVariableDialog.ui',
42 43 'ui/Variable/VariableMenuHeaderWidget.ui',
43 44 'ui/Visualization/VisualizationGraphWidget.ui',
44 45 'ui/Visualization/VisualizationTabWidget.ui',
45 46 'ui/Visualization/VisualizationWidget.ui',
46 47 'ui/Visualization/VisualizationZoneWidget.ui',
47 48 'ui/Visualization/ColorScaleEditor.ui',
48 49 'ui/Visualization/VisualizationMultiZoneSelectionDialog.ui',
49 50 'ui/Catalogue/CatalogueExplorer.ui',
50 51 'ui/Catalogue/CatalogueEventsWidget.ui',
51 52 'ui/Catalogue/CatalogueSideBarWidget.ui',
52 53 'ui/Catalogue/CatalogueInspectorWidget.ui',
53 54 'ui/Catalogue/CreateEventDialog.ui'
54 55 ]
55 56
56 57 gui_qresources = ['resources/sqpguiresources.qrc']
57 58
58 59 rcc_gen = generator(rcc,
59 60 output : 'qrc_@BASENAME@.cpp',
60 61 arguments : [
61 62 '--output',
62 63 '@OUTPUT@',
63 64 '@INPUT@',
64 65 '@EXTRA_ARGS@'])
65 66
66 67 rcc_files = rcc_gen.process(gui_qresources, extra_args : ['-name', 'sqpguiresources'])
67 68
68 69 gui_moc_files = qt5.preprocess(moc_headers : gui_moc_headers,
69 70 ui_files : gui_ui_files)
70 71
71 72 gui_sources = [
72 73 'src/SqpApplication.cpp',
73 74 'src/DragAndDrop/DragDropGuiController.cpp',
74 75 'src/DragAndDrop/DragDropScroller.cpp',
75 76 'src/DragAndDrop/DragDropTabSwitcher.cpp',
76 77 'src/Common/ColorUtils.cpp',
77 78 'src/Common/VisualizationDef.cpp',
78 79 'src/DataSource/DataSourceTreeWidgetItem.cpp',
79 80 'src/DataSource/DataSourceTreeWidgetHelper.cpp',
80 81 'src/DataSource/DataSourceWidget.cpp',
81 82 'src/DataSource/DataSourceTreeWidget.cpp',
82 83 'src/Settings/SqpSettingsDialog.cpp',
83 84 'src/Settings/SqpSettingsGeneralWidget.cpp',
84 85 'src/SidePane/SqpSidePane.cpp',
85 86 'src/TimeWidget/TimeWidget.cpp',
86 87 'src/Variable/VariableInspectorWidget.cpp',
87 88 'src/Variable/VariableInspectorTableView.cpp',
88 89 'src/Variable/VariableMenuHeaderWidget.cpp',
89 90 'src/Variable/RenameVariableDialog.cpp',
90 91 'src/Visualization/VisualizationGraphHelper.cpp',
91 92 'src/Visualization/VisualizationGraphRenderingDelegate.cpp',
92 93 'src/Visualization/VisualizationGraphWidget.cpp',
93 94 'src/Visualization/VisualizationTabWidget.cpp',
94 95 'src/Visualization/VisualizationWidget.cpp',
95 96 'src/Visualization/VisualizationZoneWidget.cpp',
96 97 'src/Visualization/qcustomplot.cpp',
97 98 'src/Visualization/QCustomPlotSynchronizer.cpp',
98 99 'src/Visualization/operations/FindVariableOperation.cpp',
99 100 'src/Visualization/operations/GenerateVariableMenuOperation.cpp',
100 101 'src/Visualization/operations/MenuBuilder.cpp',
101 102 'src/Visualization/operations/RemoveVariableOperation.cpp',
102 103 'src/Visualization/operations/RescaleAxeOperation.cpp',
103 104 'src/Visualization/VisualizationDragDropContainer.cpp',
104 105 'src/Visualization/VisualizationDragWidget.cpp',
105 106 'src/Visualization/AxisRenderingUtils.cpp',
106 107 'src/Visualization/PlottablesRenderingUtils.cpp',
107 108 'src/Visualization/MacScrollBarStyle.cpp',
108 109 'src/Visualization/VisualizationCursorItem.cpp',
109 110 'src/Visualization/ColorScaleEditor.cpp',
110 111 'src/Visualization/SqpColorScale.cpp',
111 112 'src/Visualization/QCPColorMapIterator.cpp',
112 113 'src/Visualization/VisualizationSelectionZoneItem.cpp',
113 114 'src/Visualization/VisualizationSelectionZoneManager.cpp',
114 115 'src/Actions/SelectionZoneAction.cpp',
115 116 'src/Actions/ActionsGuiController.cpp',
116 117 'src/Visualization/VisualizationActionManager.cpp',
117 118 'src/Visualization/VisualizationMultiZoneSelectionDialog.cpp',
118 119 'src/Catalogue/CatalogueExplorer.cpp',
119 120 'src/Catalogue/CatalogueEventsWidget.cpp',
120 121 'src/Catalogue/CatalogueSideBarWidget.cpp',
121 122 'src/Catalogue/CatalogueInspectorWidget.cpp',
122 123 'src/Catalogue/CatalogueTreeItems/CatalogueAbstractTreeItem.cpp',
123 124 'src/Catalogue/CatalogueTreeItems/CatalogueTreeItem.cpp',
124 125 'src/Catalogue/CatalogueTreeItems/CatalogueTextTreeItem.cpp',
125 126 'src/Catalogue/CatalogueEventsModel.cpp',
126 127 'src/Catalogue/CatalogueExplorerHelper.cpp',
127 128 'src/Catalogue/CatalogueActionManager.cpp',
128 129 'src/Catalogue/CreateEventDialog.cpp',
129 130 'src/Catalogue/CatalogueTreeModel.cpp'
130 131 ]
131 132
132 133 gui_inc = include_directories(['include'])
133 134
134 135 sciqlop_gui_lib = library('sciqlopgui',
135 136 gui_sources,
136 137 gui_moc_files,
137 138 rcc_files,
138 139 include_directories : [gui_inc],
139 140 dependencies : [ qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core, catalogueapi_dep],
140 141 install : true
141 142 )
142 143
143 144 sciqlop_gui = declare_dependency(link_with : sciqlop_gui_lib,
144 145 include_directories : gui_inc,
145 146 dependencies : [qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core, catalogueapi_dep])
146 147
147 148
@@ -1,143 +1,147
1 1 #include "Catalogue/CatalogueActionManager.h"
2 2
3 3 #include <Actions/ActionsGuiController.h>
4 4 #include <Catalogue/CatalogueController.h>
5 5 #include <DataSource/DataSourceItem.h>
6 6 #include <SqpApplication.h>
7 7 #include <Variable/Variable.h>
8 8 #include <Visualization/VisualizationGraphWidget.h>
9 9 #include <Visualization/VisualizationSelectionZoneItem.h>
10 10
11 11 #include <Catalogue/CatalogueEventsWidget.h>
12 12 #include <Catalogue/CatalogueExplorer.h>
13 13 #include <Catalogue/CatalogueSideBarWidget.h>
14 14 #include <Catalogue/CreateEventDialog.h>
15 15
16 16 #include <CatalogueDao.h>
17 17 #include <DBCatalogue.h>
18 18 #include <DBEvent.h>
19 19 #include <DBEventProduct.h>
20 20
21 21 #include <QBoxLayout>
22 22 #include <QComboBox>
23 23 #include <QDialog>
24 24 #include <QDialogButtonBox>
25 25 #include <QLineEdit>
26 26 #include <memory>
27 27
28 28 struct CatalogueActionManager::CatalogueActionManagerPrivate {
29 29
30 30 CatalogueExplorer *m_CatalogueExplorer = nullptr;
31 31
32 32 CatalogueActionManagerPrivate(CatalogueExplorer *catalogueExplorer)
33 33 : m_CatalogueExplorer(catalogueExplorer)
34 34 {
35 35 }
36 36
37 37 void createEventFromZones(const QString &eventName,
38 38 const QVector<VisualizationSelectionZoneItem *> &zones,
39 39 const std::shared_ptr<DBCatalogue> &catalogue = nullptr)
40 40 {
41 41 auto event = std::make_shared<DBEvent>();
42 42 event->setName(eventName);
43 43
44 44 std::list<DBEventProduct> productList;
45 45 for (auto zone : zones) {
46 46 auto graph = zone->parentGraphWidget();
47 47 for (auto var : graph->variables()) {
48 48 auto eventProduct = std::make_shared<DBEventProduct>();
49 49 eventProduct->setEvent(*event);
50 50
51 auto productId
52 = var->metadata().value(DataSourceItem::ID_DATA_KEY, "UnknownID").toString();
53
51 54 auto zoneRange = zone->range();
52 55 eventProduct->setTStart(zoneRange.m_TStart);
53 56 eventProduct->setTEnd(zoneRange.m_TEnd);
54 57
55 eventProduct->setProductId(
56 var->metadata().value(DataSourceItem::ID_DATA_KEY, "UnknownID").toString());
58 eventProduct->setProductId(productId);
57 59
58 60 productList.push_back(*eventProduct);
61
62 m_CatalogueExplorer->addSelectionZoneItem(event, productId, zone);
59 63 }
60 64 }
61 65
62 66 event->setEventProducts(productList);
63 67
64 68 sqpApp->catalogueController().addEvent(event);
65 69
66 70
67 71 if (catalogue) {
68 72 // TODO
69 73 // catalogue->addEvent(event);
70 74 m_CatalogueExplorer->sideBarWidget().setCatalogueChanges(catalogue, true);
71 75 if (m_CatalogueExplorer->eventsWidget().displayedCatalogues().contains(catalogue)) {
72 76 m_CatalogueExplorer->eventsWidget().addEvent(event);
73 77 m_CatalogueExplorer->eventsWidget().setEventChanges(event, true);
74 78 }
75 79 }
76 80 else if (m_CatalogueExplorer->eventsWidget().isAllEventsDisplayed()) {
77 81 m_CatalogueExplorer->eventsWidget().addEvent(event);
78 82 m_CatalogueExplorer->eventsWidget().setEventChanges(event, true);
79 83 }
80 84 }
81 85 };
82 86
83 87 CatalogueActionManager::CatalogueActionManager(CatalogueExplorer *catalogueExplorer)
84 88 : impl{spimpl::make_unique_impl<CatalogueActionManagerPrivate>(catalogueExplorer)}
85 89 {
86 90 }
87 91
88 92 void CatalogueActionManager::installSelectionZoneActions()
89 93 {
90 94 auto &actionController = sqpApp->actionsGuiController();
91 95
92 96 auto createEventEnableFuntion = [](auto zones) {
93 97
94 98 // Checks that all variables in the zones doesn't refer to the same product
95 99 QSet<QString> usedDatasource;
96 100 for (auto zone : zones) {
97 101 auto graph = zone->parentGraphWidget();
98 102 auto variables = graph->variables();
99 103
100 104 for (auto var : variables) {
101 105 auto datasourceId = var->metadata().value(DataSourceItem::ID_DATA_KEY).toString();
102 106 if (!usedDatasource.contains(datasourceId)) {
103 107 usedDatasource.insert(datasourceId);
104 108 }
105 109 else {
106 110 return false;
107 111 }
108 112 }
109 113 }
110 114
111 115 return true;
112 116 };
113 117
114 118 auto createEventAction = actionController.addSectionZoneAction(
115 119 {QObject::tr("Catalogues")}, QObject::tr("New Event..."), [this](auto zones) {
116 120 CreateEventDialog dialog(
117 121 impl->m_CatalogueExplorer->sideBarWidget().getCatalogues(REPOSITORY_DEFAULT));
118 122 dialog.hideCatalogueChoice();
119 123 if (dialog.exec() == QDialog::Accepted) {
120 124 impl->createEventFromZones(dialog.eventName(), zones);
121 125 }
122 126 });
123 127 createEventAction->setEnableFunction(createEventEnableFuntion);
124 128
125 129 auto createEventInCatalogueAction = actionController.addSectionZoneAction(
126 130 {QObject::tr("Catalogues")}, QObject::tr("New Event in Catalogue..."), [this](auto zones) {
127 131 CreateEventDialog dialog(
128 132 impl->m_CatalogueExplorer->sideBarWidget().getCatalogues(REPOSITORY_DEFAULT));
129 133 if (dialog.exec() == QDialog::Accepted) {
130 134 auto selectedCatalogue = dialog.selectedCatalogue();
131 135 if (!selectedCatalogue) {
132 136 selectedCatalogue = std::make_shared<DBCatalogue>();
133 137 selectedCatalogue->setName(dialog.catalogueName());
134 138 // sqpApp->catalogueController().addCatalogue(selectedCatalogue); TODO
135 139 impl->m_CatalogueExplorer->sideBarWidget().addCatalogue(selectedCatalogue,
136 140 REPOSITORY_DEFAULT);
137 141 }
138 142
139 143 impl->createEventFromZones(dialog.eventName(), zones, selectedCatalogue);
140 144 }
141 145 });
142 146 createEventInCatalogueAction->setEnableFunction(createEventEnableFuntion);
143 147 }
@@ -1,470 +1,590
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 #include <DBEventProduct.h>
10 #include <DataSource/DataSourceController.h>
11 #include <DataSource/DataSourceItem.h>
9 12 #include <SqpApplication.h>
13 #include <Variable/Variable.h>
14 #include <Variable/VariableController.h>
15 #include <Visualization/VisualizationGraphWidget.h>
10 16 #include <Visualization/VisualizationTabWidget.h>
11 17 #include <Visualization/VisualizationWidget.h>
12 18 #include <Visualization/VisualizationZoneWidget.h>
13 19
14 20 #include <QDialog>
15 21 #include <QDialogButtonBox>
16 22 #include <QListWidget>
17 23 #include <QMessageBox>
18 24
19 25 Q_LOGGING_CATEGORY(LOG_CatalogueEventsWidget, "CatalogueEventsWidget")
20 26
21 27 /// Fixed size of the validation column
22 28 const auto VALIDATION_COLUMN_SIZE = 35;
23 29
30 /// Percentage added to the range of a event when it is displayed
31 const auto EVENT_RANGE_MARGE = 30; // in %
32
24 33 struct CatalogueEventsWidget::CatalogueEventsWidgetPrivate {
25 34
26 35 CatalogueEventsModel *m_Model = nullptr;
27 36 QStringList m_ZonesForTimeMode;
28 37 QString m_ZoneForGraphMode;
29 38 QVector<std::shared_ptr<DBCatalogue> > m_DisplayedCatalogues;
30 39 bool m_AllEventDisplayed = false;
40 QVector<VisualizationGraphWidget *> m_CustomGraphs;
31 41
32 42 VisualizationWidget *m_VisualizationWidget = nullptr;
33 43
34 44 void setEvents(const QVector<std::shared_ptr<DBEvent> > &events, CatalogueEventsWidget *widget)
35 45 {
36 46 widget->ui->treeView->setSortingEnabled(false);
37 47 m_Model->setEvents(events);
38 48 widget->ui->treeView->setSortingEnabled(true);
39 49
40 50 for (auto event : events) {
41 51 if (sqpApp->catalogueController().eventHasChanges(event)) {
42 52 auto index = m_Model->indexOf(event);
43 53 widget->setEventChanges(event, true);
44 54 }
45 55 }
46 56 }
47 57
48 58 void addEvent(const std::shared_ptr<DBEvent> &event, QTreeView *treeView)
49 59 {
50 60 treeView->setSortingEnabled(false);
51 61 m_Model->addEvent(event);
52 62 treeView->setSortingEnabled(true);
53 63 }
54 64
55 65 void removeEvent(const std::shared_ptr<DBEvent> &event, QTreeView *treeView)
56 66 {
57 67 treeView->setSortingEnabled(false);
58 68 m_Model->removeEvent(event);
59 69 treeView->setSortingEnabled(true);
60 70 }
61 71
62 72 QStringList getAvailableVisualizationZoneList() const
63 73 {
64 74 if (m_VisualizationWidget) {
65 75 if (auto tab = m_VisualizationWidget->currentTabWidget()) {
66 76 return tab->availableZoneWidgets();
67 77 }
68 78 }
69 79
70 80 return QStringList{};
71 81 }
72 82
73 83 QStringList selectZone(QWidget *parent, const QStringList &selectedZones,
74 84 bool allowMultiSelection, const QPoint &location)
75 85 {
76 86 auto availableZones = getAvailableVisualizationZoneList();
77 87 if (availableZones.isEmpty()) {
78 88 return QStringList{};
79 89 }
80 90
81 91 QDialog d(parent, Qt::Tool);
82 92 d.setWindowTitle("Choose a zone");
83 93 auto layout = new QVBoxLayout{&d};
84 94 layout->setContentsMargins(0, 0, 0, 0);
85 95 auto listWidget = new QListWidget{&d};
86 96 layout->addWidget(listWidget);
87 97
88 98 QSet<QListWidgetItem *> checkedItems;
89 99 for (auto zone : availableZones) {
90 100 auto item = new QListWidgetItem{zone};
91 101 item->setFlags(Qt::ItemIsEnabled | Qt::ItemIsUserCheckable);
92 102 if (selectedZones.contains(zone)) {
93 103 item->setCheckState(Qt::Checked);
94 104 checkedItems << item;
95 105 }
96 106 else {
97 107 item->setCheckState(Qt::Unchecked);
98 108 }
99 109
100 110 listWidget->addItem(item);
101 111 }
102 112
103 113 auto buttonBox = new QDialogButtonBox{QDialogButtonBox::Ok, &d};
104 114 layout->addWidget(buttonBox);
105 115
106 116 QObject::connect(buttonBox, &QDialogButtonBox::accepted, &d, &QDialog::accept);
107 117 QObject::connect(buttonBox, &QDialogButtonBox::rejected, &d, &QDialog::reject);
108 118
109 119 QObject::connect(listWidget, &QListWidget::itemChanged,
110 120 [&checkedItems, allowMultiSelection, listWidget](auto item) {
111 121 if (item->checkState() == Qt::Checked) {
112 122 if (!allowMultiSelection) {
113 123 for (auto checkedItem : checkedItems) {
114 124 listWidget->blockSignals(true);
115 125 checkedItem->setCheckState(Qt::Unchecked);
116 126 listWidget->blockSignals(false);
117 127 }
118 128
119 129 checkedItems.clear();
120 130 }
121 131 checkedItems << item;
122 132 }
123 133 else {
124 134 checkedItems.remove(item);
125 135 }
126 136 });
127 137
128 138 QStringList result;
129 139
130 140 d.setMinimumWidth(120);
131 141 d.resize(d.minimumSizeHint());
132 142 d.move(location);
133 143 if (d.exec() == QDialog::Accepted) {
134 144 for (auto item : checkedItems) {
135 145 result += item->text();
136 146 }
137 147 }
138 148 else {
139 149 result = selectedZones;
140 150 }
141 151
142 152 return result;
143 153 }
144 154
145 155 void updateForTimeMode(QTreeView *treeView)
146 156 {
147 157 auto selectedRows = treeView->selectionModel()->selectedRows();
148 158
149 159 if (selectedRows.count() == 1) {
150 160 auto event = m_Model->getEvent(selectedRows.first());
151 161 if (event) {
152 162 if (m_VisualizationWidget) {
153 163 if (auto tab = m_VisualizationWidget->currentTabWidget()) {
154 164
155 165 for (auto zoneName : m_ZonesForTimeMode) {
156 166 if (auto zone = tab->getZoneWithName(zoneName)) {
157 167 SqpRange eventRange;
158 168 eventRange.m_TStart = event->getTStart();
159 169 eventRange.m_TEnd = event->getTEnd();
160 170 zone->setZoneRange(eventRange);
161 171 }
162 172 }
163 173 }
164 174 else {
165 175 qCWarning(LOG_CatalogueEventsWidget())
166 176 << "updateTimeZone: no tab found in the visualization";
167 177 }
168 178 }
169 179 else {
170 180 qCWarning(LOG_CatalogueEventsWidget())
171 181 << "updateTimeZone: visualization widget not found";
172 182 }
173 183 }
174 184 }
175 185 else {
176 186 qCWarning(LOG_CatalogueEventsWidget())
177 187 << "updateTimeZone: not compatible with multiple events selected";
178 188 }
179 189 }
180 190
181 void updateForGraphMode(QTreeView *treeView)
191 QVector<SqpRange> getGraphRanges(const std::shared_ptr<DBEvent> &event)
182 192 {
183 auto selectedRows = treeView->selectionModel()->selectedRows();
193 // Retrieves the range of each product and the maximum size
194 QVector<SqpRange> graphRanges;
195 double maxDt = 0;
196 for (auto eventProduct : event->getEventProducts()) {
197 SqpRange eventRange;
198 eventRange.m_TStart = eventProduct.getTStart();
199 eventRange.m_TEnd = eventProduct.getTEnd();
200 graphRanges << eventRange;
184 201
185 if (selectedRows.count() == 1) {
186 auto event = m_Model->getEvent(selectedRows.first());
187 if (m_VisualizationWidget) {
188 if (auto tab = m_VisualizationWidget->currentTabWidget()) {
189 if (auto zone = tab->getZoneWithName(m_ZoneForGraphMode)) {
190 // TODO
202 auto dt = eventRange.m_TEnd - eventRange.m_TStart;
203 if (dt > maxDt) {
204 maxDt = dt;
191 205 }
192 206 }
193 else {
194 qCWarning(LOG_CatalogueEventsWidget())
195 << "updateGraphMode: no tab found in the visualization";
207
208 // Adds the marge
209 maxDt *= (100.0 + EVENT_RANGE_MARGE) / 100.0;
210
211 // Corrects the graph ranges so that they all have the same size
212 QVector<SqpRange> correctedGraphRanges;
213 for (auto range : graphRanges) {
214 auto dt = range.m_TEnd - range.m_TStart;
215 auto diff = qAbs((maxDt - dt) / 2.0);
216
217 SqpRange correctedRange;
218 correctedRange.m_TStart = range.m_TStart - diff;
219 correctedRange.m_TEnd = range.m_TEnd + diff;
220
221 correctedGraphRanges << correctedRange;
196 222 }
223
224 return correctedGraphRanges;
197 225 }
198 else {
226
227 void updateForGraphMode(CatalogueEventsWidget *catalogueEventWidget)
228 {
229 auto selectedRows = catalogueEventWidget->ui->treeView->selectionModel()->selectedRows();
230 if (selectedRows.count() != 1) {
231 qCWarning(LOG_CatalogueEventsWidget())
232 << "updateGraphMode: not compatible with multiple events selected";
233 return;
234 }
235
236 if (!m_VisualizationWidget) {
199 237 qCWarning(LOG_CatalogueEventsWidget())
200 238 << "updateGraphMode: visualization widget not found";
239 return;
201 240 }
241
242 auto event = m_Model->getEvent(selectedRows.first());
243 if (!event) {
244 // A event product is probably selected
245 qCInfo(LOG_CatalogueEventsWidget()) << "updateGraphMode: no events are selected";
246 return;
202 247 }
203 else {
248
249 auto tab = m_VisualizationWidget->currentTabWidget();
250 if (!tab) {
204 251 qCWarning(LOG_CatalogueEventsWidget())
205 << "updateGraphMode: not compatible with multiple events selected";
252 << "updateGraphMode: no tab found in the visualization";
253 return;
254 }
255
256 auto zone = tab->getZoneWithName(m_ZoneForGraphMode);
257 if (!zone) {
258 qCWarning(LOG_CatalogueEventsWidget()) << "updateGraphMode: zone not found";
259 return;
260 }
261
262 // Close the previous graph and delete the asociated variables
263 for (auto graph : m_CustomGraphs) {
264 graph->close();
265 auto variables = graph->variables().toVector();
266
267 QMetaObject::invokeMethod(&sqpApp->variableController(), "deleteVariables",
268 Qt::QueuedConnection,
269 Q_ARG(QVector<std::shared_ptr<Variable> >, variables));
270 }
271 m_CustomGraphs.clear();
272
273 // Calculates the range of each graph which will be created
274 auto graphRange = getGraphRanges(event);
275
276 // Loops through the event products and create the graph
277 auto itRange = graphRange.cbegin();
278 for (auto eventProduct : event->getEventProducts()) {
279 auto productId = eventProduct.getProductId();
280
281 auto range = *itRange;
282 ++itRange;
283
284 SqpRange productRange;
285 productRange.m_TStart = eventProduct.getTStart();
286 productRange.m_TEnd = eventProduct.getTEnd();
287
288 auto context = new QObject{catalogueEventWidget};
289 QObject::connect(
290 &sqpApp->variableController(), &VariableController::variableAdded, context,
291 [this, catalogueEventWidget, zone, context, event, range, productRange,
292 productId](auto variable) {
293
294 if (variable->metadata().value(DataSourceItem::ID_DATA_KEY).toString()
295 == productId) {
296 auto graph = zone->createGraph(variable);
297 graph->setAutoRangeOnVariableInitialization(false);
298
299 auto selectionZone
300 = graph->addSelectionZone(event->getName(), productRange);
301 emit catalogueEventWidget->selectionZoneAdded(event, productId,
302 selectionZone);
303 m_CustomGraphs << graph;
304
305 graph->setGraphRange(range, true);
306
307 // Removes the graph from the graph list if it is closed manually
308 QObject::connect(graph, &VisualizationGraphWidget::destroyed,
309 [this, graph]() { m_CustomGraphs.removeAll(graph); });
310
311 delete context; // removes the connection
312 }
313 },
314 Qt::QueuedConnection);
315
316 QMetaObject::invokeMethod(&sqpApp->dataSourceController(),
317 "requestVariableFromProductIdKey", Qt::QueuedConnection,
318 Q_ARG(QString, productId));
206 319 }
207 320 }
208 321
209 322 void getSelectedItems(
210 323 QTreeView *treeView, QVector<std::shared_ptr<DBEvent> > &events,
211 324 QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > > &eventProducts)
212 325 {
213 326 for (auto rowIndex : treeView->selectionModel()->selectedRows()) {
214 327 auto itemType = m_Model->itemTypeOf(rowIndex);
215 328 if (itemType == CatalogueEventsModel::ItemType::Event) {
216 329 events << m_Model->getEvent(rowIndex);
217 330 }
218 331 else if (itemType == CatalogueEventsModel::ItemType::EventProduct) {
219 332 eventProducts << qMakePair(m_Model->getParentEvent(rowIndex),
220 333 m_Model->getEventProduct(rowIndex));
221 334 }
222 335 }
223 336 }
224 337 };
225 338
226 339 CatalogueEventsWidget::CatalogueEventsWidget(QWidget *parent)
227 340 : QWidget(parent),
228 341 ui(new Ui::CatalogueEventsWidget),
229 342 impl{spimpl::make_unique_impl<CatalogueEventsWidgetPrivate>()}
230 343 {
231 344 ui->setupUi(this);
232 345
233 346 impl->m_Model = new CatalogueEventsModel{this};
234 347 ui->treeView->setModel(impl->m_Model);
235 348
236 349 ui->treeView->setSortingEnabled(true);
237 350 ui->treeView->setDragDropMode(QAbstractItemView::DragDrop);
238 351 ui->treeView->setDragEnabled(true);
239 352
240 353 connect(ui->btnTime, &QToolButton::clicked, [this](auto checked) {
241 354 if (checked) {
242 355 ui->btnChart->setChecked(false);
243 356 impl->m_ZonesForTimeMode
244 357 = impl->selectZone(this, impl->m_ZonesForTimeMode, true,
245 358 this->mapToGlobal(ui->btnTime->frameGeometry().center()));
246 359
247 360 impl->updateForTimeMode(ui->treeView);
248 361 }
249 362 });
250 363
251 364 connect(ui->btnChart, &QToolButton::clicked, [this](auto checked) {
252 365 if (checked) {
253 366 ui->btnTime->setChecked(false);
254 367 impl->m_ZoneForGraphMode
255 368 = impl->selectZone(this, {impl->m_ZoneForGraphMode}, false,
256 369 this->mapToGlobal(ui->btnChart->frameGeometry().center()))
257 370 .value(0);
258 371
259 impl->updateForGraphMode(ui->treeView);
372 impl->updateForGraphMode(this);
260 373 }
261 374 });
262 375
263 376 connect(ui->btnRemove, &QToolButton::clicked, [this]() {
264 377 QVector<std::shared_ptr<DBEvent> > events;
265 378 QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > > eventProducts;
266 379 impl->getSelectedItems(ui->treeView, events, eventProducts);
267 380
268 381 if (!events.isEmpty() && eventProducts.isEmpty()) {
269 382
270 383 if (QMessageBox::warning(this, tr("Remove Event(s)"),
271 tr("The selected event(s) will be completly removed "
384 tr("The selected event(s) will be permanently removed "
272 385 "from the repository!\nAre you sure you want to continue?"),
273 386 QMessageBox::Yes | QMessageBox::No, QMessageBox::No)
274 387 == QMessageBox::Yes) {
275 388
276 389 for (auto event : events) {
277 390 sqpApp->catalogueController().removeEvent(event);
278 391 impl->removeEvent(event, ui->treeView);
279 392 }
393
394 emit this->eventsRemoved(events);
280 395 }
281 396 }
282 397 });
283 398
284 399 connect(ui->treeView, &QTreeView::clicked, this, &CatalogueEventsWidget::emitSelection);
285 400 connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this,
286 401 &CatalogueEventsWidget::emitSelection);
287 402
288 403 ui->btnRemove->setEnabled(false); // Disabled by default when nothing is selected
289 404 connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, [this]() {
290 405 auto isNotMultiSelection = ui->treeView->selectionModel()->selectedRows().count() <= 1;
291 406 ui->btnChart->setEnabled(isNotMultiSelection);
292 407 ui->btnTime->setEnabled(isNotMultiSelection);
293 408
294 409 if (isNotMultiSelection && ui->btnTime->isChecked()) {
295 410 impl->updateForTimeMode(ui->treeView);
296 411 }
297 412 else if (isNotMultiSelection && ui->btnChart->isChecked()) {
298 impl->updateForGraphMode(ui->treeView);
413 impl->updateForGraphMode(this);
299 414 }
300 415
301 416 QVector<std::shared_ptr<DBEvent> > events;
302 417 QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > > eventProducts;
303 418 impl->getSelectedItems(ui->treeView, events, eventProducts);
304 419 ui->btnRemove->setEnabled(!events.isEmpty() && eventProducts.isEmpty());
305 420 });
306 421
307 422 ui->treeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
308 423 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Tags,
309 424 QHeaderView::Stretch);
310 425 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Validation,
311 426 QHeaderView::Fixed);
312 427 ui->treeView->header()->setSectionResizeMode((int)CatalogueEventsModel::Column::Name,
313 428 QHeaderView::Interactive);
314 429 ui->treeView->header()->resizeSection((int)CatalogueEventsModel::Column::Validation,
315 430 VALIDATION_COLUMN_SIZE);
316 431 ui->treeView->header()->setSortIndicatorShown(true);
317 432
318 433 connect(impl->m_Model, &CatalogueEventsModel::modelSorted, [this]() {
319 434 auto allEvents = impl->m_Model->events();
320 435 for (auto event : allEvents) {
321 436 setEventChanges(event, sqpApp->catalogueController().eventHasChanges(event));
322 437 }
323 438 });
324 439
325 440 populateWithAllEvents();
326 441 }
327 442
328 443 CatalogueEventsWidget::~CatalogueEventsWidget()
329 444 {
330 445 delete ui;
331 446 }
332 447
333 448 void CatalogueEventsWidget::setVisualizationWidget(VisualizationWidget *visualization)
334 449 {
335 450 impl->m_VisualizationWidget = visualization;
336 451 }
337 452
338 453 void CatalogueEventsWidget::addEvent(const std::shared_ptr<DBEvent> &event)
339 454 {
340 455 impl->addEvent(event, ui->treeView);
341 456 }
342 457
343 458 void CatalogueEventsWidget::setEventChanges(const std::shared_ptr<DBEvent> &event, bool hasChanges)
344 459 {
345 460 impl->m_Model->refreshEvent(event);
346 461
347 462 auto eventIndex = impl->m_Model->indexOf(event);
348 463 auto validationIndex
349 464 = eventIndex.sibling(eventIndex.row(), (int)CatalogueEventsModel::Column::Validation);
350 465
351 466 if (validationIndex.isValid()) {
352 467 if (hasChanges) {
353 468 if (ui->treeView->indexWidget(validationIndex) == nullptr) {
354 469 auto widget = CatalogueExplorerHelper::buildValidationWidget(
355 470 ui->treeView,
356 471 [this, event]() {
357 472 sqpApp->catalogueController().saveEvent(event);
358 473 setEventChanges(event, false);
359 474 },
360 475 [this, event]() {
361 476 bool removed = false;
362 477 sqpApp->catalogueController().discardEvent(event, removed);
363 478 if (removed) {
364 479 impl->m_Model->removeEvent(event);
365 480 }
366 481 else {
367 482 setEventChanges(event, false);
368 483 impl->m_Model->refreshEvent(event, true);
369 484 }
370 485 emitSelection();
371 486 });
372 487 ui->treeView->setIndexWidget(validationIndex, widget);
373 488 }
374 489 }
375 490 else {
376 491 // Note: the widget is destroyed
377 492 ui->treeView->setIndexWidget(validationIndex, nullptr);
378 493 }
379 494 }
380 495 else {
381 496 qCWarning(LOG_CatalogueEventsWidget())
382 497 << "setEventChanges: the event is not displayed in the model.";
383 498 }
384 499 }
385 500
386 501 QVector<std::shared_ptr<DBCatalogue> > CatalogueEventsWidget::displayedCatalogues() const
387 502 {
388 503 return impl->m_DisplayedCatalogues;
389 504 }
390 505
391 506 bool CatalogueEventsWidget::isAllEventsDisplayed() const
392 507 {
393 508 return impl->m_AllEventDisplayed;
394 509 }
395 510
396 511 bool CatalogueEventsWidget::isEventDisplayed(const std::shared_ptr<DBEvent> &event) const
397 512 {
398 513 return impl->m_Model->indexOf(event).isValid();
399 514 }
400 515
516 void CatalogueEventsWidget::refreshEvent(const std::shared_ptr<DBEvent> &event)
517 {
518 impl->m_Model->refreshEvent(event, true);
519 }
520
401 521 void CatalogueEventsWidget::populateWithCatalogues(
402 522 const QVector<std::shared_ptr<DBCatalogue> > &catalogues)
403 523 {
404 524 impl->m_DisplayedCatalogues = catalogues;
405 525 impl->m_AllEventDisplayed = false;
406 526
407 527 QSet<QUuid> eventIds;
408 528 QVector<std::shared_ptr<DBEvent> > events;
409 529
410 530 for (auto catalogue : catalogues) {
411 531 auto catalogueEvents = sqpApp->catalogueController().retrieveEventsFromCatalogue(catalogue);
412 532 for (auto event : catalogueEvents) {
413 533 if (!eventIds.contains(event->getUniqId())) {
414 534 events << event;
415 535 eventIds.insert(event->getUniqId());
416 536 }
417 537 }
418 538 }
419 539
420 540 impl->setEvents(events, this);
421 541 }
422 542
423 543 void CatalogueEventsWidget::populateWithAllEvents()
424 544 {
425 545 impl->m_DisplayedCatalogues.clear();
426 546 impl->m_AllEventDisplayed = true;
427 547
428 548 auto allEvents = sqpApp->catalogueController().retrieveAllEvents();
429 549
430 550 QVector<std::shared_ptr<DBEvent> > events;
431 551 for (auto event : allEvents) {
432 552 events << event;
433 553 }
434 554
435 555 impl->setEvents(events, this);
436 556 }
437 557
438 558 void CatalogueEventsWidget::clear()
439 559 {
440 560 impl->m_DisplayedCatalogues.clear();
441 561 impl->m_AllEventDisplayed = false;
442 562 impl->setEvents({}, this);
443 563 }
444 564
445 565 void CatalogueEventsWidget::refresh()
446 566 {
447 567 if (isAllEventsDisplayed()) {
448 568 populateWithAllEvents();
449 569 }
450 570 else if (!impl->m_DisplayedCatalogues.isEmpty()) {
451 571 populateWithCatalogues(impl->m_DisplayedCatalogues);
452 572 }
453 573 }
454 574
455 575 void CatalogueEventsWidget::emitSelection()
456 576 {
457 577 QVector<std::shared_ptr<DBEvent> > events;
458 578 QVector<QPair<std::shared_ptr<DBEvent>, std::shared_ptr<DBEventProduct> > > eventProducts;
459 579 impl->getSelectedItems(ui->treeView, events, eventProducts);
460 580
461 581 if (!events.isEmpty() && eventProducts.isEmpty()) {
462 582 emit eventsSelected(events);
463 583 }
464 584 else if (events.isEmpty() && !eventProducts.isEmpty()) {
465 585 emit eventProductsSelected(eventProducts);
466 586 }
467 587 else {
468 588 emit selectionCleared();
469 589 }
470 590 }
@@ -1,126 +1,193
1 1 #include "Catalogue/CatalogueExplorer.h"
2 2 #include "ui_CatalogueExplorer.h"
3 3
4 4 #include <Catalogue/CatalogueActionManager.h>
5 5 #include <Catalogue/CatalogueController.h>
6 6 #include <SqpApplication.h>
7 #include <Visualization/VisualizationGraphWidget.h>
8 #include <Visualization/VisualizationSelectionZoneItem.h>
7 9 #include <Visualization/VisualizationWidget.h>
8 10
9 11 #include <DBCatalogue.h>
10 12 #include <DBEvent.h>
13 #include <DBEventProduct.h>
14
15 #include <unordered_map>
11 16
12 17 struct CatalogueExplorer::CatalogueExplorerPrivate {
13 18 CatalogueActionManager m_ActionManager;
19 std::unordered_map<std::shared_ptr<DBEvent>, QVector<VisualizationSelectionZoneItem *> >
20 m_SelectionZonesPerEvents;
21
22 QMetaObject::Connection m_Conn;
14 23
15 24 CatalogueExplorerPrivate(CatalogueExplorer *catalogueExplorer)
16 25 : m_ActionManager(catalogueExplorer)
17 26 {
18 27 }
19 28 };
20 29
21 30 CatalogueExplorer::CatalogueExplorer(QWidget *parent)
22 31 : QDialog(parent, Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowCloseButtonHint),
23 32 ui(new Ui::CatalogueExplorer),
24 33 impl{spimpl::make_unique_impl<CatalogueExplorerPrivate>(this)}
25 34 {
26 35 ui->setupUi(this);
27 36
28 37 impl->m_ActionManager.installSelectionZoneActions();
29 38
39 // Updates events and inspector when something is selected in the catalogue widget
30 40 connect(ui->catalogues, &CatalogueSideBarWidget::catalogueSelected, [this](auto catalogues) {
31 41 if (catalogues.count() == 1) {
32 42 ui->inspector->setCatalogue(catalogues.first());
33 43 }
34 44 else {
35 45 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
36 46 }
37 47
38 48 ui->events->populateWithCatalogues(catalogues);
39 49 });
40 50
41 51 connect(ui->catalogues, &CatalogueSideBarWidget::databaseSelected, [this](auto databases) {
42 52 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
43 53 });
44 54
45 55 connect(ui->catalogues, &CatalogueSideBarWidget::trashSelected, [this]() {
46 56 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
47 57 ui->events->clear();
48 58 });
49 59
50 60 connect(ui->catalogues, &CatalogueSideBarWidget::allEventsSelected, [this]() {
51 61 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
52 62 ui->events->populateWithAllEvents();
53 63 });
54 64
55 65 connect(ui->catalogues, &CatalogueSideBarWidget::databaseSelected, [this](auto databaseList) {
56 66 QVector<std::shared_ptr<DBCatalogue> > catalogueList;
57 67 for (auto database : databaseList) {
58 68 catalogueList.append(ui->catalogues->getCatalogues(database));
59 69 }
60 70 ui->events->populateWithCatalogues(catalogueList);
61 71 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
62 72 });
63 73
64 74 connect(ui->catalogues, &CatalogueSideBarWidget::selectionCleared, [this]() {
65 75 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
66 76 ui->events->clear();
67 77 });
68 78
79 // Updates the inspectot when something is selected in the events
69 80 connect(ui->events, &CatalogueEventsWidget::eventsSelected, [this](auto events) {
70 81 if (events.count() == 1) {
71 82 ui->inspector->setEvent(events.first());
72 83 }
73 84 else {
74 85 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
75 86 }
76 87 });
77 88
78 89 connect(ui->events, &CatalogueEventsWidget::eventProductsSelected, [this](auto eventProducts) {
79 90 if (eventProducts.count() == 1) {
80 91 ui->inspector->setEventProduct(eventProducts.first().first,
81 92 eventProducts.first().second);
82 93 }
83 94 else {
84 95 ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty);
85 96 }
86 97 });
87 98
88 99 connect(ui->events, &CatalogueEventsWidget::selectionCleared,
89 100 [this]() { ui->inspector->showPage(CatalogueInspectorWidget::Page::Empty); });
90 101
102 // Manage Selection Zones associated to events
103 connect(ui->events, &CatalogueEventsWidget::selectionZoneAdded,
104 [this](auto event, auto productId, auto zone) {
105 this->addSelectionZoneItem(event, productId, zone);
106 });
107
108 connect(ui->events, &CatalogueEventsWidget::eventsRemoved, [this](auto events) {
109 for (auto event : events) {
110 auto associatedSelectionZonesIt = impl->m_SelectionZonesPerEvents.find(event);
111 if (associatedSelectionZonesIt != impl->m_SelectionZonesPerEvents.cend()) {
112 for (auto selectionZone : associatedSelectionZonesIt->second) {
113 auto parentGraph = selectionZone->parentGraphWidget();
114 parentGraph->removeSelectionZone(selectionZone);
115 }
116
117 impl->m_SelectionZonesPerEvents.erase(event);
118 }
119 }
120 });
121
122 // Updates changes from the inspector
91 123 connect(ui->inspector, &CatalogueInspectorWidget::catalogueUpdated, [this](auto catalogue) {
92 124 sqpApp->catalogueController().updateCatalogue(catalogue);
93 125 ui->catalogues->setCatalogueChanges(catalogue, true);
94 126 });
95 127
96 128 connect(ui->inspector, &CatalogueInspectorWidget::eventUpdated, [this](auto event) {
97 129 sqpApp->catalogueController().updateEvent(event);
98 130 ui->events->setEventChanges(event, true);
99 131 });
100 132
101 133 connect(ui->inspector, &CatalogueInspectorWidget::eventProductUpdated,
102 134 [this](auto event, auto eventProduct) {
103 135 sqpApp->catalogueController().updateEventProduct(eventProduct);
104 136 ui->events->setEventChanges(event, true);
105 137 });
106 138 }
107 139
108 140 CatalogueExplorer::~CatalogueExplorer()
109 141 {
142 disconnect(impl->m_Conn);
110 143 delete ui;
111 144 }
112 145
113 146 void CatalogueExplorer::setVisualizationWidget(VisualizationWidget *visualization)
114 147 {
115 148 ui->events->setVisualizationWidget(visualization);
116 149 }
117 150
118 151 CatalogueEventsWidget &CatalogueExplorer::eventsWidget() const
119 152 {
120 153 return *ui->events;
121 154 }
122 155
123 156 CatalogueSideBarWidget &CatalogueExplorer::sideBarWidget() const
124 157 {
125 158 return *ui->catalogues;
126 159 }
160
161 void CatalogueExplorer::clearSelectionZones()
162 {
163 impl->m_SelectionZonesPerEvents.clear();
164 }
165
166 void CatalogueExplorer::addSelectionZoneItem(const std::shared_ptr<DBEvent> &event,
167 const QString &productId,
168 VisualizationSelectionZoneItem *selectionZone)
169 {
170 impl->m_SelectionZonesPerEvents[event] << selectionZone;
171 connect(selectionZone, &VisualizationSelectionZoneItem::rangeEdited,
172 [event, productId, this](auto range) {
173 auto productList = event->getEventProducts();
174 for (auto &product : productList) {
175 if (product.getProductId() == productId) {
176 product.setTStart(range.m_TStart);
177 product.setTEnd(range.m_TEnd);
178 }
179 }
180 event->setEventProducts(productList);
181 sqpApp->catalogueController().updateEvent(event);
182 ui->events->refreshEvent(event);
183 ui->events->setEventChanges(event, true);
184 ui->inspector->refresh();
185 });
186
187 impl->m_Conn = connect(selectionZone, &VisualizationSelectionZoneItem::destroyed,
188 [event, selectionZone, this]() {
189 if (!impl->m_SelectionZonesPerEvents.empty()) {
190 impl->m_SelectionZonesPerEvents[event].removeAll(selectionZone);
191 }
192 });
193 }
@@ -1,211 +1,227
1 1 #include "Catalogue/CatalogueInspectorWidget.h"
2 2 #include "ui_CatalogueInspectorWidget.h"
3 3
4 4 #include <Common/DateUtils.h>
5 5 #include <DBCatalogue.h>
6 6 #include <DBEvent.h>
7 7 #include <DBEventProduct.h>
8 8 #include <DBTag.h>
9 9
10 10 struct CatalogueInspectorWidget::CatalogueInspectorWidgetPrivate {
11 11 std::shared_ptr<DBCatalogue> m_DisplayedCatalogue = nullptr;
12 12 std::shared_ptr<DBEvent> m_DisplayedEvent = nullptr;
13 13 std::shared_ptr<DBEventProduct> m_DisplayedEventProduct = nullptr;
14 14
15 15 void connectCatalogueUpdateSignals(CatalogueInspectorWidget *inspector,
16 16 Ui::CatalogueInspectorWidget *ui);
17 17 void connectEventUpdateSignals(CatalogueInspectorWidget *inspector,
18 18 Ui::CatalogueInspectorWidget *ui);
19 19 };
20 20
21 21 CatalogueInspectorWidget::CatalogueInspectorWidget(QWidget *parent)
22 22 : QWidget(parent),
23 23 ui(new Ui::CatalogueInspectorWidget),
24 24 impl{spimpl::make_unique_impl<CatalogueInspectorWidgetPrivate>()}
25 25 {
26 26 ui->setupUi(this);
27 27 showPage(Page::Empty);
28 28
29 29 impl->connectCatalogueUpdateSignals(this, ui);
30 30 impl->connectEventUpdateSignals(this, ui);
31 31 }
32 32
33 33 CatalogueInspectorWidget::~CatalogueInspectorWidget()
34 34 {
35 35 delete ui;
36 36 }
37 37
38 38 void CatalogueInspectorWidget::CatalogueInspectorWidgetPrivate::connectCatalogueUpdateSignals(
39 39 CatalogueInspectorWidget *inspector, Ui::CatalogueInspectorWidget *ui)
40 40 {
41 41 connect(ui->leCatalogueName, &QLineEdit::editingFinished, [ui, inspector, this]() {
42 42 if (ui->leCatalogueName->text() != m_DisplayedCatalogue->getName()) {
43 43 m_DisplayedCatalogue->setName(ui->leCatalogueName->text());
44 44 emit inspector->catalogueUpdated(m_DisplayedCatalogue);
45 45 }
46 46 });
47 47
48 48 connect(ui->leCatalogueAuthor, &QLineEdit::editingFinished, [ui, inspector, this]() {
49 49 if (ui->leCatalogueAuthor->text() != m_DisplayedCatalogue->getAuthor()) {
50 50 m_DisplayedCatalogue->setAuthor(ui->leCatalogueAuthor->text());
51 51 emit inspector->catalogueUpdated(m_DisplayedCatalogue);
52 52 }
53 53 });
54 54 }
55 55
56 56 void CatalogueInspectorWidget::CatalogueInspectorWidgetPrivate::connectEventUpdateSignals(
57 57 CatalogueInspectorWidget *inspector, Ui::CatalogueInspectorWidget *ui)
58 58 {
59 59 connect(ui->leEventName, &QLineEdit::editingFinished, [ui, inspector, this]() {
60 60 if (ui->leEventName->text() != m_DisplayedEvent->getName()) {
61 61 m_DisplayedEvent->setName(ui->leEventName->text());
62 62 emit inspector->eventUpdated(m_DisplayedEvent);
63 63 }
64 64 });
65 65
66 66 connect(ui->leEventTags, &QLineEdit::editingFinished, [ui, inspector, this]() {
67 67 auto tags = ui->leEventTags->text().split(QRegExp("\\s+"), QString::SkipEmptyParts);
68 68 std::list<QString> tagNames;
69 69 for (auto tag : tags) {
70 70 tagNames.push_back(tag);
71 71 }
72 72
73 73 if (m_DisplayedEvent->getTagsNames() != tagNames) {
74 74 m_DisplayedEvent->setTagsNames(tagNames);
75 75 emit inspector->eventUpdated(m_DisplayedEvent);
76 76 }
77 77 });
78 78
79 79 connect(ui->leEventProduct, &QLineEdit::editingFinished, [ui, inspector, this]() {
80 80 if (ui->leEventProduct->text() != m_DisplayedEventProduct->getProductId()) {
81 81 auto oldProductId = m_DisplayedEventProduct->getProductId();
82 82 m_DisplayedEventProduct->setProductId(ui->leEventProduct->text());
83 83
84 84 auto eventProducts = m_DisplayedEvent->getEventProducts();
85 85 for (auto &eventProduct : eventProducts) {
86 86 if (eventProduct.getProductId() == oldProductId) {
87 87 eventProduct.setProductId(m_DisplayedEventProduct->getProductId());
88 88 }
89 89 }
90 90 m_DisplayedEvent->setEventProducts(eventProducts);
91 91
92 92 emit inspector->eventUpdated(m_DisplayedEvent);
93 93 }
94 94 });
95 95
96 96 connect(ui->dateTimeEventTStart, &QDateTimeEdit::editingFinished, [ui, inspector, this]() {
97 97 auto time = DateUtils::secondsSinceEpoch(ui->dateTimeEventTStart->dateTime());
98 98 if (time != m_DisplayedEventProduct->getTStart()) {
99 99 m_DisplayedEventProduct->setTStart(time);
100 100
101 101 auto eventProducts = m_DisplayedEvent->getEventProducts();
102 102 for (auto &eventProduct : eventProducts) {
103 103 if (eventProduct.getProductId() == m_DisplayedEventProduct->getProductId()) {
104 104 eventProduct.setTStart(m_DisplayedEventProduct->getTStart());
105 105 }
106 106 }
107 107 m_DisplayedEvent->setEventProducts(eventProducts);
108 108
109 109 emit inspector->eventUpdated(m_DisplayedEvent);
110 110 }
111 111 });
112 112
113 113 connect(ui->dateTimeEventTEnd, &QDateTimeEdit::editingFinished, [ui, inspector, this]() {
114 114 auto time = DateUtils::secondsSinceEpoch(ui->dateTimeEventTEnd->dateTime());
115 115 if (time != m_DisplayedEventProduct->getTEnd()) {
116 116 m_DisplayedEventProduct->setTEnd(time);
117 117
118 118 auto eventProducts = m_DisplayedEvent->getEventProducts();
119 119 for (auto &eventProduct : eventProducts) {
120 120 if (eventProduct.getProductId() == m_DisplayedEventProduct->getProductId()) {
121 121 eventProduct.setTEnd(m_DisplayedEventProduct->getTEnd());
122 122 }
123 123 }
124 124 m_DisplayedEvent->setEventProducts(eventProducts);
125 125
126 126 emit inspector->eventUpdated(m_DisplayedEvent);
127 127 }
128 128 });
129 129 }
130 130
131 131 void CatalogueInspectorWidget::showPage(CatalogueInspectorWidget::Page page)
132 132 {
133 133 ui->stackedWidget->setCurrentIndex(static_cast<int>(page));
134 134 }
135 135
136 136 CatalogueInspectorWidget::Page CatalogueInspectorWidget::currentPage() const
137 137 {
138 138 return static_cast<Page>(ui->stackedWidget->currentIndex());
139 139 }
140 140
141 141 void CatalogueInspectorWidget::setEvent(const std::shared_ptr<DBEvent> &event)
142 142 {
143 143 impl->m_DisplayedEvent = event;
144 144
145 145 blockSignals(true);
146 146
147 147 showPage(Page::EventProperties);
148 148 ui->leEventName->setEnabled(true);
149 149 ui->leEventName->setText(event->getName());
150 150 ui->leEventProduct->setEnabled(false);
151 151 ui->leEventProduct->setText(
152 152 QString::number(event->getEventProducts().size()).append(" product(s)"));
153 153
154 154 QString tagList;
155 155 auto tags = event->getTagsNames();
156 156 for (auto tag : tags) {
157 157 tagList += tag;
158 158 tagList += ' ';
159 159 }
160 160
161 161 ui->leEventTags->setEnabled(true);
162 162 ui->leEventTags->setText(tagList);
163 163
164 164 ui->dateTimeEventTStart->setEnabled(false);
165 165 ui->dateTimeEventTEnd->setEnabled(false);
166 166
167 167 ui->dateTimeEventTStart->setDateTime(DateUtils::dateTime(event->getTStart()));
168 168 ui->dateTimeEventTEnd->setDateTime(DateUtils::dateTime(event->getTEnd()));
169 169
170 170 blockSignals(false);
171 171 }
172 172
173 173 void CatalogueInspectorWidget::setEventProduct(const std::shared_ptr<DBEvent> &event,
174 174 const std::shared_ptr<DBEventProduct> &eventProduct)
175 175 {
176 176
177 177 impl->m_DisplayedEvent = event;
178 178 impl->m_DisplayedEventProduct = eventProduct;
179 179
180 180 blockSignals(true);
181 181
182 182 showPage(Page::EventProperties);
183 183 ui->leEventName->setEnabled(false);
184 184 ui->leEventName->setText(event->getName());
185 185 ui->leEventProduct->setEnabled(false);
186 186 ui->leEventProduct->setText(eventProduct->getProductId());
187 187
188 188 ui->leEventTags->setEnabled(false);
189 189 ui->leEventTags->clear();
190 190
191 191 ui->dateTimeEventTStart->setEnabled(true);
192 192 ui->dateTimeEventTEnd->setEnabled(true);
193 193
194 194 ui->dateTimeEventTStart->setDateTime(DateUtils::dateTime(eventProduct->getTStart()));
195 195 ui->dateTimeEventTEnd->setDateTime(DateUtils::dateTime(eventProduct->getTEnd()));
196 196
197 197 blockSignals(false);
198 198 }
199 199
200 200 void CatalogueInspectorWidget::setCatalogue(const std::shared_ptr<DBCatalogue> &catalogue)
201 201 {
202 202 impl->m_DisplayedCatalogue = catalogue;
203 203
204 204 blockSignals(true);
205 205
206 206 showPage(Page::CatalogueProperties);
207 207 ui->leCatalogueName->setText(catalogue->getName());
208 208 ui->leCatalogueAuthor->setText(catalogue->getAuthor());
209 209
210 210 blockSignals(false);
211 211 }
212
213 void CatalogueInspectorWidget::refresh()
214 {
215 switch (static_cast<Page>(ui->stackedWidget->currentIndex())) {
216 case Page::CatalogueProperties:
217 setCatalogue(impl->m_DisplayedCatalogue);
218 break;
219 case Page::EventProperties: {
220 auto isEventShowed = ui->leEventName->isEnabled();
221 setEvent(impl->m_DisplayedEvent);
222 if (!isEventShowed && impl->m_DisplayedEvent) {
223 setEventProduct(impl->m_DisplayedEvent, impl->m_DisplayedEventProduct);
224 }
225 }
226 }
227 }
@@ -1,296 +1,308
1 1 #include "DragAndDrop/DragDropGuiController.h"
2 2 #include "DragAndDrop/DragDropScroller.h"
3 3 #include "DragAndDrop/DragDropTabSwitcher.h"
4 4 #include "SqpApplication.h"
5 5 #include "Visualization/VisualizationDragDropContainer.h"
6 6 #include "Visualization/VisualizationDragWidget.h"
7 7 #include "Visualization/VisualizationWidget.h"
8 8 #include "Visualization/operations/FindVariableOperation.h"
9 9
10 #include "DataSource/DataSourceController.h"
10 11 #include "Variable/Variable.h"
11 12 #include "Variable/VariableController.h"
12 13
13 14 #include "Common/MimeTypesDef.h"
14 15 #include "Common/VisualizationDef.h"
15 16
16 17 #include <QDir>
17 18 #include <QLabel>
18 19 #include <QUrl>
19 20 #include <QVBoxLayout>
20 21
21 22
22 23 Q_LOGGING_CATEGORY(LOG_DragDropGuiController, "DragDropGuiController")
23 24
24 25
25 26 struct DragDropGuiController::DragDropGuiControllerPrivate {
26 27
27 28 VisualizationDragWidget *m_CurrentDragWidget = nullptr;
28 29 std::unique_ptr<QWidget> m_PlaceHolder = nullptr;
29 30 QLabel *m_PlaceHolderLabel;
30 31 QWidget *m_PlaceBackground;
31 32 std::unique_ptr<DragDropScroller> m_DragDropScroller = nullptr;
32 33 std::unique_ptr<DragDropTabSwitcher> m_DragDropTabSwitcher = nullptr;
33 34 QString m_ImageTempUrl; // Temporary file for image url generated by the drag & drop. Not using
34 35 // QTemporaryFile to have a name which is not generated.
35 36
36 37 VisualizationDragWidget *m_HighlightedDragWidget = nullptr;
37 38
38 39 QMetaObject::Connection m_DragWidgetDestroyedConnection;
39 40 QMetaObject::Connection m_HighlightedWidgetDestroyedConnection;
40 41
41 42 QList<QWidget *> m_WidgetToClose;
42 43
43 44 explicit DragDropGuiControllerPrivate()
44 45 : m_PlaceHolder{std::make_unique<QWidget>()},
45 46 m_DragDropScroller{std::make_unique<DragDropScroller>()},
46 47 m_DragDropTabSwitcher{std::make_unique<DragDropTabSwitcher>()}
47 48 {
48 49
49 50 auto layout = new QVBoxLayout{m_PlaceHolder.get()};
50 51 layout->setSpacing(0);
51 52 layout->setContentsMargins(0, 0, 0, 0);
52 53
53 54 m_PlaceHolderLabel = new QLabel{"", m_PlaceHolder.get()};
54 55 m_PlaceHolderLabel->setMinimumHeight(25);
55 56 layout->addWidget(m_PlaceHolderLabel);
56 57
57 58 m_PlaceBackground = new QWidget{m_PlaceHolder.get()};
58 59 m_PlaceBackground->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
59 60 layout->addWidget(m_PlaceBackground);
60 61
61 62 sqpApp->installEventFilter(m_DragDropScroller.get());
62 63 sqpApp->installEventFilter(m_DragDropTabSwitcher.get());
63 64
64 65 m_ImageTempUrl = QDir::temp().absoluteFilePath("Sciqlop_graph.png");
65 66 }
66 67
67 68 void preparePlaceHolder(DragDropGuiController::PlaceHolderType type,
68 69 const QString &topLabelText) const
69 70 {
70 71 if (m_CurrentDragWidget) {
71 72 m_PlaceHolder->setMinimumSize(m_CurrentDragWidget->size());
72 73 m_PlaceHolder->setSizePolicy(m_CurrentDragWidget->sizePolicy());
73 74 }
74 75 else {
75 76 // Configuration of the placeHolder when there is no dragWidget
76 77 // (for instance with a drag from a variable)
77 78
78 79 m_PlaceHolder->setMinimumSize(0, GRAPH_MINIMUM_HEIGHT);
79 80 m_PlaceHolder->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
80 81 }
81 82
82 83 switch (type) {
83 84 case DragDropGuiController::PlaceHolderType::Graph:
84 85 m_PlaceBackground->setStyleSheet(
85 86 "background-color: #BBD5EE; border: 1px solid #2A7FD4");
86 87 break;
87 88 case DragDropGuiController::PlaceHolderType::Zone:
88 89 case DragDropGuiController::PlaceHolderType::Default:
89 90 m_PlaceBackground->setStyleSheet(
90 91 "background-color: #BBD5EE; border: 2px solid #2A7FD4");
91 92 m_PlaceHolderLabel->setStyleSheet("color: #2A7FD4");
92 93 break;
93 94 }
94 95
95 96 m_PlaceHolderLabel->setText(topLabelText);
96 97 m_PlaceHolderLabel->setVisible(!topLabelText.isEmpty());
97 98 }
98 99 };
99 100
100 101
101 102 DragDropGuiController::DragDropGuiController()
102 103 : impl{spimpl::make_unique_impl<DragDropGuiControllerPrivate>()}
103 104 {
104 105 }
105 106
106 107 DragDropGuiController::~DragDropGuiController()
107 108 {
108 109 QFile::remove(impl->m_ImageTempUrl);
109 110 }
110 111
111 112 void DragDropGuiController::resetDragAndDrop()
112 113 {
113 114 setCurrentDragWidget(nullptr);
114 115 impl->m_HighlightedDragWidget = nullptr;
115 116 }
116 117
117 118 void DragDropGuiController::setCurrentDragWidget(VisualizationDragWidget *dragWidget)
118 119 {
119 120 if (impl->m_CurrentDragWidget) {
120 121
121 122 QObject::disconnect(impl->m_DragWidgetDestroyedConnection);
122 123 }
123 124
124 125 if (dragWidget) {
125 126 // ensures the impl->m_CurrentDragWidget is reset when the widget is destroyed
126 127 impl->m_DragWidgetDestroyedConnection
127 128 = QObject::connect(dragWidget, &VisualizationDragWidget::destroyed,
128 129 [this]() { impl->m_CurrentDragWidget = nullptr; });
129 130 }
130 131
131 132 impl->m_CurrentDragWidget = dragWidget;
132 133 }
133 134
134 135 VisualizationDragWidget *DragDropGuiController::getCurrentDragWidget() const
135 136 {
136 137 return impl->m_CurrentDragWidget;
137 138 }
138 139
139 140 QWidget &DragDropGuiController::placeHolder() const
140 141 {
141 142 return *impl->m_PlaceHolder;
142 143 }
143 144
144 145 void DragDropGuiController::insertPlaceHolder(QVBoxLayout *layout, int index, PlaceHolderType type,
145 146 const QString &topLabelText)
146 147 {
147 148 removePlaceHolder();
148 149 impl->preparePlaceHolder(type, topLabelText);
149 150 layout->insertWidget(index, impl->m_PlaceHolder.get());
150 151 impl->m_PlaceHolder->show();
151 152 }
152 153
153 154 void DragDropGuiController::removePlaceHolder()
154 155 {
155 156 auto parentWidget = impl->m_PlaceHolder->parentWidget();
156 157 if (parentWidget) {
157 158 parentWidget->layout()->removeWidget(impl->m_PlaceHolder.get());
158 159 impl->m_PlaceHolder->setParent(nullptr);
159 160 impl->m_PlaceHolder->hide();
160 161 }
161 162 }
162 163
163 164 bool DragDropGuiController::isPlaceHolderSet() const
164 165 {
165 166 return impl->m_PlaceHolder->parentWidget();
166 167 }
167 168
168 169 void DragDropGuiController::addDragDropScrollArea(QScrollArea *scrollArea)
169 170 {
170 171 impl->m_DragDropScroller->addScrollArea(scrollArea);
171 172 }
172 173
173 174 void DragDropGuiController::removeDragDropScrollArea(QScrollArea *scrollArea)
174 175 {
175 176 impl->m_DragDropScroller->removeScrollArea(scrollArea);
176 177 }
177 178
178 179 void DragDropGuiController::addDragDropTabBar(QTabBar *tabBar)
179 180 {
180 181 impl->m_DragDropTabSwitcher->addTabBar(tabBar);
181 182 }
182 183
183 184 void DragDropGuiController::removeDragDropTabBar(QTabBar *tabBar)
184 185 {
185 186 impl->m_DragDropTabSwitcher->removeTabBar(tabBar);
186 187 }
187 188
188 189 QUrl DragDropGuiController::imageTemporaryUrl(const QImage &image) const
189 190 {
190 191 image.save(impl->m_ImageTempUrl);
191 192 return QUrl::fromLocalFile(impl->m_ImageTempUrl);
192 193 }
193 194
194 195 void DragDropGuiController::setHightlightedDragWidget(VisualizationDragWidget *dragWidget)
195 196 {
196 197 if (impl->m_HighlightedDragWidget) {
197 198 impl->m_HighlightedDragWidget->highlightForMerge(false);
198 199 QObject::disconnect(impl->m_HighlightedWidgetDestroyedConnection);
199 200 }
200 201
201 202 if (dragWidget) {
202 203 dragWidget->highlightForMerge(true);
203 204
204 205 // ensures the impl->m_HighlightedDragWidget is reset when the widget is destroyed
205 206 impl->m_DragWidgetDestroyedConnection
206 207 = QObject::connect(dragWidget, &VisualizationDragWidget::destroyed,
207 208 [this]() { impl->m_HighlightedDragWidget = nullptr; });
208 209 }
209 210
210 211 impl->m_HighlightedDragWidget = dragWidget;
211 212 }
212 213
213 214 VisualizationDragWidget *DragDropGuiController::getHightlightedDragWidget() const
214 215 {
215 216 return impl->m_HighlightedDragWidget;
216 217 }
217 218
218 219 void DragDropGuiController::delayedCloseWidget(QWidget *widget)
219 220 {
220 221 widget->hide();
221 222 impl->m_WidgetToClose << widget;
222 223 }
223 224
224 225 void DragDropGuiController::doCloseWidgets()
225 226 {
226 227 for (auto widget : impl->m_WidgetToClose) {
227 228 widget->close();
228 229 }
229 230
230 231 impl->m_WidgetToClose.clear();
231 232 }
232 233
233 234 bool DragDropGuiController::checkMimeDataForVisualization(
234 235 const QMimeData *mimeData, VisualizationDragDropContainer *dropContainer)
235 236 {
236 237 if (!mimeData || !dropContainer) {
237 238 qCWarning(LOG_DragDropGuiController()) << QObject::tr(
238 239 "DragDropGuiController::checkMimeDataForVisualization, invalid input parameters.");
239 240 Q_ASSERT(false);
240 241 return false;
241 242 }
242 243
243 244 auto result = false;
244 245
245 246 if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
246 247 auto variables = sqpApp->variableController().variablesForMimeData(
247 248 mimeData->data(MIME_TYPE_VARIABLE_LIST));
248 249
249 250 if (variables.count() == 1) {
250 251
251 252 auto variable = variables.first();
252 253 if (variable->dataSeries() != nullptr) {
253 254
254 255 // Check that the variable is not already in a graph
255 256
256 257 auto parent = dropContainer->parentWidget();
257 258 while (parent && qobject_cast<VisualizationWidget *>(parent) == nullptr) {
258 259 parent = parent->parentWidget(); // Search for the top level VisualizationWidget
259 260 }
260 261
261 262 if (parent) {
262 263 auto visualizationWidget = static_cast<VisualizationWidget *>(parent);
263 264
264 265 FindVariableOperation findVariableOperation{variable};
265 266 visualizationWidget->accept(&findVariableOperation);
266 267 auto variableContainers = findVariableOperation.result();
267 268 if (variableContainers.empty()) {
268 269 result = true;
269 270 }
270 271 else {
271 272 // result = false: the variable already exist in the visualisation
272 273 }
273 274 }
274 275 else {
275 276 qCWarning(LOG_DragDropGuiController()) << QObject::tr(
276 277 "DragDropGuiController::checkMimeDataForVisualization, the parent "
277 278 "VisualizationWidget cannot be found. Cannot check if the variable is "
278 279 "already used or not.");
279 280 }
280 281 }
281 282 else {
282 283 // result = false: the variable is not fully loaded
283 284 }
284 285 }
285 286 else {
286 287 // result = false: cannot drop multiple variables in the visualisation
287 288 }
288 289 }
290 else if (mimeData->hasFormat(MIME_TYPE_PRODUCT_LIST)) {
291 auto productDataList = sqpApp->dataSourceController().productsDataForMimeData(
292 mimeData->data(MIME_TYPE_PRODUCT_LIST));
293 if (productDataList.count() == 1) {
294 result = true;
295 }
296 else {
297 // result = false: cannot drop multiple products in the visualisation
298 }
299 }
289 300 else {
290 301 // Other MIME data
291 302 // no special rules, accepted by default
292 303 result = true;
293 304 }
294 305
306
295 307 return result;
296 308 }
@@ -1,177 +1,207
1 1 #include "Visualization/AxisRenderingUtils.h"
2 2
3 3 #include <Data/ScalarSeries.h>
4 4 #include <Data/SpectrogramSeries.h>
5 5 #include <Data/VectorSeries.h>
6 6
7 #include <Variable/Variable.h>
8
7 9 #include <Visualization/SqpColorScale.h>
8 10 #include <Visualization/qcustomplot.h>
9 11
10 12 Q_LOGGING_CATEGORY(LOG_AxisRenderingUtils, "AxisRenderingUtils")
11 13
12 14 namespace {
13 15
14 16 const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss:zzz");
15 17
16 18 /// Format for datetimes on a axis
17 19 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
18 20
19 21 const auto NUMBER_FORMAT = 'g';
20 22 const auto NUMBER_PRECISION = 9;
21 23
22 24 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
23 25 /// non-time data
24 26 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis, QCPAxis::ScaleType scaleType)
25 27 {
26 28 if (isTimeAxis) {
27 29 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
28 30 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
29 31 dateTicker->setDateTimeSpec(Qt::UTC);
30 32
31 33 return dateTicker;
32 34 }
33 35 else if (scaleType == QCPAxis::stLogarithmic) {
34 36 return QSharedPointer<QCPAxisTickerLog>::create();
35 37 }
36 38 else {
37 39 // default ticker
38 40 return QSharedPointer<QCPAxisTicker>::create();
39 41 }
40 42 }
41 43
42 44 /**
43 45 * Sets properties of the axis passed as parameter
44 46 * @param axis the axis to set
45 47 * @param unit the unit to set for the axis
46 48 * @param scaleType the scale type to set for the axis
47 49 */
48 50 void setAxisProperties(QCPAxis &axis, const Unit &unit,
49 51 QCPAxis::ScaleType scaleType = QCPAxis::stLinear)
50 52 {
51 53 // label (unit name)
52 54 axis.setLabel(unit.m_Name);
53 55
54 56 // scale type
55 57 axis.setScaleType(scaleType);
56 58 if (scaleType == QCPAxis::stLogarithmic) {
57 59 // Scientific notation
58 60 axis.setNumberPrecision(0);
59 61 axis.setNumberFormat("eb");
60 62 }
61 63
62 64 // ticker (depending on the type of unit)
63 65 axis.setTicker(axisTicker(unit.m_TimeUnit, scaleType));
64 66 }
65 67
66 68 /**
67 69 * Delegate used to set axes properties
68 70 */
69 71 template <typename T, typename Enabled = void>
70 72 struct AxisSetter {
71 static void setProperties(T &, QCustomPlot &, SqpColorScale &)
73 static void setProperties(QCustomPlot &, SqpColorScale &)
72 74 {
73 75 // Default implementation does nothing
74 76 qCCritical(LOG_AxisRenderingUtils()) << "Can't set axis properties: unmanaged type of data";
75 77 }
78
79 static void setUnits(T &, QCustomPlot &, SqpColorScale &)
80 {
81 // Default implementation does nothing
82 qCCritical(LOG_AxisRenderingUtils()) << "Can't set axis units: unmanaged type of data";
83 }
76 84 };
77 85
78 86 /**
79 87 * Specialization of AxisSetter for scalars and vectors
80 88 * @sa ScalarSeries
81 89 * @sa VectorSeries
82 90 */
83 91 template <typename T>
84 92 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
85 93 or std::is_base_of<VectorSeries, T>::value> > {
86 static void setProperties(T &dataSeries, QCustomPlot &plot, SqpColorScale &)
94 static void setProperties(QCustomPlot &, SqpColorScale &)
95 {
96 // Nothing to do
97 }
98
99 static void setUnits(T &dataSeries, QCustomPlot &plot, SqpColorScale &)
87 100 {
88 101 dataSeries.lockRead();
89 102 auto xAxisUnit = dataSeries.xAxisUnit();
90 103 auto valuesUnit = dataSeries.valuesUnit();
91 104 dataSeries.unlock();
92 105
93 106 setAxisProperties(*plot.xAxis, xAxisUnit);
94 107 setAxisProperties(*plot.yAxis, valuesUnit);
95 108 }
96 109 };
97 110
98 111 /**
99 112 * Specialization of AxisSetter for spectrograms
100 113 * @sa SpectrogramSeries
101 114 */
102 115 template <typename T>
103 116 struct AxisSetter<T, typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
104 static void setProperties(T &dataSeries, QCustomPlot &plot, SqpColorScale &colorScale)
117 static void setProperties(QCustomPlot &plot, SqpColorScale &colorScale)
105 118 {
106 dataSeries.lockRead();
107 auto xAxisUnit = dataSeries.xAxisUnit();
108 auto yAxisUnit = dataSeries.yAxisUnit();
109 auto valuesUnit = dataSeries.valuesUnit();
110 dataSeries.unlock();
111
112 setAxisProperties(*plot.xAxis, xAxisUnit);
113 setAxisProperties(*plot.yAxis, yAxisUnit, QCPAxis::stLogarithmic);
114
115 119 // Displays color scale in plot
116 120 plot.plotLayout()->insertRow(0);
117 121 plot.plotLayout()->addElement(0, 0, colorScale.m_Scale);
118 122 colorScale.m_Scale->setType(QCPAxis::atTop);
119 123 colorScale.m_Scale->setMinimumMargins(QMargins{0, 0, 0, 0});
120 124
121 125 // Aligns color scale with axes
122 126 auto marginGroups = plot.axisRect()->marginGroups();
123 127 for (auto it = marginGroups.begin(), end = marginGroups.end(); it != end; ++it) {
124 128 colorScale.m_Scale->setMarginGroup(it.key(), it.value());
125 129 }
126 130
127 131 // Set color scale properties
128 setAxisProperties(*colorScale.m_Scale->axis(), valuesUnit, QCPAxis::stLogarithmic);
129 132 colorScale.m_AutomaticThreshold = true;
130 133 }
134
135 static void setUnits(T &dataSeries, QCustomPlot &plot, SqpColorScale &colorScale)
136 {
137 dataSeries.lockRead();
138 auto xAxisUnit = dataSeries.xAxisUnit();
139 auto yAxisUnit = dataSeries.yAxisUnit();
140 auto valuesUnit = dataSeries.valuesUnit();
141 dataSeries.unlock();
142
143 setAxisProperties(*plot.xAxis, xAxisUnit);
144 setAxisProperties(*plot.yAxis, yAxisUnit, QCPAxis::stLogarithmic);
145 setAxisProperties(*colorScale.m_Scale->axis(), valuesUnit, QCPAxis::stLogarithmic);
146 }
131 147 };
132 148
133 149 /**
134 150 * Default implementation of IAxisHelper, which takes data series to set axes properties
135 151 * @tparam T the data series' type
136 152 */
137 153 template <typename T>
138 154 struct AxisHelper : public IAxisHelper {
139 explicit AxisHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
155 explicit AxisHelper(std::shared_ptr<T> dataSeries) : m_DataSeries{dataSeries} {}
140 156
141 157 void setProperties(QCustomPlot &plot, SqpColorScale &colorScale) override
142 158 {
143 AxisSetter<T>::setProperties(m_DataSeries, plot, colorScale);
159 AxisSetter<T>::setProperties(plot, colorScale);
160 }
161
162 void setUnits(QCustomPlot &plot, SqpColorScale &colorScale) override
163 {
164 if (m_DataSeries) {
165 AxisSetter<T>::setUnits(*m_DataSeries, plot, colorScale);
166 }
167 else {
168 qCCritical(LOG_AxisRenderingUtils()) << "Can't set units: inconsistency between the "
169 "type of data series and the type supposed";
170 }
144 171 }
145 172
146 T &m_DataSeries;
173 std::shared_ptr<T> m_DataSeries;
147 174 };
148 175
149 176 } // namespace
150 177
151 178 QString formatValue(double value, const QCPAxis &axis)
152 179 {
153 180 // If the axis is a time axis, formats the value as a date
154 181 if (auto axisTicker = qSharedPointerDynamicCast<QCPAxisTickerDateTime>(axis.ticker())) {
155 182 return DateUtils::dateTime(value, axisTicker->dateTimeSpec()).toString(DATETIME_FORMAT);
156 183 }
157 184 else {
158 185 return QString::number(value, NUMBER_FORMAT, NUMBER_PRECISION);
159 186 }
160 187 }
161 188
162 std::unique_ptr<IAxisHelper>
163 IAxisHelperFactory::create(std::shared_ptr<IDataSeries> dataSeries) noexcept
189 std::unique_ptr<IAxisHelper> IAxisHelperFactory::create(const Variable &variable) noexcept
164 190 {
165 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
166 return std::make_unique<AxisHelper<ScalarSeries> >(*scalarSeries);
167 }
168 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
169 return std::make_unique<AxisHelper<SpectrogramSeries> >(*spectrogramSeries);
170 }
171 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
172 return std::make_unique<AxisHelper<VectorSeries> >(*vectorSeries);
173 }
174 else {
175 return std::make_unique<AxisHelper<IDataSeries> >(*dataSeries);
176 }
191 switch (variable.type()) {
192 case DataSeriesType::SCALAR:
193 return std::make_unique<AxisHelper<ScalarSeries> >(
194 std::dynamic_pointer_cast<ScalarSeries>(variable.dataSeries()));
195 case DataSeriesType::SPECTROGRAM:
196 return std::make_unique<AxisHelper<SpectrogramSeries> >(
197 std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()));
198 case DataSeriesType::VECTOR:
199 return std::make_unique<AxisHelper<VectorSeries> >(
200 std::dynamic_pointer_cast<VectorSeries>(variable.dataSeries()));
201 default:
202 // Creates default helper
203 break;
204 }
205
206 return std::make_unique<AxisHelper<IDataSeries> >(nullptr);
177 207 }
@@ -1,122 +1,126
1 1 #include "Visualization/PlottablesRenderingUtils.h"
2 2
3 3 #include <Common/ColorUtils.h>
4 4
5 5 #include <Data/ScalarSeries.h>
6 6 #include <Data/SpectrogramSeries.h>
7 7 #include <Data/VectorSeries.h>
8 8
9 #include <Variable/Variable.h>
10
9 11 #include <Visualization/qcustomplot.h>
10 12
11 13 Q_LOGGING_CATEGORY(LOG_PlottablesRenderingUtils, "PlottablesRenderingUtils")
12 14
13 15 namespace {
14 16
15 17 /**
16 18 * Delegate used to set plottables properties
17 19 */
18 20 template <typename T, typename Enabled = void>
19 21 struct PlottablesSetter {
20 static void setProperties(T &, PlottablesMap &)
22 static void setProperties(PlottablesMap &)
21 23 {
22 24 // Default implementation does nothing
23 25 qCCritical(LOG_PlottablesRenderingUtils())
24 26 << "Can't set plottables properties: unmanaged type of data";
25 27 }
26 28 };
27 29
28 30 /**
29 31 * Specialization of PlottablesSetter for scalars and vectors
30 32 * @sa ScalarSeries
31 33 * @sa VectorSeries
32 34 */
33 35 template <typename T>
34 36 struct PlottablesSetter<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
35 37 or std::is_base_of<VectorSeries, T>::value> > {
36 static void setProperties(T &dataSeries, PlottablesMap &plottables)
38 static void setProperties(PlottablesMap &plottables)
37 39 {
38 // Gets the number of components of the data series
39 dataSeries.lockRead();
40 auto componentCount = dataSeries.valuesData()->componentCount();
41 dataSeries.unlock();
40 // Finds the plottable with the highest index to determine the number of colors to generate
41 auto end = plottables.cend();
42 auto maxPlottableIndexIt
43 = std::max_element(plottables.cbegin(), end, [](const auto &it1, const auto &it2) {
44 return it1.first < it2.first;
45 });
46 auto componentCount = maxPlottableIndexIt != end ? maxPlottableIndexIt->first + 1 : 0;
42 47
43 48 // Generates colors for each component
44 49 auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount);
45 50
46 51 // For each component of the data series, creates a QCPGraph to add to the plot
47 52 for (auto i = 0; i < componentCount; ++i) {
48 auto graph = plottables.at(i);
49 graph->setPen(QPen{colors.at(i)});
53 auto graphIt = plottables.find(i);
54 if (graphIt != end) {
55 graphIt->second->setPen(QPen{colors.at(i)});
56 }
50 57 }
51 58 }
52 59 };
53 60
54 61 /**
55 62 * Specialization of PlottablesSetter for spectrograms
56 63 * @sa SpectrogramSeries
57 64 */
58 65 template <typename T>
59 66 struct PlottablesSetter<T,
60 67 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
61 static void setProperties(T &, PlottablesMap &plottables)
68 static void setProperties(PlottablesMap &plottables)
62 69 {
63 70 // Checks that for a spectrogram there is only one plottable, that is a colormap
64 71 if (plottables.size() != 1) {
65 72 return;
66 73 }
67 74
68 75 if (auto colormap = dynamic_cast<QCPColorMap *>(plottables.begin()->second)) {
69 76 colormap->setInterpolate(false); // No value interpolation
70 77 colormap->setTightBoundary(true);
71 78
72 79 // Finds color scale in the colormap's plot to associate with it
73 80 auto plot = colormap->parentPlot();
74 81 auto plotElements = plot->plotLayout()->elements(false);
75 82 for (auto plotElement : plotElements) {
76 83 if (auto colorScale = dynamic_cast<QCPColorScale *>(plotElement)) {
77 84 colormap->setColorScale(colorScale);
78 85 }
79 86 }
80 87
81 88 colormap->rescaleDataRange();
82 89 }
83 90 else {
84 91 qCCritical(LOG_PlottablesRenderingUtils()) << "Can't get colormap of the spectrogram";
85 92 }
86 93 }
87 94 };
88 95
89 96 /**
90 97 * Default implementation of IPlottablesHelper, which takes data series to set plottables properties
91 98 * @tparam T the data series' type
92 99 */
93 100 template <typename T>
94 101 struct PlottablesHelper : public IPlottablesHelper {
95 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
96
97 102 void setProperties(PlottablesMap &plottables) override
98 103 {
99 PlottablesSetter<T>::setProperties(m_DataSeries, plottables);
104 PlottablesSetter<T>::setProperties(plottables);
100 105 }
101
102 T &m_DataSeries;
103 106 };
104 107
105 108 } // namespace
106 109
107 110 std::unique_ptr<IPlottablesHelper>
108 IPlottablesHelperFactory::create(std::shared_ptr<IDataSeries> dataSeries) noexcept
111 IPlottablesHelperFactory::create(const Variable &variable) noexcept
109 112 {
110 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
111 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
112 }
113 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
114 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(*spectrogramSeries);
115 }
116 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
117 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
118 }
119 else {
120 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
113 switch (variable.type()) {
114 case DataSeriesType::SCALAR:
115 return std::make_unique<PlottablesHelper<ScalarSeries> >();
116 case DataSeriesType::SPECTROGRAM:
117 return std::make_unique<PlottablesHelper<SpectrogramSeries> >();
118 case DataSeriesType::VECTOR:
119 return std::make_unique<PlottablesHelper<VectorSeries> >();
120 default:
121 // Returns default helper
122 break;
121 123 }
124
125 return std::make_unique<PlottablesHelper<IDataSeries> >();
122 126 }
@@ -1,340 +1,361
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 4 #include <Data/DataSeriesUtils.h>
5 5 #include <Data/ScalarSeries.h>
6 6 #include <Data/SpectrogramSeries.h>
7 7 #include <Data/VectorSeries.h>
8 8
9 9 #include <Variable/Variable.h>
10 10
11 11 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
12 12
13 13 namespace {
14 14
15 15 class SqpDataContainer : public QCPGraphDataContainer {
16 16 public:
17 17 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
18 18 };
19 19
20 20 /**
21 21 * Struct used to create plottables, depending on the type of the data series from which to create
22 22 * them
23 23 * @tparam T the data series' type
24 24 * @remarks Default implementation can't create plottables
25 25 */
26 26 template <typename T, typename Enabled = void>
27 27 struct PlottablesCreator {
28 static PlottablesMap createPlottables(T &, QCustomPlot &)
28 static PlottablesMap createPlottables(QCustomPlot &)
29 29 {
30 30 qCCritical(LOG_DataSeries())
31 31 << QObject::tr("Can't create plottables: unmanaged data series type");
32 32 return {};
33 33 }
34 34 };
35 35
36 /**
37 * Specialization of PlottablesCreator for scalars and vectors
38 * @sa ScalarSeries
39 * @sa VectorSeries
40 */
41 template <typename T>
42 struct PlottablesCreator<T,
43 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
44 or std::is_base_of<VectorSeries, T>::value> > {
45 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
36 PlottablesMap createGraphs(QCustomPlot &plot, int nbGraphs)
46 37 {
47 38 PlottablesMap result{};
48 39
49 // Gets the number of components of the data series
50 dataSeries.lockRead();
51 auto componentCount = dataSeries.valuesData()->componentCount();
52 dataSeries.unlock();
53
54 // For each component of the data series, creates a QCPGraph to add to the plot
55 for (auto i = 0; i < componentCount; ++i) {
40 // Creates {nbGraphs} QCPGraph to add to the plot
41 for (auto i = 0; i < nbGraphs; ++i) {
56 42 auto graph = plot.addGraph();
57 43 result.insert({i, graph});
58 44 }
59 45
60 46 plot.replot();
61 47
62 48 return result;
63 49 }
50
51 /**
52 * Specialization of PlottablesCreator for scalars
53 * @sa ScalarSeries
54 */
55 template <typename T>
56 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value> > {
57 static PlottablesMap createPlottables(QCustomPlot &plot) { return createGraphs(plot, 1); }
58 };
59
60 /**
61 * Specialization of PlottablesCreator for vectors
62 * @sa VectorSeries
63 */
64 template <typename T>
65 struct PlottablesCreator<T, typename std::enable_if_t<std::is_base_of<VectorSeries, T>::value> > {
66 static PlottablesMap createPlottables(QCustomPlot &plot) { return createGraphs(plot, 3); }
64 67 };
65 68
66 69 /**
67 70 * Specialization of PlottablesCreator for spectrograms
68 71 * @sa SpectrogramSeries
69 72 */
70 73 template <typename T>
71 74 struct PlottablesCreator<T,
72 75 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
73 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
76 static PlottablesMap createPlottables(QCustomPlot &plot)
74 77 {
75 78 PlottablesMap result{};
76 79 result.insert({0, new QCPColorMap{plot.xAxis, plot.yAxis}});
77 80
78 81 plot.replot();
79 82
80 83 return result;
81 84 }
82 85 };
83 86
84 87 /**
85 88 * Struct used to update plottables, depending on the type of the data series from which to update
86 89 * them
87 90 * @tparam T the data series' type
88 91 * @remarks Default implementation can't update plottables
89 92 */
90 93 template <typename T, typename Enabled = void>
91 94 struct PlottablesUpdater {
92 95 static void setPlotYAxisRange(T &, const SqpRange &, QCustomPlot &)
93 96 {
94 97 qCCritical(LOG_VisualizationGraphHelper())
95 98 << QObject::tr("Can't set plot y-axis range: unmanaged data series type");
96 99 }
97 100
98 101 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
99 102 {
100 103 qCCritical(LOG_VisualizationGraphHelper())
101 104 << QObject::tr("Can't update plottables: unmanaged data series type");
102 105 }
103 106 };
104 107
105 108 /**
106 109 * Specialization of PlottablesUpdater for scalars and vectors
107 110 * @sa ScalarSeries
108 111 * @sa VectorSeries
109 112 */
110 113 template <typename T>
111 114 struct PlottablesUpdater<T,
112 115 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
113 116 or std::is_base_of<VectorSeries, T>::value> > {
114 117 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
115 118 {
116 119 auto minValue = 0., maxValue = 0.;
117 120
118 121 dataSeries.lockRead();
119 122 auto valuesBounds = dataSeries.valuesBounds(xAxisRange.m_TStart, xAxisRange.m_TEnd);
120 123 auto end = dataSeries.cend();
121 124 if (valuesBounds.first != end && valuesBounds.second != end) {
122 125 auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; };
123 126
124 127 minValue = rangeValue(valuesBounds.first->minValue());
125 128 maxValue = rangeValue(valuesBounds.second->maxValue());
126 129 }
127 130 dataSeries.unlock();
128 131
129 132 plot.yAxis->setRange(QCPRange{minValue, maxValue});
130 133 }
131 134
132 135 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
133 136 bool rescaleAxes)
134 137 {
135 138
136 139 // For each plottable to update, resets its data
137 140 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
138 141 for (const auto &plottable : plottables) {
139 142 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
140 143 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
141 144 graph->setData(dataContainer);
142 145
143 146 dataContainers.insert({plottable.first, dataContainer});
144 147 }
145 148 }
146 149 dataSeries.lockRead();
147 150
148 151 // - Gets the data of the series included in the current range
149 152 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
150 153 // and value data. The correct value is retrieved according to the index of the component
151 154 auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
152 155 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
153 156 for (const auto &dataContainer : dataContainers) {
154 157 auto componentIndex = dataContainer.first;
155 158 dataContainer.second->appendGraphData(
156 159 QCPGraphData(it->x(), it->value(componentIndex)));
157 160 }
158 161 }
159 162
160 163 dataSeries.unlock();
161 164
162 165 if (!plottables.empty()) {
163 166 auto plot = plottables.begin()->second->parentPlot();
164 167
165 168 if (rescaleAxes) {
166 169 plot->rescaleAxes();
167 170 }
168 171 }
169 172 }
170 173 };
171 174
172 175 /**
173 176 * Specialization of PlottablesUpdater for spectrograms
174 177 * @sa SpectrogramSeries
175 178 */
176 179 template <typename T>
177 180 struct PlottablesUpdater<T,
178 181 typename std::enable_if_t<std::is_base_of<SpectrogramSeries, T>::value> > {
179 182 static void setPlotYAxisRange(T &dataSeries, const SqpRange &xAxisRange, QCustomPlot &plot)
180 183 {
181 184 double min, max;
182 185 std::tie(min, max) = dataSeries.yBounds();
183 186
184 187 if (!std::isnan(min) && !std::isnan(max)) {
185 188 plot.yAxis->setRange(QCPRange{min, max});
186 189 }
187 190 }
188 191
189 192 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
190 193 bool rescaleAxes)
191 194 {
192 195 if (plottables.empty()) {
193 196 qCDebug(LOG_VisualizationGraphHelper())
194 197 << QObject::tr("Can't update spectrogram: no colormap has been associated");
195 198 return;
196 199 }
197 200
198 201 // Gets the colormap to update (normally there is only one colormap)
199 202 Q_ASSERT(plottables.size() == 1);
200 203 auto colormap = dynamic_cast<QCPColorMap *>(plottables.at(0));
201 204 Q_ASSERT(colormap != nullptr);
202 205
203 206 dataSeries.lockRead();
204 207
205 208 // Processing spectrogram data for display in QCustomPlot
206 209 auto its = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
207 210
208 211 // Computes logarithmic y-axis resolution for the spectrogram
209 212 auto yData = its.first->y();
210 213 auto yResolution = DataSeriesUtils::resolution(yData.begin(), yData.end(), true);
211 214
212 215 // Generates mesh for colormap
213 216 auto mesh = DataSeriesUtils::regularMesh(
214 217 its.first, its.second, DataSeriesUtils::Resolution{dataSeries.xResolution()},
215 218 yResolution);
216 219
217 220 dataSeries.unlock();
218 221
219 222 colormap->data()->setSize(mesh.m_NbX, mesh.m_NbY);
220 223 if (!mesh.isEmpty()) {
221 224 colormap->data()->setRange(
222 225 QCPRange{mesh.m_XMin, mesh.xMax()},
223 226 // y-axis range is converted to linear values
224 227 QCPRange{std::pow(10, mesh.m_YMin), std::pow(10, mesh.yMax())});
225 228
226 229 // Sets values
227 230 auto index = 0;
228 231 for (auto it = mesh.m_Data.begin(), end = mesh.m_Data.end(); it != end; ++it, ++index) {
229 232 auto xIndex = index % mesh.m_NbX;
230 233 auto yIndex = index / mesh.m_NbX;
231 234
232 235 colormap->data()->setCell(xIndex, yIndex, *it);
233 236
234 237 // Makes the NaN values to be transparent in the colormap
235 238 if (std::isnan(*it)) {
236 239 colormap->data()->setAlpha(xIndex, yIndex, 0);
237 240 }
238 241 }
239 242 }
240 243
241 244 // Rescales axes
242 245 auto plot = colormap->parentPlot();
243 246
244 247 if (rescaleAxes) {
245 248 plot->rescaleAxes();
246 249 }
247 250 }
248 251 };
249 252
250 253 /**
251 254 * Helper used to create/update plottables
252 255 */
253 256 struct IPlottablesHelper {
254 257 virtual ~IPlottablesHelper() noexcept = default;
255 258 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
256 259 virtual void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const = 0;
257 260 virtual void update(PlottablesMap &plottables, const SqpRange &range,
258 261 bool rescaleAxes = false) const = 0;
259 262 };
260 263
261 264 /**
262 265 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
263 266 * @tparam T the data series' type
264 267 */
265 268 template <typename T>
266 269 struct PlottablesHelper : public IPlottablesHelper {
267 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
270 explicit PlottablesHelper(std::shared_ptr<T> dataSeries) : m_DataSeries{dataSeries} {}
268 271
269 272 PlottablesMap create(QCustomPlot &plot) const override
270 273 {
271 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
274 return PlottablesCreator<T>::createPlottables(plot);
272 275 }
273 276
274 277 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
275 278 {
276 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
279 if (m_DataSeries) {
280 PlottablesUpdater<T>::updatePlottables(*m_DataSeries, plottables, range, rescaleAxes);
281 }
282 else {
283 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
284 "between the type of data series and the "
285 "type supposed";
286 }
277 287 }
278 288
279 289 void setYAxisRange(const SqpRange &xAxisRange, QCustomPlot &plot) const override
280 290 {
281 return PlottablesUpdater<T>::setPlotYAxisRange(m_DataSeries, xAxisRange, plot);
291 if (m_DataSeries) {
292 PlottablesUpdater<T>::setPlotYAxisRange(*m_DataSeries, xAxisRange, plot);
293 }
294 else {
295 qCCritical(LOG_VisualizationGraphHelper()) << "Can't update plottables: inconsistency "
296 "between the type of data series and the "
297 "type supposed";
298 }
282 299 }
283 300
284 T &m_DataSeries;
301 std::shared_ptr<T> m_DataSeries;
285 302 };
286 303
287 /// Creates IPlottablesHelper according to a data series
288 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
304 /// Creates IPlottablesHelper according to the type of data series a variable holds
305 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<Variable> variable) noexcept
289 306 {
290 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
291 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
292 }
293 else if (auto spectrogramSeries = std::dynamic_pointer_cast<SpectrogramSeries>(dataSeries)) {
294 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(*spectrogramSeries);
295 }
296 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
297 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
298 }
299 else {
300 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
307 switch (variable->type()) {
308 case DataSeriesType::SCALAR:
309 return std::make_unique<PlottablesHelper<ScalarSeries> >(
310 std::dynamic_pointer_cast<ScalarSeries>(variable->dataSeries()));
311 case DataSeriesType::SPECTROGRAM:
312 return std::make_unique<PlottablesHelper<SpectrogramSeries> >(
313 std::dynamic_pointer_cast<SpectrogramSeries>(variable->dataSeries()));
314 case DataSeriesType::VECTOR:
315 return std::make_unique<PlottablesHelper<VectorSeries> >(
316 std::dynamic_pointer_cast<VectorSeries>(variable->dataSeries()));
317 default:
318 // Creates default helper
319 break;
301 320 }
321
322 return std::make_unique<PlottablesHelper<IDataSeries> >(nullptr);
302 323 }
303 324
304 325 } // namespace
305 326
306 327 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
307 328 QCustomPlot &plot) noexcept
308 329 {
309 330 if (variable) {
310 auto helper = createHelper(variable->dataSeries());
331 auto helper = createHelper(variable);
311 332 auto plottables = helper->create(plot);
312 333 return plottables;
313 334 }
314 335 else {
315 336 qCDebug(LOG_VisualizationGraphHelper())
316 337 << QObject::tr("Can't create graph plottables : the variable is null");
317 338 return PlottablesMap{};
318 339 }
319 340 }
320 341
321 342 void VisualizationGraphHelper::setYAxisRange(std::shared_ptr<Variable> variable,
322 343 QCustomPlot &plot) noexcept
323 344 {
324 345 if (variable) {
325 auto helper = createHelper(variable->dataSeries());
346 auto helper = createHelper(variable);
326 347 helper->setYAxisRange(variable->range(), plot);
327 348 }
328 349 else {
329 350 qCDebug(LOG_VisualizationGraphHelper())
330 351 << QObject::tr("Can't set y-axis range of plot: the variable is null");
331 352 }
332 353 }
333 354
334 355 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
335 std::shared_ptr<IDataSeries> dataSeries,
356 std::shared_ptr<Variable> variable,
336 357 const SqpRange &dateTime)
337 358 {
338 auto helper = createHelper(dataSeries);
359 auto helper = createHelper(variable);
339 360 helper->update(plottables, dateTime);
340 361 }
@@ -1,332 +1,338
1 1 #include "Visualization/VisualizationGraphRenderingDelegate.h"
2 2 #include "Visualization/AxisRenderingUtils.h"
3 3 #include "Visualization/ColorScaleEditor.h"
4 4 #include "Visualization/PlottablesRenderingUtils.h"
5 5 #include "Visualization/SqpColorScale.h"
6 6 #include "Visualization/VisualizationGraphWidget.h"
7 7 #include "Visualization/qcustomplot.h"
8 8
9 9 #include <Common/DateUtils.h>
10 10
11 11 #include <Data/IDataSeries.h>
12 #include <Variable/Variable.h>
12 13
13 14 #include <SqpApplication.h>
14 15
15 16 namespace {
16 17
17 18 /// Name of the axes layer in QCustomPlot
18 19 const auto AXES_LAYER = QStringLiteral("axes");
19 20
20 21 /// Icon used to show x-axis properties
21 22 const auto HIDE_AXIS_ICON_PATH = QStringLiteral(":/icones/down.png");
22 23
23 24 /// Name of the overlay layer in QCustomPlot
24 25 const auto OVERLAY_LAYER = QStringLiteral("overlay");
25 26
26 27 /// Pixmap used to show x-axis properties
27 28 const auto SHOW_AXIS_ICON_PATH = QStringLiteral(":/icones/up.png");
28 29
29 30 /// Tooltip format for graphs
30 31 const auto GRAPH_TOOLTIP_FORMAT = QStringLiteral("key: %1\nvalue: %2");
31 32
32 33 /// Tooltip format for colormaps
33 34 const auto COLORMAP_TOOLTIP_FORMAT = QStringLiteral("x: %1\ny: %2\nvalue: %3");
34 35
35 36 /// Offset used to shift the tooltip of the mouse
36 37 const auto TOOLTIP_OFFSET = QPoint{20, 20};
37 38
38 39 /// Tooltip display rectangle (the tooltip is hidden when the mouse leaves this rectangle)
39 40 const auto TOOLTIP_RECT = QRect{10, 10, 10, 10};
40 41
41 42 /// Timeout after which the tooltip is displayed
42 43 const auto TOOLTIP_TIMEOUT = 500;
43 44
44 45 void initPointTracerStyle(QCPItemTracer &tracer) noexcept
45 46 {
46 47 tracer.setInterpolating(false);
47 48 tracer.setStyle(QCPItemTracer::tsCircle);
48 49 tracer.setSize(3);
49 50 tracer.setPen(QPen(Qt::black));
50 51 tracer.setBrush(Qt::black);
51 52 tracer.setSelectable(false);
52 53 }
53 54
54 55 QPixmap pixmap(const QString &iconPath) noexcept
55 56 {
56 57 return QIcon{iconPath}.pixmap(QSize{16, 16});
57 58 }
58 59
59 60 void initClosePixmapStyle(QCPItemPixmap &pixmap) noexcept
60 61 {
61 62 // Icon
62 63 pixmap.setPixmap(
63 64 sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton).pixmap(QSize{16, 16}));
64 65
65 66 // Position
66 67 pixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
67 68 pixmap.topLeft->setCoords(1, 0);
68 69 pixmap.setClipToAxisRect(false);
69 70
70 71 // Can be selected
71 72 pixmap.setSelectable(true);
72 73 }
73 74
74 75 void initXAxisPixmapStyle(QCPItemPixmap &itemPixmap) noexcept
75 76 {
76 77 // Icon
77 78 itemPixmap.setPixmap(pixmap(HIDE_AXIS_ICON_PATH));
78 79
79 80 // Position
80 81 itemPixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
81 82 itemPixmap.topLeft->setCoords(0, 1);
82 83 itemPixmap.setClipToAxisRect(false);
83 84
84 85 // Can be selected
85 86 itemPixmap.setSelectable(true);
86 87 }
87 88
88 89 void initTitleTextStyle(QCPItemText &text) noexcept
89 90 {
90 91 // Font and background styles
91 92 text.setColor(Qt::gray);
92 93 text.setBrush(Qt::white);
93 94
94 95 // Position
95 96 text.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
96 97 text.position->setType(QCPItemPosition::ptAxisRectRatio);
97 98 text.position->setCoords(0.5, 0);
98 99 text.setSelectable(false);
99 100 }
100 101
101 102 /**
102 103 * Returns the cell index (x or y) of a colormap according to the coordinate passed in parameter.
103 104 * This method handles the fact that a colormap axis can be logarithmic or linear.
104 105 * @param colormap the colormap for which to calculate the index
105 106 * @param coord the coord to convert to cell index
106 107 * @param xCoord calculates the x index if true, calculates y index if false
107 108 * @return the cell index
108 109 */
109 110 int colorMapCellIndex(const QCPColorMap &colormap, double coord, bool xCoord)
110 111 {
111 112 // Determines the axis of the colormap according to xCoord, and whether it is logarithmic or not
112 113 auto isLogarithmic = (xCoord ? colormap.keyAxis() : colormap.valueAxis())->scaleType()
113 114 == QCPAxis::stLogarithmic;
114 115
115 116 if (isLogarithmic) {
116 117 // For a logarithmic axis we can't use the conversion method of colormap, so we calculate
117 118 // the index manually based on the position of the coordinate on the axis
118 119
119 120 // Gets the axis range and the number of values between range bounds to calculate the step
120 121 // between each value of the range
121 122 auto range = xCoord ? colormap.data()->keyRange() : colormap.data()->valueRange();
122 123 auto nbValues = (xCoord ? colormap.data()->keySize() : colormap.data()->valueSize()) - 1;
123 124 auto valueStep
124 125 = (std::log10(range.upper) - std::log10(range.lower)) / static_cast<double>(nbValues);
125 126
126 127 // According to the coord position, calculates the closest index in the range
127 128 return std::round((std::log10(coord) - std::log10(range.lower)) / valueStep);
128 129 }
129 130 else {
130 131 // For a linear axis, we use the conversion method of colormap
131 132 int index;
132 133 if (xCoord) {
133 134 colormap.data()->coordToCell(coord, 0., &index, nullptr);
134 135 }
135 136 else {
136 137 colormap.data()->coordToCell(0., coord, nullptr, &index);
137 138 }
138 139
139 140 return index;
140 141 }
141 142 }
142 143
143 144 } // namespace
144 145
145 146 struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate {
146 147 explicit VisualizationGraphRenderingDelegatePrivate(VisualizationGraphWidget &graphWidget)
147 148 : m_Plot{graphWidget.plot()},
148 149 m_PointTracer{new QCPItemTracer{&m_Plot}},
149 150 m_TracerTimer{},
150 151 m_ClosePixmap{new QCPItemPixmap{&m_Plot}},
151 152 m_TitleText{new QCPItemText{&m_Plot}},
152 153 m_XAxisPixmap{new QCPItemPixmap{&m_Plot}},
153 154 m_ShowXAxis{true},
154 155 m_XAxisLabel{},
155 156 m_ColorScale{SqpColorScale{m_Plot}}
156 157 {
157 158 initPointTracerStyle(*m_PointTracer);
158 159
159 160 m_TracerTimer.setInterval(TOOLTIP_TIMEOUT);
160 161 m_TracerTimer.setSingleShot(true);
161 162
162 163 // Inits "close button" in plot overlay
163 164 m_ClosePixmap->setLayer(OVERLAY_LAYER);
164 165 initClosePixmapStyle(*m_ClosePixmap);
165 166
166 167 // Connects pixmap selection to graph widget closing
167 168 QObject::connect(&m_Plot, &QCustomPlot::itemClick,
168 169 [&graphWidget, this](auto item, auto mouseEvent) {
169 170 if (item == m_ClosePixmap) {
170 171 graphWidget.close();
171 172 }
172 173 });
173 174
174 175 // Inits graph name in plot overlay
175 176 m_TitleText->setLayer(OVERLAY_LAYER);
176 177 m_TitleText->setText(graphWidget.name());
177 178 initTitleTextStyle(*m_TitleText);
178 179
179 180 // Inits "show x-axis button" in plot overlay
180 181 m_XAxisPixmap->setLayer(OVERLAY_LAYER);
181 182 initXAxisPixmapStyle(*m_XAxisPixmap);
182 183
183 184 // Connects pixmap selection to graph x-axis showing/hiding
184 185 QObject::connect(&m_Plot, &QCustomPlot::itemClick, [this](auto item, auto mouseEvent) {
185 186 if (m_XAxisPixmap == item) {
186 187 // Changes the selection state and refreshes the x-axis
187 188 m_ShowXAxis = !m_ShowXAxis;
188 189 this->updateXAxisState();
189 190 m_Plot.layer(AXES_LAYER)->replot();
190 191
191 192 // Deselects the x-axis pixmap and updates icon
192 193 m_XAxisPixmap->setPixmap(
193 194 pixmap(m_ShowXAxis ? HIDE_AXIS_ICON_PATH : SHOW_AXIS_ICON_PATH));
194 195 m_Plot.layer(OVERLAY_LAYER)->replot();
195 196 }
196 197 });
197 198 }
198 199
199 200 /// Updates state of x-axis according to the current selection of x-axis pixmap
200 201 /// @remarks the method doesn't call plot refresh
201 202 void updateXAxisState() noexcept
202 203 {
203 204 m_Plot.xAxis->setTickLabels(m_ShowXAxis);
204 205 m_Plot.xAxis->setLabel(m_ShowXAxis ? m_XAxisLabel : QString{});
205 206 }
206 207
207 208 QCustomPlot &m_Plot;
208 209 QCPItemTracer *m_PointTracer;
209 210 QTimer m_TracerTimer;
210 211 QCPItemPixmap *m_ClosePixmap; /// Graph's close button
211 212 QCPItemText *m_TitleText; /// Graph's title
212 213 QCPItemPixmap *m_XAxisPixmap;
213 214 bool m_ShowXAxis; /// X-axis properties are shown or hidden
214 215 QString m_XAxisLabel;
215 216 SqpColorScale m_ColorScale; /// Color scale used for some types of graphs (as spectrograms)
216 217 };
217 218
218 219 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(
219 220 VisualizationGraphWidget &graphWidget)
220 221 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(graphWidget)}
221 222 {
222 223 }
223 224
224 225 void VisualizationGraphRenderingDelegate::onMouseDoubleClick(QMouseEvent *event) noexcept
225 226 {
226 227 // Opens color scale editor if color scale is double clicked
227 228 auto colorScale = static_cast<QCPColorScale *>(impl->m_Plot.layoutElementAt(event->pos()));
228 229 if (impl->m_ColorScale.m_Scale == colorScale) {
229 230 if (ColorScaleEditor{impl->m_ColorScale}.exec() == QDialog::Accepted) {
230 231 impl->m_Plot.replot();
231 232 }
232 233 }
233 234 }
234 235
235 236 void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept
236 237 {
237 238 // Cancels pending refresh
238 239 impl->m_TracerTimer.disconnect();
239 240
240 241 // Reinits tracers
241 242 impl->m_PointTracer->setGraph(nullptr);
242 243 impl->m_PointTracer->setVisible(false);
243 244 impl->m_Plot.replot();
244 245
245 246 QString tooltip{};
246 247
247 248 // Gets the graph under the mouse position
248 249 auto eventPos = event->pos();
249 250 if (auto graph = qobject_cast<QCPGraph *>(impl->m_Plot.plottableAt(eventPos))) {
250 251 auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x());
251 252 auto graphData = graph->data();
252 253
253 254 // Gets the closest data point to the mouse
254 255 auto graphDataIt = graphData->findBegin(mouseKey);
255 256 if (graphDataIt != graphData->constEnd()) {
256 257 // Sets tooltip
257 258 auto key = formatValue(graphDataIt->key, *graph->keyAxis());
258 259 auto value = formatValue(graphDataIt->value, *graph->valueAxis());
259 260 tooltip = GRAPH_TOOLTIP_FORMAT.arg(key, value);
260 261
261 262 // Displays point tracer
262 263 impl->m_PointTracer->setGraph(graph);
263 264 impl->m_PointTracer->setGraphKey(graphDataIt->key);
264 265 impl->m_PointTracer->setLayer(
265 266 impl->m_Plot.layer("main")); // Tracer is set on top of the plot's main layer
266 267 impl->m_PointTracer->setVisible(true);
267 268 impl->m_Plot.replot();
268 269 }
269 270 }
270 271 else if (auto colorMap = qobject_cast<QCPColorMap *>(impl->m_Plot.plottableAt(eventPos))) {
271 272 // Gets x and y coords
272 273 auto x = colorMap->keyAxis()->pixelToCoord(eventPos.x());
273 274 auto y = colorMap->valueAxis()->pixelToCoord(eventPos.y());
274 275
275 276 // Calculates x and y cell indexes, and retrieves the underlying value
276 277 auto xCellIndex = colorMapCellIndex(*colorMap, x, true);
277 278 auto yCellIndex = colorMapCellIndex(*colorMap, y, false);
278 279 auto value = colorMap->data()->cell(xCellIndex, yCellIndex);
279 280
280 281 // Sets tooltips
281 282 tooltip = COLORMAP_TOOLTIP_FORMAT.arg(formatValue(x, *colorMap->keyAxis()),
282 283 formatValue(y, *colorMap->valueAxis()),
283 284 formatValue(value, *colorMap->colorScale()->axis()));
284 285 }
285 286
286 287 if (!tooltip.isEmpty()) {
287 288 // Starts timer to show tooltip after timeout
288 289 auto showTooltip = [tooltip, eventPos, this]() {
289 290 QToolTip::showText(impl->m_Plot.mapToGlobal(eventPos) + TOOLTIP_OFFSET, tooltip,
290 291 &impl->m_Plot, TOOLTIP_RECT);
291 292 };
292 293
293 294 QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTooltip);
294 295 impl->m_TracerTimer.start();
295 296 }
296 297 }
297 298
298 299 void VisualizationGraphRenderingDelegate::onPlotUpdated() noexcept
299 300 {
300 301 // Updates color scale bounds
301 302 impl->m_ColorScale.updateDataRange();
302 303 impl->m_Plot.replot();
303 304 }
304 305
305 void VisualizationGraphRenderingDelegate::setAxesProperties(
306 std::shared_ptr<IDataSeries> dataSeries) noexcept
306 void VisualizationGraphRenderingDelegate::setAxesUnits(const Variable &variable) noexcept
307 307 {
308 // Stores x-axis label to be able to retrieve it when x-axis pixmap is unselected
309 impl->m_XAxisLabel = dataSeries->xAxisUnit().m_Name;
310 308
311 auto axisHelper = IAxisHelperFactory::create(dataSeries);
312 axisHelper->setProperties(impl->m_Plot, impl->m_ColorScale);
309 auto axisHelper = IAxisHelperFactory::create(variable);
310 axisHelper->setUnits(impl->m_Plot, impl->m_ColorScale);
311
312 // Stores x-axis label to be able to retrieve it when x-axis pixmap is unselected
313 impl->m_XAxisLabel = impl->m_Plot.xAxis->label();
313 314
314 315 // Updates x-axis state
315 316 impl->updateXAxisState();
316 317
317 318 impl->m_Plot.layer(AXES_LAYER)->replot();
318 319 }
319 320
320 void VisualizationGraphRenderingDelegate::setPlottablesProperties(
321 std::shared_ptr<IDataSeries> dataSeries, PlottablesMap &plottables) noexcept
321 void VisualizationGraphRenderingDelegate::setGraphProperties(const Variable &variable,
322 PlottablesMap &plottables) noexcept
322 323 {
323 auto plottablesHelper = IPlottablesHelperFactory::create(dataSeries);
324 // Axes' properties
325 auto axisHelper = IAxisHelperFactory::create(variable);
326 axisHelper->setProperties(impl->m_Plot, impl->m_ColorScale);
327
328 // Plottables' properties
329 auto plottablesHelper = IPlottablesHelperFactory::create(variable);
324 330 plottablesHelper->setProperties(plottables);
325 331 }
326 332
327 333 void VisualizationGraphRenderingDelegate::showGraphOverlay(bool show) noexcept
328 334 {
329 335 auto overlay = impl->m_Plot.layer(OVERLAY_LAYER);
330 336 overlay->setVisible(show);
331 337 overlay->replot();
332 338 }
@@ -1,1004 +1,1055
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationCursorItem.h"
4 4 #include "Visualization/VisualizationDefs.h"
5 5 #include "Visualization/VisualizationGraphHelper.h"
6 6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
7 7 #include "Visualization/VisualizationMultiZoneSelectionDialog.h"
8 8 #include "Visualization/VisualizationSelectionZoneItem.h"
9 9 #include "Visualization/VisualizationSelectionZoneManager.h"
10 10 #include "Visualization/VisualizationWidget.h"
11 11 #include "Visualization/VisualizationZoneWidget.h"
12 12 #include "ui_VisualizationGraphWidget.h"
13 13
14 14 #include <Actions/ActionsGuiController.h>
15 15 #include <Common/MimeTypesDef.h>
16 16 #include <Data/ArrayData.h>
17 17 #include <Data/IDataSeries.h>
18 18 #include <Data/SpectrogramSeries.h>
19 19 #include <DragAndDrop/DragDropGuiController.h>
20 20 #include <Settings/SqpSettingsDefs.h>
21 21 #include <SqpApplication.h>
22 22 #include <Time/TimeController.h>
23 23 #include <Variable/Variable.h>
24 24 #include <Variable/VariableController.h>
25 25
26 26 #include <unordered_map>
27 27
28 28 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
29 29
30 30 namespace {
31 31
32 32 /// Key pressed to enable drag&drop in all modes
33 33 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
34 34
35 35 /// Key pressed to enable zoom on horizontal axis
36 36 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
37 37
38 38 /// Key pressed to enable zoom on vertical axis
39 39 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
40 40
41 41 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
42 42 const auto PAN_SPEED = 5;
43 43
44 44 /// Key pressed to enable a calibration pan
45 45 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
46 46
47 47 /// Key pressed to enable multi selection of selection zones
48 48 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
49 49
50 50 /// Minimum size for the zoom box, in percentage of the axis range
51 51 const auto ZOOM_BOX_MIN_SIZE = 0.8;
52 52
53 53 /// Format of the dates appearing in the label of a cursor
54 54 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
55 55
56 56 } // namespace
57 57
58 58 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
59 59
60 60 explicit VisualizationGraphWidgetPrivate(const QString &name)
61 61 : m_Name{name},
62 62 m_Flags{GraphFlag::EnableAll},
63 63 m_IsCalibration{false},
64 64 m_RenderingDelegate{nullptr}
65 65 {
66 66 }
67 67
68 void updateData(PlottablesMap &plottables, std::shared_ptr<IDataSeries> dataSeries,
68 void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
69 69 const SqpRange &range)
70 70 {
71 VisualizationGraphHelper::updateData(plottables, dataSeries, range);
71 VisualizationGraphHelper::updateData(plottables, variable, range);
72 72
73 73 // Prevents that data has changed to update rendering
74 74 m_RenderingDelegate->onPlotUpdated();
75 75 }
76 76
77 77 QString m_Name;
78 78 // 1 variable -> n qcpplot
79 79 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
80 80 GraphFlags m_Flags;
81 81 bool m_IsCalibration;
82 82 /// Delegate used to attach rendering features to the plot
83 83 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
84 84
85 85 QCPItemRect *m_DrawingZoomRect = nullptr;
86 86 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
87 87
88 88 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
89 89 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
90 90
91 91 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
92 92 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
93 93 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
94 94
95 95 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
96 96
97 bool m_VariableAutoRangeOnInit = true;
98
97 99 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
98 100 {
99 101 removeDrawingRect(plot);
100 102
101 103 auto axisPos = posToAxisPos(pos, plot);
102 104
103 105 m_DrawingZoomRect = new QCPItemRect{&plot};
104 106 QPen p;
105 107 p.setWidth(2);
106 108 m_DrawingZoomRect->setPen(p);
107 109
108 110 m_DrawingZoomRect->topLeft->setCoords(axisPos);
109 111 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
110 112 }
111 113
112 114 void removeDrawingRect(QCustomPlot &plot)
113 115 {
114 116 if (m_DrawingZoomRect) {
115 117 plot.removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
116 118 m_DrawingZoomRect = nullptr;
117 119 plot.replot(QCustomPlot::rpQueuedReplot);
118 120 }
119 121 }
120 122
121 123 void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph)
122 124 {
123 125 endDrawingZone(graph);
124 126
125 127 auto axisPos = posToAxisPos(pos, graph->plot());
126 128
127 129 m_DrawingZone = new VisualizationSelectionZoneItem{&graph->plot()};
128 130 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
129 131 m_DrawingZone->setEditionEnabled(false);
130 132 }
131 133
132 134 void endDrawingZone(VisualizationGraphWidget *graph)
133 135 {
134 136 if (m_DrawingZone) {
135 137 auto drawingZoneRange = m_DrawingZone->range();
136 138 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
137 139 m_DrawingZone->setEditionEnabled(true);
138 140 addSelectionZone(m_DrawingZone);
139 141 }
140 142 else {
141 143 graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot
142 144 }
143 145
144 146 graph->plot().replot(QCustomPlot::rpQueuedReplot);
145 147 m_DrawingZone = nullptr;
146 148 }
147 149 }
148 150
149 151 void setSelectionZonesEditionEnabled(bool value)
150 152 {
151 153 for (auto s : m_SelectionZones) {
152 154 s->setEditionEnabled(value);
153 155 }
154 156 }
155 157
156 158 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
157 159
158 160 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos,
159 161 const QCustomPlot &plot) const
160 162 {
161 163 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
162 164 auto minDistanceToZone = -1;
163 165 for (auto zone : m_SelectionZones) {
164 166 auto distanceToZone = zone->selectTest(pos, false);
165 167 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
166 168 && distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
167 169 selectionZoneItemUnderCursor = zone;
168 170 }
169 171 }
170 172
171 173 return selectionZoneItemUnderCursor;
172 174 }
173 175
174 176 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
175 177 const QCustomPlot &plot) const
176 178 {
177 179 QVector<VisualizationSelectionZoneItem *> zones;
178 180 for (auto zone : m_SelectionZones) {
179 181 auto distanceToZone = zone->selectTest(pos, false);
180 182 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
181 183 zones << zone;
182 184 }
183 185 }
184 186
185 187 return zones;
186 188 }
187 189
188 190 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
189 191 {
190 192 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
191 193 zone->moveToTop();
192 194 m_SelectionZones.removeAll(zone);
193 195 m_SelectionZones.append(zone);
194 196 }
195 197 }
196 198
197 199 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
198 200 {
199 201 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
200 202 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
201 203 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
202 204 }
203 205
204 206 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
205 207 {
206 208 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
207 209 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
208 210 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
209 211 }
210 212 };
211 213
212 214 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
213 215 : VisualizationDragWidget{parent},
214 216 ui{new Ui::VisualizationGraphWidget},
215 217 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
216 218 {
217 219 ui->setupUi(this);
218 220
219 221 // 'Close' options : widget is deleted when closed
220 222 setAttribute(Qt::WA_DeleteOnClose);
221 223
222 224 // Set qcpplot properties :
223 225 // - zoom is enabled
224 226 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
225 227 ui->widget->setInteractions(QCP::iRangeZoom);
226 228 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical);
227 229
228 230 // The delegate must be initialized after the ui as it uses the plot
229 231 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
230 232
231 233 // Init the cursors
232 234 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
233 235 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
234 236 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
235 237 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
236 238
237 239 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
238 240 connect(ui->widget, &QCustomPlot::mouseRelease, this,
239 241 &VisualizationGraphWidget::onMouseRelease);
240 242 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
241 243 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
242 244 connect(ui->widget, &QCustomPlot::mouseDoubleClick, this,
243 245 &VisualizationGraphWidget::onMouseDoubleClick);
244 246 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
245 247 &QCPAxis::rangeChanged),
246 248 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
247 249
248 250 // Activates menu when right clicking on the graph
249 251 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
250 252 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
251 253 &VisualizationGraphWidget::onGraphMenuRequested);
252 254
253 255 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
254 256 &VariableController::onRequestDataLoading);
255 257
256 258 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
257 259 &VisualizationGraphWidget::onUpdateVarDisplaying);
258 260
259 261 #ifdef Q_OS_MAC
260 262 plot().setPlottingHint(QCP::phFastPolylines, true);
261 263 #endif
262 264 }
263 265
264 266
265 267 VisualizationGraphWidget::~VisualizationGraphWidget()
266 268 {
267 269 delete ui;
268 270 }
269 271
270 272 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
271 273 {
272 274 auto parent = parentWidget();
273 275 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
274 276 parent = parent->parentWidget();
275 277 }
276 278
277 279 return qobject_cast<VisualizationZoneWidget *>(parent);
278 280 }
279 281
280 282 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
281 283 {
282 284 auto parent = parentWidget();
283 285 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
284 286 parent = parent->parentWidget();
285 287 }
286 288
287 289 return qobject_cast<VisualizationWidget *>(parent);
288 290 }
289 291
290 292 void VisualizationGraphWidget::setFlags(GraphFlags flags)
291 293 {
292 294 impl->m_Flags = std::move(flags);
293 295 }
294 296
295 297 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
296 298 {
297 // Uses delegate to create the qcpplot components according to the variable
298 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
299 /// Lambda used to set graph's units and range according to the variable passed in parameter
300 auto loadRange = [this](std::shared_ptr<Variable> variable, const SqpRange &range) {
301 impl->m_RenderingDelegate->setAxesUnits(*variable);
302
303 this->setFlags(GraphFlag::DisableAll);
304 setGraphRange(range);
305 this->setFlags(GraphFlag::EnableAll);
299 306
300 if (auto dataSeries = variable->dataSeries()) {
301 // Set axes properties according to the units of the data series
302 impl->m_RenderingDelegate->setAxesProperties(dataSeries);
307 emit requestDataLoading({variable}, range, false);
308 };
303 309
304 // Sets rendering properties for the new plottables
305 // Warning: this method must be called after setAxesProperties(), as it can access to some
306 // axes properties that have to be initialized
307 impl->m_RenderingDelegate->setPlottablesProperties(dataSeries, createdPlottables);
310 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
311
312 // Calls update of graph's range and units when the data of the variable have been initialized.
313 // Note: we use QueuedConnection here as the update event must be called in the UI thread
314 connect(variable.get(), &Variable::dataInitialized, this,
315 [ varW = std::weak_ptr<Variable>{variable}, range, loadRange, this ]() {
316 if (auto var = varW.lock()) {
317 // If the variable is the first added in the graph, we load its range
318 auto firstVariableInGraph = range == INVALID_RANGE;
319 auto loadedRange = graphRange();
320 if (impl->m_VariableAutoRangeOnInit) {
321 loadedRange = firstVariableInGraph ? var->range() : range;
322 }
323 loadRange(var, loadedRange);
324 setYRange(var);
308 325 }
326 },
327 Qt::QueuedConnection);
309 328
310 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
329 // Uses delegate to create the qcpplot components according to the variable
330 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
311 331
312 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
332 // Sets graph properties
333 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
313 334
314 this->setFlags(GraphFlag::DisableAll);
315 this->setGraphRange(range);
316 this->setFlags(GraphFlag::EnableAll);
335 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
317 336
318 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, false);
337 // If the variable already has its data loaded, load its units and its range in the graph
338 if (variable->dataSeries() != nullptr) {
339 loadRange(variable, range);
340 }
319 341
320 342 emit variableAdded(variable);
321 343 }
322 344
323 345 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
324 346 {
325 347 // Each component associated to the variable :
326 348 // - is removed from qcpplot (which deletes it)
327 349 // - is no longer referenced in the map
328 350 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
329 351 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
330 352 emit variableAboutToBeRemoved(variable);
331 353
332 354 auto &plottablesMap = variableIt->second;
333 355
334 356 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
335 357 plottableIt != plottableEnd;) {
336 358 ui->widget->removePlottable(plottableIt->second);
337 359 plottableIt = plottablesMap.erase(plottableIt);
338 360 }
339 361
340 362 impl->m_VariableToPlotMultiMap.erase(variableIt);
341 363 }
342 364
343 365 // Updates graph
344 366 ui->widget->replot();
345 367 }
346 368
347 369 QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
348 370 {
349 371 auto variables = QList<std::shared_ptr<Variable> >{};
350 372 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
351 373 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
352 374 variables << it->first;
353 375 }
354 376
355 377 return variables;
356 378 }
357 379
358 380 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
359 381 {
360 382 if (!variable) {
361 383 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
362 384 return;
363 385 }
364 386
365 387 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
366 388 }
367 389
368 390 SqpRange VisualizationGraphWidget::graphRange() const noexcept
369 391 {
370 392 auto graphRange = ui->widget->xAxis->range();
371 393 return SqpRange{graphRange.lower, graphRange.upper};
372 394 }
373 395
374 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
396 void VisualizationGraphWidget::setGraphRange(const SqpRange &range, bool calibration)
375 397 {
376 398 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
399
400 if (calibration) {
401 impl->m_IsCalibration = true;
402 }
403
377 404 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
378 405 ui->widget->replot();
406
407 if (calibration) {
408 impl->m_IsCalibration = false;
409 }
410
379 411 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
380 412 }
381 413
414 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
415 {
416 impl->m_VariableAutoRangeOnInit = value;
417 }
418
382 419 QVector<SqpRange> VisualizationGraphWidget::selectionZoneRanges() const
383 420 {
384 421 QVector<SqpRange> ranges;
385 422 for (auto zone : impl->m_SelectionZones) {
386 423 ranges << zone->range();
387 424 }
388 425
389 426 return ranges;
390 427 }
391 428
392 429 void VisualizationGraphWidget::addSelectionZones(const QVector<SqpRange> &ranges)
393 430 {
394 431 for (const auto &range : ranges) {
395 432 // note: ownership is transfered to QCustomPlot
396 433 auto zone = new VisualizationSelectionZoneItem(&plot());
397 434 zone->setRange(range.m_TStart, range.m_TEnd);
398 435 impl->addSelectionZone(zone);
399 436 }
400 437
401 438 plot().replot(QCustomPlot::rpQueuedReplot);
402 439 }
403 440
441 VisualizationSelectionZoneItem *VisualizationGraphWidget::addSelectionZone(const QString &name,
442 const SqpRange &range)
443 {
444 // note: ownership is transfered to QCustomPlot
445 auto zone = new VisualizationSelectionZoneItem(&plot());
446 zone->setName(name);
447 zone->setRange(range.m_TStart, range.m_TEnd);
448 impl->addSelectionZone(zone);
449
450 plot().replot(QCustomPlot::rpQueuedReplot);
451
452 return zone;
453 }
454
404 455 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
405 456 {
406 457 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
407 458
408 459 if (impl->m_HoveredZone == selectionZone) {
409 460 impl->m_HoveredZone = nullptr;
410 461 setCursor(Qt::ArrowCursor);
411 462 }
412 463
413 464 impl->m_SelectionZones.removeAll(selectionZone);
414 465 plot().removeItem(selectionZone);
415 466 plot().replot(QCustomPlot::rpQueuedReplot);
416 467 }
417 468
418 469 void VisualizationGraphWidget::undoZoom()
419 470 {
420 471 auto zoom = impl->m_ZoomStack.pop();
421 472 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
422 473 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
423 474
424 475 axisX->setRange(zoom.first);
425 476 axisY->setRange(zoom.second);
426 477
427 478 plot().replot(QCustomPlot::rpQueuedReplot);
428 479 }
429 480
430 481 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
431 482 {
432 483 if (visitor) {
433 484 visitor->visit(this);
434 485 }
435 486 else {
436 487 qCCritical(LOG_VisualizationGraphWidget())
437 488 << tr("Can't visit widget : the visitor is null");
438 489 }
439 490 }
440 491
441 492 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
442 493 {
443 494 auto isSpectrogram = [](const auto &variable) {
444 495 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
445 496 };
446 497
447 498 // - A spectrogram series can't be dropped on graph with existing plottables
448 499 // - No data series can be dropped on graph with existing spectrogram series
449 500 return isSpectrogram(variable)
450 501 ? impl->m_VariableToPlotMultiMap.empty()
451 502 : std::none_of(
452 503 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
453 504 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
454 505 }
455 506
456 507 bool VisualizationGraphWidget::contains(const Variable &variable) const
457 508 {
458 509 // Finds the variable among the keys of the map
459 510 auto variablePtr = &variable;
460 511 auto findVariable
461 512 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
462 513
463 514 auto end = impl->m_VariableToPlotMultiMap.cend();
464 515 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
465 516 return it != end;
466 517 }
467 518
468 519 QString VisualizationGraphWidget::name() const
469 520 {
470 521 return impl->m_Name;
471 522 }
472 523
473 524 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
474 525 {
475 526 auto mimeData = new QMimeData;
476 527
477 528 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position, plot());
478 529 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
479 530 && selectionZoneItemUnderCursor) {
480 531 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
481 532 selectionZoneItemUnderCursor->range()));
482 533 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
483 534 selectionZoneItemUnderCursor->range()));
484 535 }
485 536 else {
486 537 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
487 538
488 539 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
489 540 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
490 541 }
491 542
492 543 return mimeData;
493 544 }
494 545
495 546 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
496 547 {
497 548 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition, plot());
498 549 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
499 550 && selectionZoneItemUnderCursor) {
500 551
501 552 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
502 553 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
503 554
504 555 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
505 556 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
506 557 .toSize();
507 558
508 559 auto pixmap = QPixmap(zoneSize);
509 560 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
510 561
511 562 return pixmap;
512 563 }
513 564
514 565 return QPixmap();
515 566 }
516 567
517 568 bool VisualizationGraphWidget::isDragAllowed() const
518 569 {
519 570 return true;
520 571 }
521 572
522 573 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
523 574 {
524 575 if (highlighted) {
525 576 plot().setBackground(QBrush(QColor("#BBD5EE")));
526 577 }
527 578 else {
528 579 plot().setBackground(QBrush(Qt::white));
529 580 }
530 581
531 582 plot().update();
532 583 }
533 584
534 585 void VisualizationGraphWidget::addVerticalCursor(double time)
535 586 {
536 587 impl->m_VerticalCursor->setPosition(time);
537 588 impl->m_VerticalCursor->setVisible(true);
538 589
539 590 auto text
540 591 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
541 592 impl->m_VerticalCursor->setLabelText(text);
542 593 }
543 594
544 595 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
545 596 {
546 597 impl->m_VerticalCursor->setAbsolutePosition(position);
547 598 impl->m_VerticalCursor->setVisible(true);
548 599
549 600 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
550 601 auto text
551 602 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
552 603 impl->m_VerticalCursor->setLabelText(text);
553 604 }
554 605
555 606 void VisualizationGraphWidget::removeVerticalCursor()
556 607 {
557 608 impl->m_VerticalCursor->setVisible(false);
558 609 plot().replot(QCustomPlot::rpQueuedReplot);
559 610 }
560 611
561 612 void VisualizationGraphWidget::addHorizontalCursor(double value)
562 613 {
563 614 impl->m_HorizontalCursor->setPosition(value);
564 615 impl->m_HorizontalCursor->setVisible(true);
565 616 impl->m_HorizontalCursor->setLabelText(QString::number(value));
566 617 }
567 618
568 619 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
569 620 {
570 621 impl->m_HorizontalCursor->setAbsolutePosition(position);
571 622 impl->m_HorizontalCursor->setVisible(true);
572 623
573 624 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
574 625 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
575 626 }
576 627
577 628 void VisualizationGraphWidget::removeHorizontalCursor()
578 629 {
579 630 impl->m_HorizontalCursor->setVisible(false);
580 631 plot().replot(QCustomPlot::rpQueuedReplot);
581 632 }
582 633
583 634 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
584 635 {
585 636 Q_UNUSED(event);
586 637
587 638 // Prevents that all variables will be removed from graph when it will be closed
588 639 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
589 640 emit variableAboutToBeRemoved(variableEntry.first);
590 641 }
591 642 }
592 643
593 644 void VisualizationGraphWidget::enterEvent(QEvent *event)
594 645 {
595 646 Q_UNUSED(event);
596 647 impl->m_RenderingDelegate->showGraphOverlay(true);
597 648 }
598 649
599 650 void VisualizationGraphWidget::leaveEvent(QEvent *event)
600 651 {
601 652 Q_UNUSED(event);
602 653 impl->m_RenderingDelegate->showGraphOverlay(false);
603 654
604 655 if (auto parentZone = parentZoneWidget()) {
605 656 parentZone->notifyMouseLeaveGraph(this);
606 657 }
607 658 else {
608 659 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
609 660 }
610 661
611 662 if (impl->m_HoveredZone) {
612 663 impl->m_HoveredZone->setHovered(false);
613 664 impl->m_HoveredZone = nullptr;
614 665 }
615 666 }
616 667
617 668 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
618 669 {
619 670 return *ui->widget;
620 671 }
621 672
622 673 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
623 674 {
624 675 QMenu graphMenu{};
625 676
626 677 // Iterates on variables (unique keys)
627 678 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
628 679 end = impl->m_VariableToPlotMultiMap.cend();
629 680 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
630 681 // 'Remove variable' action
631 682 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
632 683 [ this, var = it->first ]() { removeVariable(var); });
633 684 }
634 685
635 686 if (!impl->m_ZoomStack.isEmpty()) {
636 687 if (!graphMenu.isEmpty()) {
637 688 graphMenu.addSeparator();
638 689 }
639 690
640 691 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
641 692 }
642 693
643 694 // Selection Zone Actions
644 695 auto selectionZoneItem = impl->selectionZoneAt(pos, plot());
645 696 if (selectionZoneItem) {
646 697 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
647 698 selectedItems.removeAll(selectionZoneItem);
648 699 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
649 700
650 701 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
651 702 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
652 703 graphMenu.addSeparator();
653 704 }
654 705
655 706 QHash<QString, QMenu *> subMenus;
656 707 QHash<QString, bool> subMenusEnabled;
657 708
658 709 for (auto zoneAction : zoneActions) {
659 710
660 711 auto isEnabled = zoneAction->isEnabled(selectedItems);
661 712
662 713 auto menu = &graphMenu;
663 714 for (auto subMenuName : zoneAction->subMenuList()) {
664 715 if (!subMenus.contains(subMenuName)) {
665 716 menu = menu->addMenu(subMenuName);
666 717 subMenus[subMenuName] = menu;
667 718 subMenusEnabled[subMenuName] = isEnabled;
668 719 }
669 720 else {
670 721 menu = subMenus.value(subMenuName);
671 722 if (isEnabled) {
672 723 // The sub menu is enabled if at least one of its actions is enabled
673 724 subMenusEnabled[subMenuName] = true;
674 725 }
675 726 }
676 727 }
677 728
678 729 auto action = menu->addAction(zoneAction->name());
679 730 action->setEnabled(isEnabled);
680 731 action->setShortcut(zoneAction->displayedShortcut());
681 732 QObject::connect(action, &QAction::triggered,
682 733 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
683 734 }
684 735
685 736 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
686 737 it.value()->setEnabled(subMenusEnabled[it.key()]);
687 738 }
688 739 }
689 740
690 741 if (!graphMenu.isEmpty()) {
691 742 graphMenu.exec(QCursor::pos());
692 743 }
693 744 }
694 745
695 746 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
696 747 {
697 748 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
698 749 << QThread::currentThread()->objectName() << "DoAcqui"
699 750 << impl->m_Flags.testFlag(GraphFlag::EnableAcquisition);
700 751
701 752 auto graphRange = SqpRange{t1.lower, t1.upper};
702 753 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
703 754
704 755 if (impl->m_Flags.testFlag(GraphFlag::EnableAcquisition)) {
705 756 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
706 757
707 758 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
708 759 end = impl->m_VariableToPlotMultiMap.end();
709 760 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
710 761 variableUnderGraphVector.push_back(it->first);
711 762 }
712 763 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
713 764 !impl->m_IsCalibration);
714 765 }
715 766
716 767 if (impl->m_Flags.testFlag(GraphFlag::EnableSynchronization) && !impl->m_IsCalibration) {
717 768 qCDebug(LOG_VisualizationGraphWidget())
718 769 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
719 770 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
720 771 emit synchronize(graphRange, oldGraphRange);
721 772 }
722 773
723 774 auto pos = mapFromGlobal(QCursor::pos());
724 775 auto axisPos = impl->posToAxisPos(pos, plot());
725 776 if (auto parentZone = parentZoneWidget()) {
726 777 if (impl->pointIsInAxisRect(axisPos, plot())) {
727 778 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
728 779 }
729 780 else {
730 781 parentZone->notifyMouseLeaveGraph(this);
731 782 }
732 783 }
733 784 else {
734 785 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
735 786 }
736 787
737 788 // Quits calibration
738 789 impl->m_IsCalibration = false;
739 790 }
740 791
741 792 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
742 793 {
743 794 impl->m_RenderingDelegate->onMouseDoubleClick(event);
744 795 }
745 796
746 797 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
747 798 {
748 799 // Handles plot rendering when mouse is moving
749 800 impl->m_RenderingDelegate->onMouseMove(event);
750 801
751 802 auto axisPos = impl->posToAxisPos(event->pos(), plot());
752 803
753 804 // Zoom box and zone drawing
754 805 if (impl->m_DrawingZoomRect) {
755 806 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
756 807 }
757 808 else if (impl->m_DrawingZone) {
758 809 impl->m_DrawingZone->setEnd(axisPos.x());
759 810 }
760 811
761 812 // Cursor
762 813 if (auto parentZone = parentZoneWidget()) {
763 814 if (impl->pointIsInAxisRect(axisPos, plot())) {
764 815 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
765 816 }
766 817 else {
767 818 parentZone->notifyMouseLeaveGraph(this);
768 819 }
769 820 }
770 821 else {
771 822 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
772 823 }
773 824
774 825 // Search for the selection zone under the mouse
775 826 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
776 827 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
777 828 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
778 829
779 830 // Sets the appropriate cursor shape
780 831 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
781 832 setCursor(cursorShape);
782 833
783 834 // Manages the hovered zone
784 835 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
785 836 if (impl->m_HoveredZone) {
786 837 impl->m_HoveredZone->setHovered(false);
787 838 }
788 839 selectionZoneItemUnderCursor->setHovered(true);
789 840 impl->m_HoveredZone = selectionZoneItemUnderCursor;
790 841 plot().replot(QCustomPlot::rpQueuedReplot);
791 842 }
792 843 }
793 844 else {
794 845 // There is no zone under the mouse or the interaction mode is not "selection zones"
795 846 if (impl->m_HoveredZone) {
796 847 impl->m_HoveredZone->setHovered(false);
797 848 impl->m_HoveredZone = nullptr;
798 849 }
799 850
800 851 setCursor(Qt::ArrowCursor);
801 852 }
802 853
803 854 impl->m_HasMovedMouse = true;
804 855 VisualizationDragWidget::mouseMoveEvent(event);
805 856 }
806 857
807 858 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
808 859 {
809 860 auto value = event->angleDelta().x() + event->angleDelta().y();
810 861 if (value != 0) {
811 862
812 863 auto direction = value > 0 ? 1.0 : -1.0;
813 864 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
814 865 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
815 866 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
816 867
817 868 auto zoomOrientations = QFlags<Qt::Orientation>{};
818 869 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
819 870 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
820 871
821 872 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
822 873
823 874 if (!isZoomX && !isZoomY) {
824 875 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
825 876 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
826 877
827 878 axis->setRange(axis->range() + diff);
828 879
829 880 if (plot().noAntialiasingOnDrag()) {
830 881 plot().setNotAntialiasedElements(QCP::aeAll);
831 882 }
832 883
833 884 plot().replot(QCustomPlot::rpQueuedReplot);
834 885 }
835 886 }
836 887 }
837 888
838 889 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
839 890 {
840 891 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
841 892 auto isSelectionZoneMode
842 893 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
843 894 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
844 895
845 896 if (!isDragDropClick && isLeftClick) {
846 897 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
847 898 // Starts a zoom box
848 899 impl->startDrawingRect(event->pos(), plot());
849 900 }
850 901 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
851 902 // Starts a new selection zone
852 903 auto zoneAtPos = impl->selectionZoneAt(event->pos(), plot());
853 904 if (!zoneAtPos) {
854 905 impl->startDrawingZone(event->pos(), this);
855 906 }
856 907 }
857 908 }
858 909
859 910 // Allows mouse panning only in default mode
860 911 plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode()
861 912 == SqpApplication::PlotsInteractionMode::None
862 913 && !isDragDropClick);
863 914
864 915 // Allows zone edition only in selection zone mode without drag&drop
865 916 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
866 917
867 918 // Selection / Deselection
868 919 if (isSelectionZoneMode) {
869 920 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
870 921 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
871 922
872 923
873 924 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
874 925 && !isMultiSelectionClick) {
875 926 parentVisualizationWidget()->selectionZoneManager().select(
876 927 {selectionZoneItemUnderCursor});
877 928 }
878 929 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
879 930 parentVisualizationWidget()->selectionZoneManager().clearSelection();
880 931 }
881 932 else {
882 933 // No selection change
883 934 }
884 935
885 936 if (selectionZoneItemUnderCursor && isLeftClick) {
886 937 selectionZoneItemUnderCursor->setAssociatedEditedZones(
887 938 parentVisualizationWidget()->selectionZoneManager().selectedItems());
888 939 }
889 940 }
890 941
891 942
892 943 impl->m_HasMovedMouse = false;
893 944 VisualizationDragWidget::mousePressEvent(event);
894 945 }
895 946
896 947 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
897 948 {
898 949 if (impl->m_DrawingZoomRect) {
899 950
900 951 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
901 952 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
902 953
903 954 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
904 955 impl->m_DrawingZoomRect->bottomRight->coords().x()};
905 956
906 957 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
907 958 impl->m_DrawingZoomRect->bottomRight->coords().y()};
908 959
909 960 impl->removeDrawingRect(plot());
910 961
911 962 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
912 963 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
913 964 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
914 965 axisX->setRange(newAxisXRange);
915 966 axisY->setRange(newAxisYRange);
916 967
917 968 plot().replot(QCustomPlot::rpQueuedReplot);
918 969 }
919 970 }
920 971
921 972 impl->endDrawingZone(this);
922 973
923 974 // Selection / Deselection
924 975 auto isSelectionZoneMode
925 976 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
926 977 if (isSelectionZoneMode) {
927 978 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
928 979 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
929 980 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
930 981 && !impl->m_HasMovedMouse) {
931 982
932 983 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
933 984 if (zonesUnderCursor.count() > 1) {
934 985 // There are multiple zones under the mouse.
935 986 // Performs the selection with a selection dialog.
936 987 VisualizationMultiZoneSelectionDialog dialog{this};
937 988 dialog.setZones(zonesUnderCursor);
938 989 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
939 990 dialog.activateWindow();
940 991 dialog.raise();
941 992 if (dialog.exec() == QDialog::Accepted) {
942 993 auto selection = dialog.selectedZones();
943 994
944 995 if (!isMultiSelectionClick) {
945 996 parentVisualizationWidget()->selectionZoneManager().clearSelection();
946 997 }
947 998
948 999 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
949 1000 auto zone = it.key();
950 1001 auto isSelected = it.value();
951 1002 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
952 1003 isSelected);
953 1004
954 1005 if (isSelected) {
955 1006 // Puts the zone on top of the stack so it can be moved or resized
956 1007 impl->moveSelectionZoneOnTop(zone, plot());
957 1008 }
958 1009 }
959 1010 }
960 1011 }
961 1012 else {
962 1013 if (!isMultiSelectionClick) {
963 1014 parentVisualizationWidget()->selectionZoneManager().select(
964 1015 {selectionZoneItemUnderCursor});
965 1016 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
966 1017 }
967 1018 else {
968 1019 parentVisualizationWidget()->selectionZoneManager().setSelected(
969 1020 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
970 1021 || event->button() == Qt::RightButton);
971 1022 }
972 1023 }
973 1024 }
974 1025 else {
975 1026 // No selection change
976 1027 }
977 1028 }
978 1029 }
979 1030
980 1031 void VisualizationGraphWidget::onDataCacheVariableUpdated()
981 1032 {
982 1033 auto graphRange = ui->widget->xAxis->range();
983 1034 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
984 1035
985 1036 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
986 1037 auto variable = variableEntry.first;
987 1038 qCDebug(LOG_VisualizationGraphWidget())
988 1039 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
989 1040 qCDebug(LOG_VisualizationGraphWidget())
990 1041 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
991 1042 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
992 impl->updateData(variableEntry.second, variable->dataSeries(), variable->range());
1043 impl->updateData(variableEntry.second, variable, variable->range());
993 1044 }
994 1045 }
995 1046 }
996 1047
997 1048 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
998 1049 const SqpRange &range)
999 1050 {
1000 1051 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1001 1052 if (it != impl->m_VariableToPlotMultiMap.end()) {
1002 impl->updateData(it->second, variable->dataSeries(), range);
1053 impl->updateData(it->second, variable, range);
1003 1054 }
1004 1055 }
@@ -1,440 +1,443
1 1 #include "Visualization/VisualizationSelectionZoneItem.h"
2 2 #include "Visualization/VisualizationGraphWidget.h"
3 3 #include "Visualization/VisualizationSelectionZoneManager.h"
4 4 #include "Visualization/VisualizationWidget.h"
5 5
6 6 const QString &DEFAULT_COLOR = QStringLiteral("#E79D41");
7 7
8 8 struct VisualizationSelectionZoneItem::VisualizationSelectionZoneItemPrivate {
9 9
10 10 QCustomPlot *m_Plot;
11 11 double m_T1 = 0;
12 12 double m_T2 = 0;
13 13 QColor m_Color;
14 14
15 15 bool m_IsEditionEnabled = true;
16 16 double m_MovedOrinalT1 = 0;
17 17 double m_MovedOrinalT2 = 0;
18 18
19 19 QCPItemStraightLine *m_LeftLine;
20 20 QCPItemStraightLine *m_RightLine;
21 21 QCPItemText *m_NameLabelItem = nullptr;
22 22
23 23 enum class EditionMode { NoEdition, ResizeLeft, ResizeRight, Move };
24 24 EditionMode m_CurrentEditionMode;
25 25
26 26 QVector<VisualizationSelectionZoneItem *> m_AssociatedEditedZones;
27 27
28 28 VisualizationSelectionZoneItemPrivate(QCustomPlot *plot)
29 29 : m_Plot(plot), m_Color(Qt::blue), m_CurrentEditionMode(EditionMode::NoEdition)
30 30 {
31 31 }
32 32
33 33 void updatePosition(VisualizationSelectionZoneItem *item)
34 34 {
35 35 item->topLeft->setCoords(m_T1, 0);
36 36 item->bottomRight->setCoords(m_T2, 1);
37 37 }
38 38
39 39 EditionMode getEditionMode(const QPoint &pos, const VisualizationSelectionZoneItem *zoneItem)
40 40 {
41 41 auto distanceLeft = m_LeftLine->selectTest(pos, false);
42 42 auto distanceRight = m_RightLine->selectTest(pos, false);
43 43 auto distance = zoneItem->selectTest(pos, false);
44 44
45 45 if (distanceRight <= distance) {
46 46 return VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight;
47 47 }
48 48 else if (distanceLeft <= distance) {
49 49 return VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft;
50 50 }
51 51
52 52 return VisualizationSelectionZoneItemPrivate::EditionMode::Move;
53 53 }
54 54
55 55 double pixelSizeToAxisXSize(double pixels)
56 56 {
57 57 auto axis = m_Plot->axisRect()->axis(QCPAxis::atBottom);
58 58 return axis->pixelToCoord(pixels) - axis->pixelToCoord(0);
59 59 }
60 60
61 61 bool alignZones(VisualizationSelectionZoneItem *referenceZone,
62 62 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool alignOnLeft,
63 63 bool allowResize, bool vertically)
64 64 {
65 65 auto result = false;
66 66
67 67 auto referenceTime
68 68 = alignOnLeft ? referenceZone->range().m_TStart : referenceZone->range().m_TEnd;
69 69
70 70 auto referenceBottomAxis = m_Plot->axisRect()->axis(QCPAxis::atBottom);
71 71 auto referenceVerticalPosition = referenceBottomAxis->coordToPixel(referenceTime);
72 72
73 73 for (auto otherZone : zonesToAlign) {
74 74
75 75 auto otherZoneRange = otherZone->range();
76 76 auto newZoneStart = otherZoneRange.m_TStart;
77 77 auto newZoneEnd = otherZoneRange.m_TEnd;
78 78
79 79 auto alignedTime = referenceTime;
80 80 if (vertically) {
81 81 auto otherZoneAxis = otherZone->parentPlot()->axisRect()->axis(QCPAxis::atBottom);
82 82 alignedTime = otherZoneAxis->pixelToCoord(referenceVerticalPosition);
83 83 }
84 84
85 85 if (alignOnLeft) {
86 86 newZoneStart = alignedTime;
87 87 if (!allowResize) {
88 88 newZoneEnd = alignedTime + (otherZoneRange.m_TEnd - otherZoneRange.m_TStart);
89 89 }
90 90 }
91 91 else { // align on right
92 92 newZoneEnd = alignedTime;
93 93 if (!allowResize) {
94 94 newZoneStart = alignedTime - (otherZoneRange.m_TEnd - otherZoneRange.m_TStart);
95 95 }
96 96 }
97 97
98 98 if (newZoneStart < newZoneEnd) {
99 99 result = true;
100 100 otherZone->setRange(newZoneStart, newZoneEnd);
101 101 otherZone->parentPlot()->replot();
102 102 }
103 103 }
104 104
105 105 return result;
106 106 }
107 107 };
108 108
109 109 VisualizationSelectionZoneItem::VisualizationSelectionZoneItem(QCustomPlot *plot)
110 110 : QCPItemRect(plot),
111 111 impl{spimpl::make_unique_impl<VisualizationSelectionZoneItemPrivate>(plot)}
112 112 {
113 113 topLeft->setTypeX(QCPItemPosition::ptPlotCoords);
114 114 topLeft->setTypeY(QCPItemPosition::ptAxisRectRatio);
115 115 bottomRight->setTypeX(QCPItemPosition::ptPlotCoords);
116 116 bottomRight->setTypeY(QCPItemPosition::ptAxisRectRatio);
117 117 setSelectable(false);
118 118
119 119 impl->m_RightLine = new QCPItemStraightLine(plot);
120 120 impl->m_RightLine->point1->setParentAnchor(topRight);
121 121 impl->m_RightLine->point2->setParentAnchor(bottomRight);
122 122 impl->m_RightLine->point1->setTypeX(QCPItemPosition::ptAbsolute);
123 123 impl->m_RightLine->point1->setTypeY(QCPItemPosition::ptAbsolute);
124 124 impl->m_RightLine->point2->setTypeX(QCPItemPosition::ptAbsolute);
125 125 impl->m_RightLine->point2->setTypeY(QCPItemPosition::ptAbsolute);
126 126 impl->m_RightLine->setSelectable(false);
127 127
128 128 impl->m_LeftLine = new QCPItemStraightLine(plot);
129 129 impl->m_LeftLine->point1->setParentAnchor(topLeft);
130 130 impl->m_LeftLine->point2->setParentAnchor(bottomLeft);
131 131 impl->m_LeftLine->point1->setTypeX(QCPItemPosition::ptAbsolute);
132 132 impl->m_LeftLine->point1->setTypeY(QCPItemPosition::ptAbsolute);
133 133 impl->m_LeftLine->point2->setTypeX(QCPItemPosition::ptAbsolute);
134 134 impl->m_LeftLine->point2->setTypeY(QCPItemPosition::ptAbsolute);
135 135 impl->m_LeftLine->setSelectable(false);
136 136
137 137 connect(this, &VisualizationSelectionZoneItem::selectionChanged, impl->m_RightLine,
138 138 &QCPItemStraightLine::setSelected);
139 139 connect(this, &VisualizationSelectionZoneItem::selectionChanged, impl->m_LeftLine,
140 140 &QCPItemStraightLine::setSelected);
141 141
142 142 setColor(QColor(DEFAULT_COLOR));
143 143 }
144 144
145 145 VisualizationSelectionZoneItem::~VisualizationSelectionZoneItem()
146 146 {
147 147 }
148 148
149 149 VisualizationGraphWidget *VisualizationSelectionZoneItem::parentGraphWidget() const noexcept
150 150 {
151 151 auto parent = impl->m_Plot->parentWidget();
152 152 while (parent != nullptr && !qobject_cast<VisualizationGraphWidget *>(parent)) {
153 153 parent = parent->parentWidget();
154 154 }
155 155
156 156 return qobject_cast<VisualizationGraphWidget *>(parent);
157 157 }
158 158
159 159 void VisualizationSelectionZoneItem::setName(const QString &name)
160 160 {
161 161 if (name.isEmpty() && impl->m_NameLabelItem) {
162 162 impl->m_Plot->removeItem(impl->m_NameLabelItem);
163 163 impl->m_NameLabelItem = nullptr;
164 164 }
165 165 else if (!impl->m_NameLabelItem) {
166 166 impl->m_NameLabelItem = new QCPItemText(impl->m_Plot);
167 167 impl->m_NameLabelItem->setText(name);
168 168 impl->m_NameLabelItem->setPositionAlignment(Qt::AlignHCenter | Qt::AlignTop);
169 169 impl->m_NameLabelItem->setColor(impl->m_Color);
170 170 impl->m_NameLabelItem->position->setParentAnchor(top);
171 171 }
172 172 }
173 173
174 174 QString VisualizationSelectionZoneItem::name() const
175 175 {
176 176 if (!impl->m_NameLabelItem) {
177 177 return QString();
178 178 }
179 179
180 180 return impl->m_NameLabelItem->text();
181 181 }
182 182
183 183 SqpRange VisualizationSelectionZoneItem::range() const
184 184 {
185 185 SqpRange range;
186 186 range.m_TStart = impl->m_T1 <= impl->m_T2 ? impl->m_T1 : impl->m_T2;
187 187 range.m_TEnd = impl->m_T1 > impl->m_T2 ? impl->m_T1 : impl->m_T2;
188 188 return range;
189 189 }
190 190
191 191 void VisualizationSelectionZoneItem::setRange(double tstart, double tend)
192 192 {
193 193 impl->m_T1 = tstart;
194 194 impl->m_T2 = tend;
195 195 impl->updatePosition(this);
196 196 }
197 197
198 198 void VisualizationSelectionZoneItem::setStart(double tstart)
199 199 {
200 200 impl->m_T1 = tstart;
201 201 impl->updatePosition(this);
202 202 }
203 203
204 204 void VisualizationSelectionZoneItem::setEnd(double tend)
205 205 {
206 206 impl->m_T2 = tend;
207 207 impl->updatePosition(this);
208 208 }
209 209
210 210 void VisualizationSelectionZoneItem::setColor(const QColor &color)
211 211 {
212 212 impl->m_Color = color;
213 213
214 214 auto brushColor = color;
215 215 brushColor.setAlpha(80);
216 216 setBrush(QBrush(brushColor));
217 217 setPen(QPen(Qt::NoPen));
218 218
219 219 auto selectedBrushColor = brushColor;
220 220 selectedBrushColor.setAlpha(150);
221 221 setSelectedBrush(QBrush(selectedBrushColor));
222 222 setSelectedPen(QPen(Qt::NoPen));
223 223
224 224 auto linePen = QPen(color);
225 225 linePen.setStyle(Qt::SolidLine);
226 226 linePen.setWidth(4);
227 227
228 228 auto selectedLinePen = linePen;
229 229 selectedLinePen.setColor(color.darker(120));
230 230 selectedLinePen.setWidth(4);
231 231
232 232 impl->m_LeftLine->setPen(linePen);
233 233 impl->m_RightLine->setPen(linePen);
234 234
235 235 impl->m_LeftLine->setSelectedPen(selectedLinePen);
236 236 impl->m_RightLine->setSelectedPen(selectedLinePen);
237 237 }
238 238
239 239 void VisualizationSelectionZoneItem::setEditionEnabled(bool value)
240 240 {
241 241 impl->m_IsEditionEnabled = value;
242 242 setSelectable(value);
243 243 if (!value) {
244 244 setSelected(false);
245 245 impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition;
246 246 }
247 247 }
248 248
249 249 bool VisualizationSelectionZoneItem::isEditionEnabled() const
250 250 {
251 251 return impl->m_IsEditionEnabled;
252 252 }
253 253
254 254 void VisualizationSelectionZoneItem::moveToTop()
255 255 {
256 256 moveToLayer(layer(), false);
257 257 }
258 258
259 259 Qt::CursorShape
260 260 VisualizationSelectionZoneItem::curshorShapeForPosition(const QPoint &position) const
261 261 {
262 262 auto mode = impl->m_CurrentEditionMode
263 263 == VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition
264 264 ? impl->getEditionMode(position, this)
265 265 : impl->m_CurrentEditionMode;
266 266 switch (mode) {
267 267 case VisualizationSelectionZoneItemPrivate::EditionMode::Move:
268 268 return Qt::SizeAllCursor;
269 269 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft:
270 270 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight: // fallthrough
271 271 return Qt::SizeHorCursor;
272 272 default:
273 273 return Qt::ArrowCursor;
274 274 }
275 275 }
276 276
277 277 void VisualizationSelectionZoneItem::setHovered(bool value)
278 278 {
279 279 if (value) {
280 280 auto linePen = impl->m_LeftLine->pen();
281 281 linePen.setStyle(Qt::DotLine);
282 282 linePen.setWidth(3);
283 283
284 284 auto selectedLinePen = impl->m_LeftLine->selectedPen();
285 285 ;
286 286 selectedLinePen.setStyle(Qt::DotLine);
287 287 selectedLinePen.setWidth(3);
288 288
289 289 impl->m_LeftLine->setPen(linePen);
290 290 impl->m_RightLine->setPen(linePen);
291 291
292 292 impl->m_LeftLine->setSelectedPen(selectedLinePen);
293 293 impl->m_RightLine->setSelectedPen(selectedLinePen);
294 294 }
295 295 else {
296 296 setColor(impl->m_Color);
297 297 }
298 298 }
299 299
300 300 void VisualizationSelectionZoneItem::setAssociatedEditedZones(
301 301 const QVector<VisualizationSelectionZoneItem *> &associatedZones)
302 302 {
303 303 impl->m_AssociatedEditedZones = associatedZones;
304 304 impl->m_AssociatedEditedZones.removeAll(this);
305 305 }
306 306
307 307 bool VisualizationSelectionZoneItem::alignZonesVerticallyOnLeft(
308 308 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool allowResize)
309 309 {
310 310 return impl->alignZones(this, zonesToAlign, true, allowResize, true);
311 311 }
312 312
313 313 bool VisualizationSelectionZoneItem::alignZonesVerticallyOnRight(
314 314 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool allowResize)
315 315 {
316 316 return impl->alignZones(this, zonesToAlign, false, allowResize, true);
317 317 }
318 318
319 319 bool VisualizationSelectionZoneItem::alignZonesTemporallyOnLeft(
320 320 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool allowResize)
321 321 {
322 322 return impl->alignZones(this, zonesToAlign, true, allowResize, false);
323 323 }
324 324
325 325 bool VisualizationSelectionZoneItem::alignZonesTemporallyOnRight(
326 326 const QVector<VisualizationSelectionZoneItem *> &zonesToAlign, bool allowResize)
327 327 {
328 328 return impl->alignZones(this, zonesToAlign, false, allowResize, false);
329 329 }
330 330
331 331 void VisualizationSelectionZoneItem::mousePressEvent(QMouseEvent *event, const QVariant &details)
332 332 {
333 333 Q_UNUSED(details);
334 334
335 335 if (isEditionEnabled() && event->button() == Qt::LeftButton) {
336 336 impl->m_CurrentEditionMode = impl->getEditionMode(event->pos(), this);
337 337
338 338 impl->m_MovedOrinalT1 = impl->m_T1;
339 339 impl->m_MovedOrinalT2 = impl->m_T2;
340 340 for (auto associatedZone : impl->m_AssociatedEditedZones) {
341 341 associatedZone->impl->m_MovedOrinalT1 = associatedZone->impl->m_T1;
342 342 associatedZone->impl->m_MovedOrinalT2 = associatedZone->impl->m_T2;
343 343 }
344 344 }
345 345 else {
346 346 impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition;
347 347 event->ignore();
348 348 }
349 349 }
350 350
351 351 void VisualizationSelectionZoneItem::mouseMoveEvent(QMouseEvent *event, const QPointF &startPos)
352 352 {
353 353 if (isEditionEnabled()) {
354 354 if (!selected()) {
355 355 // Force the item to be selected during the edition
356 356 parentGraphWidget()->parentVisualizationWidget()->selectionZoneManager().setSelected(
357 357 this, true);
358 358 }
359 359
360 360 auto axis = impl->m_Plot->axisRect()->axis(QCPAxis::atBottom);
361 361 auto pixelDiff = event->pos().x() - startPos.x();
362 362 auto diff = impl->pixelSizeToAxisXSize(pixelDiff);
363 363
364 364 switch (impl->m_CurrentEditionMode) {
365 365 case VisualizationSelectionZoneItemPrivate::EditionMode::Move:
366 366 setRange(impl->m_MovedOrinalT1 + diff, impl->m_MovedOrinalT2 + diff);
367 367 for (auto associatedZone : impl->m_AssociatedEditedZones) {
368 368 associatedZone->move(pixelDiff);
369 369 }
370 370 break;
371 371 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeLeft:
372 372 setStart(impl->m_MovedOrinalT1 + diff);
373 373 for (auto associatedZone : impl->m_AssociatedEditedZones) {
374 374 impl->m_MovedOrinalT1 < impl->m_MovedOrinalT2
375 375 ? associatedZone->resizeLeft(pixelDiff)
376 376 : associatedZone->resizeRight(pixelDiff);
377 377 }
378 378 break;
379 379 case VisualizationSelectionZoneItemPrivate::EditionMode::ResizeRight:
380 380 setEnd(impl->m_MovedOrinalT2 + diff);
381 381 for (auto associatedZone : impl->m_AssociatedEditedZones) {
382 382 impl->m_MovedOrinalT1 < impl->m_MovedOrinalT2
383 383 ? associatedZone->resizeRight(pixelDiff)
384 384 : associatedZone->resizeLeft(pixelDiff);
385 385 }
386 386 break;
387 387 default:
388 388 break;
389 389 }
390 390
391 emit rangeEdited(range());
392
391 393 for (auto associatedZone : impl->m_AssociatedEditedZones) {
392 394 associatedZone->parentPlot()->replot();
395 emit associatedZone->rangeEdited(associatedZone->range());
393 396 }
394 397 }
395 398 else {
396 399 event->ignore();
397 400 }
398 401 }
399 402
400 403 void VisualizationSelectionZoneItem::mouseReleaseEvent(QMouseEvent *event, const QPointF &startPos)
401 404 {
402 405 Q_UNUSED(startPos);
403 406
404 407 if (isEditionEnabled()) {
405 408 impl->m_CurrentEditionMode = VisualizationSelectionZoneItemPrivate::EditionMode::NoEdition;
406 409 }
407 410 else {
408 411 event->ignore();
409 412 }
410 413
411 414 impl->m_AssociatedEditedZones.clear();
412 415 }
413 416
414 417 void VisualizationSelectionZoneItem::resizeLeft(double pixelDiff)
415 418 {
416 419 auto diff = impl->pixelSizeToAxisXSize(pixelDiff);
417 420 if (impl->m_MovedOrinalT1 <= impl->m_MovedOrinalT2) {
418 421 setStart(impl->m_MovedOrinalT1 + diff);
419 422 }
420 423 else {
421 424 setEnd(impl->m_MovedOrinalT2 + diff);
422 425 }
423 426 }
424 427
425 428 void VisualizationSelectionZoneItem::resizeRight(double pixelDiff)
426 429 {
427 430 auto diff = impl->pixelSizeToAxisXSize(pixelDiff);
428 431 if (impl->m_MovedOrinalT1 > impl->m_MovedOrinalT2) {
429 432 setStart(impl->m_MovedOrinalT1 + diff);
430 433 }
431 434 else {
432 435 setEnd(impl->m_MovedOrinalT2 + diff);
433 436 }
434 437 }
435 438
436 439 void VisualizationSelectionZoneItem::move(double pixelDiff)
437 440 {
438 441 auto diff = impl->pixelSizeToAxisXSize(pixelDiff);
439 442 setRange(impl->m_MovedOrinalT1 + diff, impl->m_MovedOrinalT2 + diff);
440 443 }
@@ -1,354 +1,389
1 1 #include "Visualization/VisualizationTabWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "ui_VisualizationTabWidget.h"
4 4
5 5 #include "Visualization/VisualizationGraphWidget.h"
6 6 #include "Visualization/VisualizationZoneWidget.h"
7 7
8 8 #include "Visualization/MacScrollBarStyle.h"
9 9
10 #include "DataSource/DataSourceController.h"
10 11 #include "Variable/VariableController.h"
11 12
12 13 #include "Common/MimeTypesDef.h"
13 14
14 15 #include "DragAndDrop/DragDropGuiController.h"
15 16 #include "SqpApplication.h"
16 17
17 18 Q_LOGGING_CATEGORY(LOG_VisualizationTabWidget, "VisualizationTabWidget")
18 19
19 20 namespace {
20 21
21 22 /**
22 23 * Applies a function to all zones of the tab represented by its layout
23 24 * @param layout the layout that contains zones
24 25 * @param fun the function to apply to each zone
25 26 */
26 27 template <typename Fun>
27 28 void processZones(QLayout &layout, Fun fun)
28 29 {
29 30 for (auto i = 0; i < layout.count(); ++i) {
30 31 if (auto item = layout.itemAt(i)) {
31 32 if (auto visualizationZoneWidget
32 33 = qobject_cast<VisualizationZoneWidget *>(item->widget())) {
33 34 fun(*visualizationZoneWidget);
34 35 }
35 36 }
36 37 }
37 38 }
38 39
39 40 /// Generates a default name for a new zone, according to the number of zones already displayed in
40 41 /// the tab
41 42 QString defaultZoneName(QLayout &layout)
42 43 {
43 44 QSet<QString> existingNames;
44 45 processZones(layout,
45 46 [&existingNames](auto &zoneWidget) { existingNames.insert(zoneWidget.name()); });
46 47
47 48 int zoneNum = 1;
48 49 QString name;
49 50 do {
50 51 name = QObject::tr("Zone ").append(QString::number(zoneNum));
51 52 ++zoneNum;
52 53 } while (existingNames.contains(name));
53 54
54 55 return name;
55 56 }
56 57
57 58 } // namespace
58 59
59 60 struct VisualizationTabWidget::VisualizationTabWidgetPrivate {
60 61 explicit VisualizationTabWidgetPrivate(const QString &name) : m_Name{name} {}
61 62
62 63 QString m_Name;
63 64
64 65 #ifdef Q_OS_MAC
65 66 std::unique_ptr<MacScrollBarStyle> m_MacScrollBarStyle = std::make_unique<MacScrollBarStyle>();
66 67 #endif
67 68
68 69 void dropGraph(int index, VisualizationTabWidget *tabWidget);
69 70 void dropZone(int index, VisualizationTabWidget *tabWidget);
70 71 void dropVariables(const QList<std::shared_ptr<Variable> > &variables, int index,
71 72 VisualizationTabWidget *tabWidget);
73 void dropProducts(const QVariantList &productsMetaData, int index,
74 VisualizationTabWidget *tabWidget);
72 75 };
73 76
74 77 VisualizationTabWidget::VisualizationTabWidget(const QString &name, QWidget *parent)
75 78 : QWidget{parent},
76 79 ui{new Ui::VisualizationTabWidget},
77 80 impl{spimpl::make_unique_impl<VisualizationTabWidgetPrivate>(name)}
78 81 {
79 82 ui->setupUi(this);
80 83
81 84 #ifdef Q_OS_MAC
82 85 impl->m_MacScrollBarStyle->selfInstallOn(ui->scrollArea, true);
83 86 #endif
84 87
85 88 ui->dragDropContainer->setPlaceHolderType(DragDropGuiController::PlaceHolderType::Zone, "Zone");
86 89 ui->dragDropContainer->layout()->setContentsMargins(0, 0, 0, 12);
87 90 ui->dragDropContainer->layout()->setSpacing(0);
88 91 ui->dragDropContainer->setMimeType(MIME_TYPE_GRAPH,
89 92 VisualizationDragDropContainer::DropBehavior::Inserted);
90 93 ui->dragDropContainer->setMimeType(MIME_TYPE_ZONE,
91 94 VisualizationDragDropContainer::DropBehavior::Inserted);
92 95 ui->dragDropContainer->setMimeType(MIME_TYPE_VARIABLE_LIST,
93 96 VisualizationDragDropContainer::DropBehavior::Inserted);
97 ui->dragDropContainer->setMimeType(MIME_TYPE_PRODUCT_LIST,
98 VisualizationDragDropContainer::DropBehavior::Inserted);
94 99
95 100 ui->dragDropContainer->setAcceptMimeDataFunction([this](auto mimeData) {
96 101 return sqpApp->dragDropGuiController().checkMimeDataForVisualization(mimeData,
97 102 ui->dragDropContainer);
98 103 });
99 104
100 105 connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccuredInContainer, this,
101 106 &VisualizationTabWidget::dropMimeData);
102 107
103 108 sqpApp->dragDropGuiController().addDragDropScrollArea(ui->scrollArea);
104 109
105 110 // Widget is deleted when closed
106 111 setAttribute(Qt::WA_DeleteOnClose);
107 112 }
108 113
109 114 VisualizationTabWidget::~VisualizationTabWidget()
110 115 {
111 116 sqpApp->dragDropGuiController().removeDragDropScrollArea(ui->scrollArea);
112 117 delete ui;
113 118 }
114 119
115 120 void VisualizationTabWidget::addZone(VisualizationZoneWidget *zoneWidget)
116 121 {
117 122 ui->dragDropContainer->addDragWidget(zoneWidget);
118 123 }
119 124
120 125 void VisualizationTabWidget::insertZone(int index, VisualizationZoneWidget *zoneWidget)
121 126 {
122 127 ui->dragDropContainer->insertDragWidget(index, zoneWidget);
123 128 }
124 129
125 130 QStringList VisualizationTabWidget::availableZoneWidgets() const
126 131 {
127 132 QStringList zones;
128 133 processZones(tabLayout(),
129 134 [&zones](VisualizationZoneWidget &zoneWidget) { zones << zoneWidget.name(); });
130 135
131 136 return zones;
132 137 }
133 138
134 139 VisualizationZoneWidget *VisualizationTabWidget::getZoneWithName(const QString &zoneName)
135 140 {
136 141 VisualizationZoneWidget *result = nullptr;
137 142 processZones(tabLayout(), [&zoneName, &result](VisualizationZoneWidget &zoneWidget) {
138 143 if (!result && zoneWidget.name() == zoneName) {
139 144 result = &zoneWidget;
140 145 }
141 146 });
142 147
143 148 return result;
144 149 }
145 150
146 151 VisualizationZoneWidget *VisualizationTabWidget::createZone(std::shared_ptr<Variable> variable)
147 152 {
148 153 return createZone({variable}, -1);
149 154 }
150 155
151 156 VisualizationZoneWidget *
152 157 VisualizationTabWidget::createZone(const QList<std::shared_ptr<Variable> > &variables, int index)
153 158 {
154 159 auto zoneWidget = createEmptyZone(index);
155 160
156 161 // Creates a new graph into the zone
157 162 zoneWidget->createGraph(variables, index);
158 163
159 164 return zoneWidget;
160 165 }
161 166
162 167 VisualizationZoneWidget *VisualizationTabWidget::createEmptyZone(int index)
163 168 {
164 169 auto zoneWidget
165 170 = new VisualizationZoneWidget{defaultZoneName(*ui->dragDropContainer->layout()), this};
166 171 this->insertZone(index, zoneWidget);
167 172
168 173 return zoneWidget;
169 174 }
170 175
171 176 void VisualizationTabWidget::accept(IVisualizationWidgetVisitor *visitor)
172 177 {
173 178 if (visitor) {
174 179 visitor->visitEnter(this);
175 180
176 181 // Apply visitor to zone children: widgets different from zones are not visited (no action)
177 182 processZones(tabLayout(), [visitor](VisualizationZoneWidget &zoneWidget) {
178 183 zoneWidget.accept(visitor);
179 184 });
180 185
181 186 visitor->visitLeave(this);
182 187 }
183 188 else {
184 189 qCCritical(LOG_VisualizationTabWidget()) << tr("Can't visit widget : the visitor is null");
185 190 }
186 191 }
187 192
188 193 bool VisualizationTabWidget::canDrop(const Variable &variable) const
189 194 {
190 195 // A tab can always accomodate a variable
191 196 Q_UNUSED(variable);
192 197 return true;
193 198 }
194 199
195 200 bool VisualizationTabWidget::contains(const Variable &variable) const
196 201 {
197 202 Q_UNUSED(variable);
198 203 return false;
199 204 }
200 205
201 206 QString VisualizationTabWidget::name() const
202 207 {
203 208 return impl->m_Name;
204 209 }
205 210
206 211 void VisualizationTabWidget::closeEvent(QCloseEvent *event)
207 212 {
208 213 // Closes zones in the tab
209 214 processZones(tabLayout(), [](VisualizationZoneWidget &zoneWidget) { zoneWidget.close(); });
210 215
211 216 QWidget::closeEvent(event);
212 217 }
213 218
214 219 QLayout &VisualizationTabWidget::tabLayout() const noexcept
215 220 {
216 221 return *ui->dragDropContainer->layout();
217 222 }
218 223
219 224 void VisualizationTabWidget::dropMimeData(int index, const QMimeData *mimeData)
220 225 {
221 226 if (mimeData->hasFormat(MIME_TYPE_GRAPH)) {
222 227 impl->dropGraph(index, this);
223 228 }
224 229 else if (mimeData->hasFormat(MIME_TYPE_ZONE)) {
225 230 impl->dropZone(index, this);
226 231 }
227 232 else if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
228 233 auto variables = sqpApp->variableController().variablesForMimeData(
229 234 mimeData->data(MIME_TYPE_VARIABLE_LIST));
230 235 impl->dropVariables(variables, index, this);
231 236 }
237 else if (mimeData->hasFormat(MIME_TYPE_PRODUCT_LIST)) {
238 auto productsData = sqpApp->dataSourceController().productsDataForMimeData(
239 mimeData->data(MIME_TYPE_PRODUCT_LIST));
240 impl->dropProducts(productsData, index, this);
241 }
232 242 else {
233 243 qCWarning(LOG_VisualizationZoneWidget())
234 244 << tr("VisualizationTabWidget::dropMimeData, unknown MIME data received.");
235 245 }
236 246 }
237 247
238 248 void VisualizationTabWidget::VisualizationTabWidgetPrivate::dropGraph(
239 249 int index, VisualizationTabWidget *tabWidget)
240 250 {
241 251 auto &helper = sqpApp->dragDropGuiController();
242 252
243 253 auto graphWidget = qobject_cast<VisualizationGraphWidget *>(helper.getCurrentDragWidget());
244 254 if (!graphWidget) {
245 255 qCWarning(LOG_VisualizationZoneWidget())
246 256 << tr("VisualizationTabWidget::dropGraph, drop aborted, the dropped graph is not "
247 257 "found or invalid.");
248 258 Q_ASSERT(false);
249 259 return;
250 260 }
251 261
252 262 auto parentDragDropContainer
253 263 = qobject_cast<VisualizationDragDropContainer *>(graphWidget->parentWidget());
254 264 if (!parentDragDropContainer) {
255 265 qCWarning(LOG_VisualizationZoneWidget())
256 266 << tr("VisualizationTabWidget::dropGraph, drop aborted, the parent container of "
257 267 "the dropped graph is not found.");
258 268 Q_ASSERT(false);
259 269 return;
260 270 }
261 271
262 272 auto nbGraph = parentDragDropContainer->countDragWidget();
263 273
264 274 const auto &variables = graphWidget->variables();
265 275
266 276 if (!variables.isEmpty()) {
267 277 // Abort the requests for the variables (if any)
268 278 // Commented, because it's not sure if it's needed or not
269 279 // for (const auto& var : variables)
270 280 //{
271 281 // sqpApp->variableController().onAbortProgressRequested(var);
272 282 //}
273 283
274 284 if (nbGraph == 1) {
275 285 // This is the only graph in the previous zone, close the zone
276 286 helper.delayedCloseWidget(graphWidget->parentZoneWidget());
277 287 }
278 288 else {
279 289 // Close the graph
280 290 helper.delayedCloseWidget(graphWidget);
281 291 }
282 292
283 293 auto zoneWidget = tabWidget->createZone(variables, index);
284 294 auto firstGraph = zoneWidget->firstGraph();
285 295 if (firstGraph) {
286 296 firstGraph->addSelectionZones(graphWidget->selectionZoneRanges());
287 297 }
288 298 else {
289 299 qCWarning(LOG_VisualizationZoneWidget())
290 300 << tr("VisualizationTabWidget::dropGraph, no graph added in the widget.");
291 301 Q_ASSERT(false);
292 302 }
293 303 }
294 304 else {
295 305 // The graph is empty, create an empty zone and move the graph inside
296 306
297 307 auto parentZoneWidget = graphWidget->parentZoneWidget();
298 308
299 309 parentDragDropContainer->layout()->removeWidget(graphWidget);
300 310
301 311 auto zoneWidget = tabWidget->createEmptyZone(index);
302 312 zoneWidget->addGraph(graphWidget);
303 313
304 314 // Close the old zone if it was the only graph inside
305 315 if (nbGraph == 1) {
306 316 helper.delayedCloseWidget(parentZoneWidget);
307 317 }
308 318 }
309 319 }
310 320
311 321 void VisualizationTabWidget::VisualizationTabWidgetPrivate::dropZone(
312 322 int index, VisualizationTabWidget *tabWidget)
313 323 {
314 324 auto &helper = sqpApp->dragDropGuiController();
315 325
316 326 auto zoneWidget = qobject_cast<VisualizationZoneWidget *>(helper.getCurrentDragWidget());
317 327 if (!zoneWidget) {
318 328 qCWarning(LOG_VisualizationZoneWidget())
319 329 << tr("VisualizationTabWidget::dropZone, drop aborted, the dropped zone is not "
320 330 "found or invalid.");
321 331 Q_ASSERT(false);
322 332 return;
323 333 }
324 334
325 335 auto parentDragDropContainer
326 336 = qobject_cast<VisualizationDragDropContainer *>(zoneWidget->parentWidget());
327 337 if (!parentDragDropContainer) {
328 338 qCWarning(LOG_VisualizationZoneWidget())
329 339 << tr("VisualizationTabWidget::dropZone, drop aborted, the parent container of "
330 340 "the dropped zone is not found.");
331 341 Q_ASSERT(false);
332 342 return;
333 343 }
334 344
335 345 // Simple move of the zone, no variable operation associated
336 346 parentDragDropContainer->layout()->removeWidget(zoneWidget);
337 347 tabWidget->ui->dragDropContainer->insertDragWidget(index, zoneWidget);
338 348 }
339 349
340 350 void VisualizationTabWidget::VisualizationTabWidgetPrivate::dropVariables(
341 351 const QList<std::shared_ptr<Variable> > &variables, int index,
342 352 VisualizationTabWidget *tabWidget)
343 353 {
344 354 // Note: the AcceptMimeDataFunction (set on the drop container) ensure there is a single and
345 355 // compatible variable here
346 356 if (variables.count() > 1) {
347 357 qCWarning(LOG_VisualizationZoneWidget())
348 358 << tr("VisualizationTabWidget::dropVariables, dropping multiple variables, operation "
349 359 "aborted.");
350 360 return;
351 361 }
352 362
353 363 tabWidget->createZone(variables, index);
354 364 }
365
366 void VisualizationTabWidget::VisualizationTabWidgetPrivate::dropProducts(
367 const QVariantList &productsMetaData, int index, VisualizationTabWidget *tabWidget)
368 {
369 // Note: the AcceptMimeDataFunction (set on the drop container) ensure there is a single and
370 // compatible variable here
371 if (productsMetaData.count() != 1) {
372 qCWarning(LOG_VisualizationZoneWidget())
373 << tr("VisualizationTabWidget::dropProducts, dropping multiple products, operation "
374 "aborted.");
375 return;
376 }
377
378 auto context = new QObject{tabWidget};
379 connect(&sqpApp->variableController(), &VariableController::variableAdded, context,
380 [this, index, tabWidget, context](auto variable) {
381 tabWidget->createZone({variable}, index);
382 delete context; // removes the connection
383 },
384 Qt::QueuedConnection);
385
386 auto productData = productsMetaData.first().toHash();
387 QMetaObject::invokeMethod(&sqpApp->dataSourceController(), "requestVariable",
388 Qt::QueuedConnection, Q_ARG(QVariantHash, productData));
389 }
@@ -1,601 +1,652
1 1 #include "Visualization/VisualizationZoneWidget.h"
2 2
3 3 #include "Visualization/IVisualizationWidgetVisitor.h"
4 4 #include "Visualization/QCustomPlotSynchronizer.h"
5 5 #include "Visualization/VisualizationGraphWidget.h"
6 6 #include "Visualization/VisualizationWidget.h"
7 7 #include "ui_VisualizationZoneWidget.h"
8 8
9 9 #include "Common/MimeTypesDef.h"
10 10 #include "Common/VisualizationDef.h"
11 11
12 12 #include <Data/SqpRange.h>
13 #include <DataSource/DataSourceController.h>
13 14 #include <Time/TimeController.h>
14 15 #include <Variable/Variable.h>
15 16 #include <Variable/VariableController.h>
16 17
17 18 #include <Visualization/operations/FindVariableOperation.h>
18 19
19 20 #include <DragAndDrop/DragDropGuiController.h>
20 21 #include <QUuid>
21 22 #include <SqpApplication.h>
22 23 #include <cmath>
23 24
24 25 #include <QLayout>
25 26
26 27 Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget")
27 28
28 29 namespace {
29 30
30 31 /**
31 32 * Applies a function to all graphs of the zone represented by its layout
32 33 * @param layout the layout that contains graphs
33 34 * @param fun the function to apply to each graph
34 35 */
35 36 template <typename Fun>
36 37 void processGraphs(QLayout &layout, Fun fun)
37 38 {
38 39 for (auto i = 0; i < layout.count(); ++i) {
39 40 if (auto item = layout.itemAt(i)) {
40 41 if (auto visualizationGraphWidget
41 42 = qobject_cast<VisualizationGraphWidget *>(item->widget())) {
42 43 fun(*visualizationGraphWidget);
43 44 }
44 45 }
45 46 }
46 47 }
47 48
48 49 /// Generates a default name for a new graph, according to the number of graphs already displayed in
49 50 /// the zone
50 51 QString defaultGraphName(QLayout &layout)
51 52 {
52 53 QSet<QString> existingNames;
53 54 processGraphs(
54 55 layout, [&existingNames](auto &graphWidget) { existingNames.insert(graphWidget.name()); });
55 56
56 57 int zoneNum = 1;
57 58 QString name;
58 59 do {
59 60 name = QObject::tr("Graph ").append(QString::number(zoneNum));
60 61 ++zoneNum;
61 62 } while (existingNames.contains(name));
62 63
63 64 return name;
64 65 }
65 66
66 67 } // namespace
67 68
68 69 struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate {
69 70
70 71 explicit VisualizationZoneWidgetPrivate()
71 72 : m_SynchronisationGroupId{QUuid::createUuid()},
72 73 m_Synchronizer{std::make_unique<QCustomPlotSynchronizer>()}
73 74 {
74 75 }
75 76 QUuid m_SynchronisationGroupId;
76 77 std::unique_ptr<IGraphSynchronizer> m_Synchronizer;
77 78
78 79 void dropGraph(int index, VisualizationZoneWidget *zoneWidget);
79 80 void dropVariables(const QList<std::shared_ptr<Variable> > &variables, int index,
80 81 VisualizationZoneWidget *zoneWidget);
82 void dropProducts(const QVariantList &productsData, int index,
83 VisualizationZoneWidget *zoneWidget);
81 84 };
82 85
83 86 VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent)
84 87 : VisualizationDragWidget{parent},
85 88 ui{new Ui::VisualizationZoneWidget},
86 89 impl{spimpl::make_unique_impl<VisualizationZoneWidgetPrivate>()}
87 90 {
88 91 ui->setupUi(this);
89 92
90 93 ui->zoneNameLabel->setText(name);
91 94
92 95 ui->dragDropContainer->setPlaceHolderType(DragDropGuiController::PlaceHolderType::Graph);
93 96 ui->dragDropContainer->setMimeType(MIME_TYPE_GRAPH,
94 97 VisualizationDragDropContainer::DropBehavior::Inserted);
95 98 ui->dragDropContainer->setMimeType(
96 99 MIME_TYPE_VARIABLE_LIST, VisualizationDragDropContainer::DropBehavior::InsertedAndMerged);
100 ui->dragDropContainer->setMimeType(
101 MIME_TYPE_PRODUCT_LIST, VisualizationDragDropContainer::DropBehavior::InsertedAndMerged);
97 102 ui->dragDropContainer->setMimeType(MIME_TYPE_TIME_RANGE,
98 103 VisualizationDragDropContainer::DropBehavior::Merged);
99 104 ui->dragDropContainer->setMimeType(MIME_TYPE_ZONE,
100 105 VisualizationDragDropContainer::DropBehavior::Forbidden);
101 106 ui->dragDropContainer->setMimeType(MIME_TYPE_SELECTION_ZONE,
102 107 VisualizationDragDropContainer::DropBehavior::Forbidden);
103 108 ui->dragDropContainer->setAcceptMimeDataFunction([this](auto mimeData) {
104 109 return sqpApp->dragDropGuiController().checkMimeDataForVisualization(mimeData,
105 110 ui->dragDropContainer);
106 111 });
107 112
108 113 auto acceptDragWidgetFun = [](auto dragWidget, auto mimeData) {
109 114 if (!mimeData) {
110 115 return false;
111 116 }
112 117
113 118 if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
114 119 auto variables = sqpApp->variableController().variablesForMimeData(
115 120 mimeData->data(MIME_TYPE_VARIABLE_LIST));
116 121
117 122 if (variables.count() != 1) {
118 123 return false;
119 124 }
120 125 auto variable = variables.first();
121 126
122 127 if (auto graphWidget = dynamic_cast<const VisualizationGraphWidget *>(dragWidget)) {
123 128 return graphWidget->canDrop(*variable);
124 129 }
125 130 }
126 131
127 132 return true;
128 133 };
129 134 ui->dragDropContainer->setAcceptDragWidgetFunction(acceptDragWidgetFun);
130 135
131 136 connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccuredInContainer, this,
132 137 &VisualizationZoneWidget::dropMimeData);
133 138 connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccuredOnWidget, this,
134 139 &VisualizationZoneWidget::dropMimeDataOnGraph);
135 140
136 141 // 'Close' options : widget is deleted when closed
137 142 setAttribute(Qt::WA_DeleteOnClose);
138 143 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationZoneWidget::close);
139 144 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
140 145
141 146 // Synchronisation id
142 147 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronizationGroupId",
143 148 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
144 149 }
145 150
146 151 VisualizationZoneWidget::~VisualizationZoneWidget()
147 152 {
148 153 delete ui;
149 154 }
150 155
151 156 void VisualizationZoneWidget::setZoneRange(const SqpRange &range)
152 157 {
153 158 if (auto graph = firstGraph()) {
154 159 graph->setGraphRange(range);
155 160 }
156 161 else {
157 162 qCWarning(LOG_VisualizationZoneWidget())
158 163 << tr("setZoneRange:Cannot set the range of an empty zone.");
159 164 }
160 165 }
161 166
162 167 void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget)
163 168 {
164 169 // Synchronize new graph with others in the zone
165 170 impl->m_Synchronizer->addGraph(*graphWidget);
166 171
167 172 ui->dragDropContainer->addDragWidget(graphWidget);
168 173 }
169 174
170 175 void VisualizationZoneWidget::insertGraph(int index, VisualizationGraphWidget *graphWidget)
171 176 {
172 177 // Synchronize new graph with others in the zone
173 178 impl->m_Synchronizer->addGraph(*graphWidget);
174 179
175 180 ui->dragDropContainer->insertDragWidget(index, graphWidget);
176 181 }
177 182
178 183 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable)
179 184 {
180 185 return createGraph(variable, -1);
181 186 }
182 187
183 188 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable,
184 189 int index)
185 190 {
186 191 auto graphWidget
187 192 = new VisualizationGraphWidget{defaultGraphName(*ui->dragDropContainer->layout()), this};
188 193
189 194
190 195 // Set graph properties
191 196 graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
192 197 graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT);
193 198
194 199
195 200 // Lambda to synchronize zone widget
196 201 auto synchronizeZoneWidget = [this, graphWidget](const SqpRange &graphRange,
197 202 const SqpRange &oldGraphRange) {
198 203
199 204 auto zoomType = VariableController::getZoomType(graphRange, oldGraphRange);
200 205 auto frameLayout = ui->dragDropContainer->layout();
201 206 for (auto i = 0; i < frameLayout->count(); ++i) {
202 207 auto graphChild
203 208 = dynamic_cast<VisualizationGraphWidget *>(frameLayout->itemAt(i)->widget());
204 209 if (graphChild && (graphChild != graphWidget)) {
205 210
206 211 auto graphChildRange = graphChild->graphRange();
207 212 switch (zoomType) {
208 213 case AcquisitionZoomType::ZoomIn: {
209 214 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
210 215 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
211 216 graphChildRange.m_TStart += deltaLeft;
212 217 graphChildRange.m_TEnd -= deltaRight;
213 218 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomIn");
214 219 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
215 220 << deltaLeft;
216 221 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
217 222 << deltaRight;
218 223 qCDebug(LOG_VisualizationZoneWidget())
219 224 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
220 225
221 226 break;
222 227 }
223 228
224 229 case AcquisitionZoomType::ZoomOut: {
225 230 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomOut");
226 231 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
227 232 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
228 233 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
229 234 << deltaLeft;
230 235 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
231 236 << deltaRight;
232 237 qCDebug(LOG_VisualizationZoneWidget())
233 238 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
234 239 graphChildRange.m_TStart -= deltaLeft;
235 240 graphChildRange.m_TEnd += deltaRight;
236 241 break;
237 242 }
238 243 case AcquisitionZoomType::PanRight: {
239 244 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanRight");
240 245 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
241 246 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
242 247 graphChildRange.m_TStart += deltaLeft;
243 248 graphChildRange.m_TEnd += deltaRight;
244 249 qCDebug(LOG_VisualizationZoneWidget())
245 250 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
246 251 break;
247 252 }
248 253 case AcquisitionZoomType::PanLeft: {
249 254 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanLeft");
250 255 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
251 256 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
252 257 graphChildRange.m_TStart -= deltaLeft;
253 258 graphChildRange.m_TEnd -= deltaRight;
254 259 break;
255 260 }
256 261 case AcquisitionZoomType::Unknown: {
257 262 qCDebug(LOG_VisualizationZoneWidget())
258 263 << tr("Impossible to synchronize: zoom type unknown");
259 264 break;
260 265 }
261 266 default:
262 267 qCCritical(LOG_VisualizationZoneWidget())
263 268 << tr("Impossible to synchronize: zoom type not take into account");
264 269 // No action
265 270 break;
266 271 }
267 272 graphChild->setFlags(GraphFlag::DisableAll);
268 273 qCDebug(LOG_VisualizationZoneWidget())
269 274 << tr("TORM: Range before: ") << graphChild->graphRange();
270 275 qCDebug(LOG_VisualizationZoneWidget())
271 276 << tr("TORM: Range after : ") << graphChildRange;
272 277 qCDebug(LOG_VisualizationZoneWidget())
273 278 << tr("TORM: child dt") << graphChildRange.m_TEnd - graphChildRange.m_TStart;
274 279 graphChild->setGraphRange(graphChildRange);
275 280 graphChild->setFlags(GraphFlag::EnableAll);
276 281 }
277 282 }
278 283 };
279 284
280 285 // connection for synchronization
281 286 connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget);
282 287 connect(graphWidget, &VisualizationGraphWidget::variableAdded, this,
283 288 &VisualizationZoneWidget::onVariableAdded);
284 289 connect(graphWidget, &VisualizationGraphWidget::variableAboutToBeRemoved, this,
285 290 &VisualizationZoneWidget::onVariableAboutToBeRemoved);
286 291
287 292 auto range = SqpRange{};
288 293 if (auto firstGraph = this->firstGraph()) {
289 294 // Case of a new graph in a existant zone
290 295 range = firstGraph->graphRange();
291 296 }
292 297 else {
293 298 // Case of a new graph as the first of the zone
294 299 range = variable->range();
295 300 }
296 301
297 302 this->insertGraph(index, graphWidget);
298 303
299 304 graphWidget->addVariable(variable, range);
300 305 graphWidget->setYRange(variable);
301 306
302 307 return graphWidget;
303 308 }
304 309
305 310 VisualizationGraphWidget *
306 311 VisualizationZoneWidget::createGraph(const QList<std::shared_ptr<Variable> > variables, int index)
307 312 {
308 313 if (variables.isEmpty()) {
309 314 return nullptr;
310 315 }
311 316
312 317 auto graphWidget = createGraph(variables.first(), index);
313 318 for (auto variableIt = variables.cbegin() + 1; variableIt != variables.cend(); ++variableIt) {
314 319 graphWidget->addVariable(*variableIt, graphWidget->graphRange());
315 320 }
316 321
317 322 return graphWidget;
318 323 }
319 324
320 325 VisualizationGraphWidget *VisualizationZoneWidget::firstGraph() const
321 326 {
322 327 VisualizationGraphWidget *firstGraph = nullptr;
323 328 auto layout = ui->dragDropContainer->layout();
324 329 if (layout->count() > 0) {
325 330 if (auto visualizationGraphWidget
326 331 = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
327 332 firstGraph = visualizationGraphWidget;
328 333 }
329 334 }
330 335
331 336 return firstGraph;
332 337 }
333 338
334 339 void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor)
335 340 {
336 341 if (visitor) {
337 342 visitor->visitEnter(this);
338 343
339 344 // Apply visitor to graph children: widgets different from graphs are not visited (no
340 345 // action)
341 346 processGraphs(
342 347 *ui->dragDropContainer->layout(),
343 348 [visitor](VisualizationGraphWidget &graphWidget) { graphWidget.accept(visitor); });
344 349
345 350 visitor->visitLeave(this);
346 351 }
347 352 else {
348 353 qCCritical(LOG_VisualizationZoneWidget()) << tr("Can't visit widget : the visitor is null");
349 354 }
350 355 }
351 356
352 357 bool VisualizationZoneWidget::canDrop(const Variable &variable) const
353 358 {
354 359 // A tab can always accomodate a variable
355 360 Q_UNUSED(variable);
356 361 return true;
357 362 }
358 363
359 364 bool VisualizationZoneWidget::contains(const Variable &variable) const
360 365 {
361 366 Q_UNUSED(variable);
362 367 return false;
363 368 }
364 369
365 370 QString VisualizationZoneWidget::name() const
366 371 {
367 372 return ui->zoneNameLabel->text();
368 373 }
369 374
370 375 QMimeData *VisualizationZoneWidget::mimeData(const QPoint &position) const
371 376 {
372 377 Q_UNUSED(position);
373 378
374 379 auto mimeData = new QMimeData;
375 380 mimeData->setData(MIME_TYPE_ZONE, QByteArray{});
376 381
377 382 if (auto firstGraph = this->firstGraph()) {
378 383 auto timeRangeData = TimeController::mimeDataForTimeRange(firstGraph->graphRange());
379 384 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
380 385 }
381 386
382 387 return mimeData;
383 388 }
384 389
385 390 bool VisualizationZoneWidget::isDragAllowed() const
386 391 {
387 392 return true;
388 393 }
389 394
390 395 void VisualizationZoneWidget::notifyMouseMoveInGraph(const QPointF &graphPosition,
391 396 const QPointF &plotPosition,
392 397 VisualizationGraphWidget *graphWidget)
393 398 {
394 399 processGraphs(*ui->dragDropContainer->layout(), [&graphPosition, &plotPosition, &graphWidget](
395 400 VisualizationGraphWidget &processedGraph) {
396 401
397 402 switch (sqpApp->plotsCursorMode()) {
398 403 case SqpApplication::PlotsCursorMode::Vertical:
399 404 processedGraph.removeHorizontalCursor();
400 405 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
401 406 break;
402 407 case SqpApplication::PlotsCursorMode::Temporal:
403 408 processedGraph.addVerticalCursor(plotPosition.x());
404 409 processedGraph.removeHorizontalCursor();
405 410 break;
406 411 case SqpApplication::PlotsCursorMode::Horizontal:
407 412 processedGraph.removeVerticalCursor();
408 413 if (&processedGraph == graphWidget) {
409 414 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
410 415 }
411 416 else {
412 417 processedGraph.removeHorizontalCursor();
413 418 }
414 419 break;
415 420 case SqpApplication::PlotsCursorMode::Cross:
416 421 if (&processedGraph == graphWidget) {
417 422 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
418 423 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
419 424 }
420 425 else {
421 426 processedGraph.removeHorizontalCursor();
422 427 processedGraph.removeVerticalCursor();
423 428 }
424 429 break;
425 430 case SqpApplication::PlotsCursorMode::NoCursor:
426 431 processedGraph.removeHorizontalCursor();
427 432 processedGraph.removeVerticalCursor();
428 433 break;
429 434 }
430 435
431 436
432 437 });
433 438 }
434 439
435 440 void VisualizationZoneWidget::notifyMouseLeaveGraph(VisualizationGraphWidget *graphWidget)
436 441 {
437 442 processGraphs(*ui->dragDropContainer->layout(), [](VisualizationGraphWidget &processedGraph) {
438 443 processedGraph.removeHorizontalCursor();
439 444 processedGraph.removeVerticalCursor();
440 445 });
441 446 }
442 447
443 448 void VisualizationZoneWidget::closeEvent(QCloseEvent *event)
444 449 {
445 450 // Closes graphs in the zone
446 451 processGraphs(*ui->dragDropContainer->layout(),
447 452 [](VisualizationGraphWidget &graphWidget) { graphWidget.close(); });
448 453
449 454 // Delete synchronization group from variable controller
450 455 QMetaObject::invokeMethod(&sqpApp->variableController(), "onRemoveSynchronizationGroupId",
451 456 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
452 457
453 458 QWidget::closeEvent(event);
454 459 }
455 460
456 461 void VisualizationZoneWidget::onVariableAdded(std::shared_ptr<Variable> variable)
457 462 {
458 463 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronized",
459 464 Qt::QueuedConnection, Q_ARG(std::shared_ptr<Variable>, variable),
460 465 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
461 466 }
462 467
463 468 void VisualizationZoneWidget::onVariableAboutToBeRemoved(std::shared_ptr<Variable> variable)
464 469 {
465 470 QMetaObject::invokeMethod(&sqpApp->variableController(), "desynchronize", Qt::QueuedConnection,
466 471 Q_ARG(std::shared_ptr<Variable>, variable),
467 472 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
468 473 }
469 474
470 475 void VisualizationZoneWidget::dropMimeData(int index, const QMimeData *mimeData)
471 476 {
472 477 if (mimeData->hasFormat(MIME_TYPE_GRAPH)) {
473 478 impl->dropGraph(index, this);
474 479 }
475 480 else if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
476 481 auto variables = sqpApp->variableController().variablesForMimeData(
477 482 mimeData->data(MIME_TYPE_VARIABLE_LIST));
478 483 impl->dropVariables(variables, index, this);
479 484 }
485 else if (mimeData->hasFormat(MIME_TYPE_PRODUCT_LIST)) {
486 auto products = sqpApp->dataSourceController().productsDataForMimeData(
487 mimeData->data(MIME_TYPE_PRODUCT_LIST));
488 impl->dropProducts(products, index, this);
489 }
480 490 else {
481 491 qCWarning(LOG_VisualizationZoneWidget())
482 492 << tr("VisualizationZoneWidget::dropMimeData, unknown MIME data received.");
483 493 }
484 494 }
485 495
486 496 void VisualizationZoneWidget::dropMimeDataOnGraph(VisualizationDragWidget *dragWidget,
487 497 const QMimeData *mimeData)
488 498 {
489 499 auto graphWidget = qobject_cast<VisualizationGraphWidget *>(dragWidget);
490 500 if (!graphWidget) {
491 501 qCWarning(LOG_VisualizationZoneWidget())
492 502 << tr("VisualizationZoneWidget::dropMimeDataOnGraph, dropping in an unknown widget, "
493 503 "drop aborted");
494 504 Q_ASSERT(false);
495 505 return;
496 506 }
497 507
498 508 if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
499 509 auto variables = sqpApp->variableController().variablesForMimeData(
500 510 mimeData->data(MIME_TYPE_VARIABLE_LIST));
501 511 for (const auto &var : variables) {
502 512 graphWidget->addVariable(var, graphWidget->graphRange());
503 513 }
504 514 }
515 else if (mimeData->hasFormat(MIME_TYPE_PRODUCT_LIST)) {
516 auto products = sqpApp->dataSourceController().productsDataForMimeData(
517 mimeData->data(MIME_TYPE_PRODUCT_LIST));
518
519 auto context = new QObject{this};
520 connect(&sqpApp->variableController(), &VariableController::variableAdded, context,
521 [this, graphWidget, context](auto variable) {
522 graphWidget->addVariable(variable, graphWidget->graphRange());
523 delete context; // removes the connection
524 },
525 Qt::QueuedConnection);
526
527 auto productData = products.first().toHash();
528 QMetaObject::invokeMethod(&sqpApp->dataSourceController(), "requestVariable",
529 Qt::QueuedConnection, Q_ARG(QVariantHash, productData));
530 }
505 531 else if (mimeData->hasFormat(MIME_TYPE_TIME_RANGE)) {
506 532 auto range = TimeController::timeRangeForMimeData(mimeData->data(MIME_TYPE_TIME_RANGE));
507 533 graphWidget->setGraphRange(range);
508 534 }
509 535 else {
510 536 qCWarning(LOG_VisualizationZoneWidget())
511 537 << tr("VisualizationZoneWidget::dropMimeDataOnGraph, unknown MIME data received.");
512 538 }
513 539 }
514 540
515 541 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropGraph(
516 542 int index, VisualizationZoneWidget *zoneWidget)
517 543 {
518 544 auto &helper = sqpApp->dragDropGuiController();
519 545
520 546 auto graphWidget = qobject_cast<VisualizationGraphWidget *>(helper.getCurrentDragWidget());
521 547 if (!graphWidget) {
522 548 qCWarning(LOG_VisualizationZoneWidget())
523 549 << tr("VisualizationZoneWidget::dropGraph, drop aborted, the dropped graph is not "
524 550 "found or invalid.");
525 551 Q_ASSERT(false);
526 552 return;
527 553 }
528 554
529 555 auto parentDragDropContainer
530 556 = qobject_cast<VisualizationDragDropContainer *>(graphWidget->parentWidget());
531 557 if (!parentDragDropContainer) {
532 558 qCWarning(LOG_VisualizationZoneWidget())
533 559 << tr("VisualizationZoneWidget::dropGraph, drop aborted, the parent container of "
534 560 "the dropped graph is not found.");
535 561 Q_ASSERT(false);
536 562 return;
537 563 }
538 564
539 565 const auto &variables = graphWidget->variables();
540 566
541 567 if (parentDragDropContainer != zoneWidget->ui->dragDropContainer && !variables.isEmpty()) {
542 568 // The drop didn't occur in the same zone
543 569
544 570 // Abort the requests for the variables (if any)
545 571 // Commented, because it's not sure if it's needed or not
546 572 // for (const auto& var : variables)
547 573 //{
548 574 // sqpApp->variableController().onAbortProgressRequested(var);
549 575 //}
550 576
551 577 auto previousParentZoneWidget = graphWidget->parentZoneWidget();
552 578 auto nbGraph = parentDragDropContainer->countDragWidget();
553 579 if (nbGraph == 1) {
554 580 // This is the only graph in the previous zone, close the zone
555 581 helper.delayedCloseWidget(previousParentZoneWidget);
556 582 }
557 583 else {
558 584 // Close the graph
559 585 helper.delayedCloseWidget(graphWidget);
560 586 }
561 587
562 588 // Creates the new graph in the zone
563 589 auto newGraphWidget = zoneWidget->createGraph(variables, index);
564 590 newGraphWidget->addSelectionZones(graphWidget->selectionZoneRanges());
565 591 }
566 592 else {
567 593 // The drop occurred in the same zone or the graph is empty
568 594 // Simple move of the graph, no variable operation associated
569 595 parentDragDropContainer->layout()->removeWidget(graphWidget);
570 596
571 597 if (variables.isEmpty() && parentDragDropContainer != zoneWidget->ui->dragDropContainer) {
572 598 // The graph is empty and dropped in a different zone.
573 599 // Take the range of the first graph in the zone (if existing).
574 600 auto layout = zoneWidget->ui->dragDropContainer->layout();
575 601 if (layout->count() > 0) {
576 602 if (auto visualizationGraphWidget
577 603 = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
578 604 graphWidget->setGraphRange(visualizationGraphWidget->graphRange());
579 605 }
580 606 }
581 607 }
582 608
583 609 zoneWidget->ui->dragDropContainer->insertDragWidget(index, graphWidget);
584 610 }
585 611 }
586 612
587 613 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropVariables(
588 614 const QList<std::shared_ptr<Variable> > &variables, int index,
589 615 VisualizationZoneWidget *zoneWidget)
590 616 {
591 617 // Note: the AcceptMimeDataFunction (set on the drop container) ensure there is a single and
592 618 // compatible variable here
593 619 if (variables.count() > 1) {
594 620 qCWarning(LOG_VisualizationZoneWidget())
595 621 << tr("VisualizationZoneWidget::dropVariables, dropping multiple variables, operation "
596 622 "aborted.");
597 623 return;
598 624 }
599 625
600 626 zoneWidget->createGraph(variables, index);
601 627 }
628
629 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropProducts(
630 const QVariantList &productsData, int index, VisualizationZoneWidget *zoneWidget)
631 {
632 // Note: the AcceptMimeDataFunction (set on the drop container) ensure there is a single and
633 // compatible variable here
634 if (productsData.count() != 1) {
635 qCWarning(LOG_VisualizationZoneWidget())
636 << tr("VisualizationTabWidget::dropProducts, dropping multiple products, operation "
637 "aborted.");
638 return;
639 }
640
641 auto context = new QObject{zoneWidget};
642 connect(&sqpApp->variableController(), &VariableController::variableAdded, context,
643 [this, index, zoneWidget, context](auto variable) {
644 zoneWidget->createGraph(variable, index);
645 delete context; // removes the connection
646 },
647 Qt::QueuedConnection);
648
649 auto productData = productsData.first().toHash();
650 QMetaObject::invokeMethod(&sqpApp->dataSourceController(), "requestVariable",
651 Qt::QueuedConnection, Q_ARG(QVariantHash, productData));
652 }
@@ -1,148 +1,145
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <ui version="4.0">
3 3 <class>CatalogueEventsWidget</class>
4 4 <widget class="QWidget" name="CatalogueEventsWidget">
5 5 <property name="geometry">
6 6 <rect>
7 7 <x>0</x>
8 8 <y>0</y>
9 9 <width>566</width>
10 10 <height>258</height>
11 11 </rect>
12 12 </property>
13 13 <property name="windowTitle">
14 14 <string>Form</string>
15 15 </property>
16 16 <layout class="QVBoxLayout" name="verticalLayout">
17 17 <property name="leftMargin">
18 18 <number>0</number>
19 19 </property>
20 20 <property name="topMargin">
21 21 <number>0</number>
22 22 </property>
23 23 <property name="rightMargin">
24 24 <number>0</number>
25 25 </property>
26 26 <property name="bottomMargin">
27 27 <number>0</number>
28 28 </property>
29 29 <item>
30 30 <layout class="QHBoxLayout" name="horizontalLayout">
31 31 <item>
32 32 <widget class="QToolButton" name="btnAdd">
33 33 <property name="enabled">
34 34 <bool>false</bool>
35 35 </property>
36 36 <property name="text">
37 37 <string>+</string>
38 38 </property>
39 39 <property name="icon">
40 40 <iconset resource="../../resources/sqpguiresources.qrc">
41 41 <normaloff>:/icones/add.png</normaloff>:/icones/add.png</iconset>
42 42 </property>
43 43 <property name="autoRaise">
44 44 <bool>true</bool>
45 45 </property>
46 46 </widget>
47 47 </item>
48 48 <item>
49 49 <widget class="QToolButton" name="btnRemove">
50 50 <property name="text">
51 51 <string> - </string>
52 52 </property>
53 53 <property name="icon">
54 54 <iconset resource="../../resources/sqpguiresources.qrc">
55 55 <normaloff>:/icones/remove.png</normaloff>:/icones/remove.png</iconset>
56 56 </property>
57 57 <property name="autoRaise">
58 58 <bool>true</bool>
59 59 </property>
60 60 </widget>
61 61 </item>
62 62 <item>
63 63 <widget class="Line" name="line">
64 64 <property name="orientation">
65 65 <enum>Qt::Vertical</enum>
66 66 </property>
67 67 </widget>
68 68 </item>
69 69 <item>
70 70 <widget class="QToolButton" name="btnTime">
71 71 <property name="text">
72 72 <string>T</string>
73 73 </property>
74 74 <property name="icon">
75 75 <iconset resource="../../resources/sqpguiresources.qrc">
76 76 <normaloff>:/icones/time.png</normaloff>:/icones/time.png</iconset>
77 77 </property>
78 78 <property name="checkable">
79 79 <bool>true</bool>
80 80 </property>
81 81 <property name="autoRaise">
82 82 <bool>true</bool>
83 83 </property>
84 84 </widget>
85 85 </item>
86 86 <item>
87 87 <widget class="QToolButton" name="btnChart">
88 <property name="enabled">
89 <bool>false</bool>
90 </property>
91 88 <property name="text">
92 89 <string>G</string>
93 90 </property>
94 91 <property name="icon">
95 92 <iconset resource="../../resources/sqpguiresources.qrc">
96 93 <normaloff>:/icones/chart.png</normaloff>:/icones/chart.png</iconset>
97 94 </property>
98 95 <property name="checkable">
99 96 <bool>true</bool>
100 97 </property>
101 98 <property name="autoRaise">
102 99 <bool>true</bool>
103 100 </property>
104 101 </widget>
105 102 </item>
106 103 <item>
107 104 <widget class="Line" name="line_2">
108 105 <property name="orientation">
109 106 <enum>Qt::Vertical</enum>
110 107 </property>
111 108 </widget>
112 109 </item>
113 110 <item>
114 111 <widget class="QLineEdit" name="lineEdit">
115 112 <property name="enabled">
116 113 <bool>false</bool>
117 114 </property>
118 115 </widget>
119 116 </item>
120 117 </layout>
121 118 </item>
122 119 <item>
123 120 <widget class="QTreeView" name="treeView">
124 121 <property name="dragEnabled">
125 122 <bool>true</bool>
126 123 </property>
127 124 <property name="dragDropMode">
128 125 <enum>QAbstractItemView::DragDrop</enum>
129 126 </property>
130 127 <property name="selectionMode">
131 128 <enum>QAbstractItemView::ExtendedSelection</enum>
132 129 </property>
133 130 <property name="selectionBehavior">
134 131 <enum>QAbstractItemView::SelectRows</enum>
135 132 </property>
136 133 <attribute name="headerStretchLastSection">
137 134 <bool>false</bool>
138 135 </attribute>
139 136 </widget>
140 137 </item>
141 138 </layout>
142 139 </widget>
143 140 <resources>
144 141 <include location="../../resources/sqpguiresources.qrc"/>
145 142 <include location="../../resources/sqpguiresources.qrc"/>
146 143 </resources>
147 144 <connections/>
148 145 </ui>
@@ -1,21 +1,21
1 1 #ifndef SCIQLOP_AMDARESULTPARSER_H
2 2 #define SCIQLOP_AMDARESULTPARSER_H
3 3
4 4 #include "AmdaGlobal.h"
5 5
6 #include <Data/DataSeriesType.h>
7
6 8 #include <QLoggingCategory>
7 9
8 10 #include <memory>
9 11
10 12 class IDataSeries;
11 13
12 14 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaResultParser)
13 15
14 16 struct SCIQLOP_AMDA_EXPORT AmdaResultParser {
15 enum class ValueType { SCALAR, SPECTROGRAM, VECTOR, UNKNOWN };
16
17 17 static std::shared_ptr<IDataSeries> readTxt(const QString &filePath,
18 ValueType valueType) noexcept;
18 DataSeriesType valueType) noexcept;
19 19 };
20 20
21 21 #endif // SCIQLOP_AMDARESULTPARSER_H
@@ -1,288 +1,274
1 1 #include "AmdaProvider.h"
2 2 #include "AmdaDefs.h"
3 3 #include "AmdaResultParser.h"
4 4 #include "AmdaServer.h"
5 5
6 6 #include <Common/DateUtils.h>
7 7 #include <Data/DataProviderParameters.h>
8 8 #include <Network/NetworkController.h>
9 9 #include <SqpApplication.h>
10 10 #include <Variable/Variable.h>
11 11
12 12 #include <QNetworkAccessManager>
13 13 #include <QNetworkReply>
14 14 #include <QTemporaryFile>
15 15 #include <QThread>
16 16
17 17 Q_LOGGING_CATEGORY(LOG_AmdaProvider, "AmdaProvider")
18 18
19 19 namespace {
20 20
21 21 /// URL format for a request on AMDA server. The parameters are as follows:
22 22 /// - %1: server URL
23 23 /// - %2: start date
24 24 /// - %3: end date
25 25 /// - %4: parameter id
26 26 /// AMDA V2: http://amdatest.irap.omp.eu/php/rest/
27 27 const auto AMDA_URL_FORMAT = QStringLiteral(
28 28 "http://%1/php/rest/"
29 29 "getParameter.php?startTime=%2&stopTime=%3&parameterID=%4&outputFormat=ASCII&"
30 30 "timeFormat=ISO8601&gzip=0");
31 31
32 32 /// Dates format passed in the URL (e.g 2013-09-23T09:00)
33 33 const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss");
34 34
35 35 /// Formats a time to a date that can be passed in URL
36 36 QString dateFormat(double sqpRange) noexcept
37 37 {
38 38 auto dateTime = DateUtils::dateTime(sqpRange);
39 39 return dateTime.toString(AMDA_TIME_FORMAT);
40 40 }
41 41
42 AmdaResultParser::ValueType valueType(const QString &valueType)
43 {
44 if (valueType == QStringLiteral("scalar")) {
45 return AmdaResultParser::ValueType::SCALAR;
46 }
47 else if (valueType == QStringLiteral("spectrogram")) {
48 return AmdaResultParser::ValueType::SPECTROGRAM;
49 }
50 else if (valueType == QStringLiteral("vector")) {
51 return AmdaResultParser::ValueType::VECTOR;
52 }
53 else {
54 return AmdaResultParser::ValueType::UNKNOWN;
55 }
56 }
57 42
58 43 } // namespace
59 44
60 45 AmdaProvider::AmdaProvider()
61 46 {
62 47 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::AmdaProvider") << QThread::currentThread();
63 48 if (auto app = sqpApp) {
64 49 auto &networkController = app->networkController();
65 50 connect(this, SIGNAL(requestConstructed(std::shared_ptr<QNetworkRequest>, QUuid,
66 51 std::function<void(QNetworkReply *, QUuid)>)),
67 52 &networkController,
68 53 SLOT(onProcessRequested(std::shared_ptr<QNetworkRequest>, QUuid,
69 54 std::function<void(QNetworkReply *, QUuid)>)));
70 55
71 56
72 57 connect(&sqpApp->networkController(),
73 58 SIGNAL(replyDownloadProgress(QUuid, std::shared_ptr<QNetworkRequest>, double)),
74 59 this,
75 60 SLOT(onReplyDownloadProgress(QUuid, std::shared_ptr<QNetworkRequest>, double)));
76 61 }
77 62 }
78 63
79 64 std::shared_ptr<IDataProvider> AmdaProvider::clone() const
80 65 {
81 66 // No copy is made in the clone
82 67 return std::make_shared<AmdaProvider>();
83 68 }
84 69
85 70 void AmdaProvider::requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters)
86 71 {
87 72 // NOTE: Try to use multithread if possible
88 73 const auto times = parameters.m_Times;
89 74 const auto data = parameters.m_Data;
90 75 for (const auto &dateTime : qAsConst(times)) {
91 76 qCDebug(LOG_AmdaProvider()) << tr("TORM AmdaProvider::requestDataLoading ") << acqIdentifier
92 77 << dateTime;
93 78 this->retrieveData(acqIdentifier, dateTime, data);
94 79
95 80
96 81 // TORM when AMDA will support quick asynchrone request
97 82 QThread::msleep(1000);
98 83 }
99 84 }
100 85
101 86 void AmdaProvider::requestDataAborting(QUuid acqIdentifier)
102 87 {
103 88 if (auto app = sqpApp) {
104 89 auto &networkController = app->networkController();
105 90 networkController.onReplyCanceled(acqIdentifier);
106 91 }
107 92 }
108 93
109 94 void AmdaProvider::onReplyDownloadProgress(QUuid acqIdentifier,
110 95 std::shared_ptr<QNetworkRequest> networkRequest,
111 96 double progress)
112 97 {
113 98 qCDebug(LOG_AmdaProvider()) << tr("onReplyDownloadProgress") << acqIdentifier
114 99 << networkRequest.get() << progress;
115 100 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
116 101 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
117 102
118 103 // Update the progression for the current request
119 104 auto requestPtr = networkRequest;
120 105 auto findRequest = [requestPtr](const auto &entry) { return requestPtr == entry.first; };
121 106
122 107 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
123 108 auto requestProgressMapEnd = requestProgressMap.end();
124 109 auto requestProgressMapIt
125 110 = std::find_if(requestProgressMap.begin(), requestProgressMapEnd, findRequest);
126 111
127 112 if (requestProgressMapIt != requestProgressMapEnd) {
128 113 requestProgressMapIt->second = progress;
129 114 }
130 115 else {
131 116 // This case can happened when a progression is send after the request has been
132 117 // finished.
133 118 // Generaly the case when aborting a request
134 119 qCDebug(LOG_AmdaProvider()) << tr("Can't retrieve Request in progress") << acqIdentifier
135 120 << networkRequest.get() << progress;
136 121 }
137 122
138 123 // Compute the current final progress and notify it
139 124 double finalProgress = 0.0;
140 125
141 126 auto fraq = requestProgressMap.size();
142 127
143 128 for (auto requestProgress : requestProgressMap) {
144 129 finalProgress += requestProgress.second;
145 130 qCDebug(LOG_AmdaProvider()) << tr("Current final progress without fraq:")
146 131 << finalProgress << requestProgress.second;
147 132 }
148 133
149 134 if (fraq > 0) {
150 135 finalProgress = finalProgress / fraq;
151 136 }
152 137
153 138 qCDebug(LOG_AmdaProvider()) << tr("Current final progress: ") << fraq << finalProgress;
154 139 emit dataProvidedProgress(acqIdentifier, finalProgress);
155 140 }
156 141 else {
157 142 // This case can happened when a progression is send after the request has been finished.
158 143 // Generaly the case when aborting a request
159 144 emit dataProvidedProgress(acqIdentifier, 100.0);
160 145 }
161 146 }
162 147
163 148 void AmdaProvider::retrieveData(QUuid token, const SqpRange &dateTime, const QVariantHash &data)
164 149 {
165 150 // Retrieves product ID from data: if the value is invalid, no request is made
166 151 auto productId = data.value(AMDA_XML_ID_KEY).toString();
167 152 if (productId.isNull()) {
168 153 qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve data: unknown product id");
169 154 return;
170 155 }
171 156
172 157 // Retrieves the data type that determines whether the expected format for the result file is
173 158 // scalar, vector...
174 auto productValueType = valueType(data.value(AMDA_DATA_TYPE_KEY).toString());
159 auto productValueType
160 = DataSeriesTypeUtils::fromString(data.value(AMDA_DATA_TYPE_KEY).toString());
175 161
176 162 // /////////// //
177 163 // Creates URL //
178 164 // /////////// //
179 165
180 166 auto startDate = dateFormat(dateTime.m_TStart);
181 167 auto endDate = dateFormat(dateTime.m_TEnd);
182 168
183 169 QVariantHash urlProperties{{AMDA_SERVER_KEY, data.value(AMDA_SERVER_KEY)}};
184 170 auto url = QUrl{QString{AMDA_URL_FORMAT}.arg(AmdaServer::instance().url(urlProperties),
185 171 startDate, endDate, productId)};
186 172 qCInfo(LOG_AmdaProvider()) << tr("TORM AmdaProvider::retrieveData url:") << url;
187 173 auto tempFile = std::make_shared<QTemporaryFile>();
188 174
189 175 // LAMBDA
190 176 auto httpDownloadFinished = [this, dateTime, tempFile,
191 177 productValueType](QNetworkReply *reply, QUuid dataId) noexcept {
192 178
193 179 // Don't do anything if the reply was abort
194 180 if (reply->error() == QNetworkReply::NoError) {
195 181
196 182 if (tempFile) {
197 183 auto replyReadAll = reply->readAll();
198 184 if (!replyReadAll.isEmpty()) {
199 185 tempFile->write(replyReadAll);
200 186 }
201 187 tempFile->close();
202 188
203 189 // Parse results file
204 190 if (auto dataSeries
205 191 = AmdaResultParser::readTxt(tempFile->fileName(), productValueType)) {
206 192 emit dataProvided(dataId, dataSeries, dateTime);
207 193 }
208 194 else {
209 195 /// @todo ALX : debug
210 196 emit dataProvidedFailed(dataId);
211 197 }
212 198 }
213 199 m_AcqIdToRequestProgressMap.erase(dataId);
214 200 }
215 201 else {
216 202 qCCritical(LOG_AmdaProvider()) << tr("httpDownloadFinished ERROR");
217 203 emit dataProvidedFailed(dataId);
218 204 }
219 205
220 206 };
221 207 auto httpFinishedLambda
222 208 = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
223 209
224 210 // Don't do anything if the reply was abort
225 211 if (reply->error() == QNetworkReply::NoError) {
226 212 auto downloadFileUrl = QUrl{QString{reply->readAll()}.trimmed()};
227 213
228 214 qCInfo(LOG_AmdaProvider())
229 215 << tr("TORM AmdaProvider::retrieveData downloadFileUrl:") << downloadFileUrl;
230 216 // Executes request for downloading file //
231 217
232 218 // Creates destination file
233 219 if (tempFile->open()) {
234 220 // Executes request and store the request for progression
235 221 auto request = std::make_shared<QNetworkRequest>(downloadFileUrl);
236 222 updateRequestProgress(dataId, request, 0.0);
237 223 emit requestConstructed(request, dataId, httpDownloadFinished);
238 224 }
239 225 else {
240 226 emit dataProvidedFailed(dataId);
241 227 }
242 228 }
243 229 else {
244 230 qCCritical(LOG_AmdaProvider()) << tr("httpFinishedLambda ERROR");
245 231 m_AcqIdToRequestProgressMap.erase(dataId);
246 232 emit dataProvidedFailed(dataId);
247 233 }
248 234 };
249 235
250 236 // //////////////// //
251 237 // Executes request //
252 238 // //////////////// //
253 239
254 240 auto request = std::make_shared<QNetworkRequest>(url);
255 241 qCDebug(LOG_AmdaProvider()) << tr("First Request creation") << request.get();
256 242 updateRequestProgress(token, request, 0.0);
257 243
258 244 emit requestConstructed(request, token, httpFinishedLambda);
259 245 }
260 246
261 247 void AmdaProvider::updateRequestProgress(QUuid acqIdentifier,
262 248 std::shared_ptr<QNetworkRequest> request, double progress)
263 249 {
264 250 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress request") << request.get();
265 251 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
266 252 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
267 253 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
268 254 auto requestProgressMapIt = requestProgressMap.find(request);
269 255 if (requestProgressMapIt != requestProgressMap.end()) {
270 256 requestProgressMapIt->second = progress;
271 257 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new progress for request")
272 258 << acqIdentifier << request.get() << progress;
273 259 }
274 260 else {
275 261 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new request") << acqIdentifier
276 262 << request.get() << progress;
277 263 acqIdToRequestProgressMapIt->second.insert(std::make_pair(request, progress));
278 264 }
279 265 }
280 266 else {
281 267 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new acqIdentifier")
282 268 << acqIdentifier << request.get() << progress;
283 269 auto requestProgressMap = std::map<std::shared_ptr<QNetworkRequest>, double>{};
284 270 requestProgressMap.insert(std::make_pair(request, progress));
285 271 m_AcqIdToRequestProgressMap.insert(
286 272 std::make_pair(acqIdentifier, std::move(requestProgressMap)));
287 273 }
288 274 }
@@ -1,133 +1,133
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include "AmdaResultParserHelper.h"
4 4
5 5 #include <QFile>
6 6
7 7 #include <cmath>
8 8
9 9 Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser")
10 10
11 11 namespace {
12 12
13 13 /// Message in result file when the file was not found on server
14 14 const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found");
15 15
16 16 /// Checks if a line is a comment line
17 17 bool isCommentLine(const QString &line)
18 18 {
19 19 return line.startsWith("#");
20 20 }
21 21
22 22 /**
23 23 * Creates helper that will be used to read AMDA file, according to the type passed as parameter
24 24 * @param valueType the type of values expected in the AMDA file (scalars, vectors, spectrograms...)
25 25 * @return the helper created
26 26 */
27 std::unique_ptr<IAmdaResultParserHelper> createHelper(AmdaResultParser::ValueType valueType)
27 std::unique_ptr<IAmdaResultParserHelper> createHelper(DataSeriesType valueType)
28 28 {
29 29 switch (valueType) {
30 case AmdaResultParser::ValueType::SCALAR:
30 case DataSeriesType::SCALAR:
31 31 return std::make_unique<ScalarParserHelper>();
32 case AmdaResultParser::ValueType::SPECTROGRAM:
32 case DataSeriesType::SPECTROGRAM:
33 33 return std::make_unique<SpectrogramParserHelper>();
34 case AmdaResultParser::ValueType::VECTOR:
34 case DataSeriesType::VECTOR:
35 35 return std::make_unique<VectorParserHelper>();
36 case AmdaResultParser::ValueType::UNKNOWN:
36 case DataSeriesType::UNKNOWN:
37 37 // Invalid case
38 38 break;
39 39 }
40 40
41 41 // Invalid cases
42 42 qCCritical(LOG_AmdaResultParser())
43 43 << QObject::tr("Can't create helper to read result file: unsupported type");
44 44 return nullptr;
45 45 }
46 46
47 47 /**
48 48 * Reads properties of the stream passed as parameter
49 49 * @param helper the helper used to read properties line by line
50 50 * @param stream the stream to read
51 51 */
52 52 void readProperties(IAmdaResultParserHelper &helper, QTextStream &stream)
53 53 {
54 54 // Searches properties in the comment lines (as long as the reading has not reached the data)
55 55 // AMDA V2: while (stream.readLineInto(&line) && !line.contains(DATA_HEADER_REGEX)) {
56 56 QString line{};
57 57 while (stream.readLineInto(&line) && isCommentLine(line)) {
58 58 helper.readPropertyLine(line);
59 59 }
60 60 }
61 61
62 62 /**
63 63 * Reads results of the stream passed as parameter
64 64 * @param helper the helper used to read results line by line
65 65 * @param stream the stream to read
66 66 */
67 67 void readResults(IAmdaResultParserHelper &helper, QTextStream &stream)
68 68 {
69 69 QString line{};
70 70
71 71 // Skip comment lines
72 72 while (stream.readLineInto(&line) && isCommentLine(line)) {
73 73 }
74 74
75 75 if (!stream.atEnd()) {
76 76 do {
77 77 helper.readResultLine(line);
78 78 } while (stream.readLineInto(&line));
79 79 }
80 80 }
81 81
82 82 } // namespace
83 83
84 84 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath,
85 ValueType valueType) noexcept
85 DataSeriesType type) noexcept
86 86 {
87 if (valueType == ValueType::UNKNOWN) {
87 if (type == DataSeriesType::UNKNOWN) {
88 88 qCCritical(LOG_AmdaResultParser())
89 89 << QObject::tr("Can't retrieve AMDA data: the type of values to be read is unknown");
90 90 return nullptr;
91 91 }
92 92
93 93 QFile file{filePath};
94 94
95 95 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
96 96 qCCritical(LOG_AmdaResultParser())
97 97 << QObject::tr("Can't retrieve AMDA data from file %1: %2")
98 98 .arg(filePath, file.errorString());
99 99 return nullptr;
100 100 }
101 101
102 102 QTextStream stream{&file};
103 103
104 104 // Checks if the file was found on the server
105 105 auto firstLine = stream.readLine();
106 106 if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0) {
107 107 qCCritical(LOG_AmdaResultParser())
108 108 << QObject::tr("Can't retrieve AMDA data from file %1: file was not found on server")
109 109 .arg(filePath);
110 110 return nullptr;
111 111 }
112 112
113 auto helper = createHelper(valueType);
113 auto helper = createHelper(type);
114 114 Q_ASSERT(helper != nullptr);
115 115
116 116 // Reads header file to retrieve properties
117 117 stream.seek(0); // returns to the beginning of the file
118 118 readProperties(*helper, stream);
119 119
120 120 // Checks properties
121 121 if (helper->checkProperties()) {
122 122 // Reads results
123 123 // AMDA V2: remove line
124 124 stream.seek(0); // returns to the beginning of the file
125 125 readResults(*helper, stream);
126 126
127 127 // Creates data series
128 128 return helper->createSeries();
129 129 }
130 130 else {
131 131 return nullptr;
132 132 }
133 133 }
@@ -1,164 +1,164
1 1 #include "AmdaProvider.h"
2 2 #include "AmdaResultParser.h"
3 3
4 4 #include "SqpApplication.h"
5 5 #include <Data/DataSeries.h>
6 6 #include <Data/IDataSeries.h>
7 7 #include <Data/ScalarSeries.h>
8 8 #include <Time/TimeController.h>
9 9 #include <Variable/Variable.h>
10 10 #include <Variable/VariableController.h>
11 11
12 12 #include <QObject>
13 13 #include <QtTest>
14 14
15 15 #include <memory>
16 16
17 17 // TEST with REF:
18 18 // AmdaData-2012-01-01-12-00-00_2012-01-03-12-00-00
19 19 // imf(0) - Type : Local Parameter @ CDPP/AMDA -
20 20 // Name : bx_gse - Units : nT - Size : 1 -
21 21 // Frame : GSE - Mission : ACE -
22 22 // Instrument : MFI - Dataset : mfi_final-prelim
23 23 // REFERENCE DOWNLOAD FILE =
24 24 // http://amdatest.irap.omp.eu/php/rest/getParameter.php?startTime=2012-01-01T12:00:00&stopTime=2012-01-03T12:00:00&parameterID=imf(0)&outputFormat=ASCII&timeFormat=ISO8601&gzip=0
25 25
26 26 namespace {
27 27
28 28 /// Path for the tests
29 29 const auto TESTS_RESOURCES_PATH
30 30 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaAcquisition"}.absoluteFilePath();
31 31
32 32 /// Delay after each operation on the variable before validating it (in ms)
33 33 const auto OPERATION_DELAY = 10000;
34 34
35 35 template <typename T>
36 36 bool compareDataSeries(std::shared_ptr<IDataSeries> candidate, SqpRange candidateCacheRange,
37 37 std::shared_ptr<IDataSeries> reference)
38 38 {
39 39 auto compareLambda = [](const auto &it1, const auto &it2) {
40 40 return (it1.x() == it2.x()) && (it1.value() == it2.value());
41 41 };
42 42
43 43 auto candidateDS = std::dynamic_pointer_cast<T>(candidate);
44 44 auto referenceDS = std::dynamic_pointer_cast<T>(reference);
45 45
46 46 if (candidateDS && referenceDS) {
47 47
48 48 auto itRefs
49 49 = referenceDS->xAxisRange(candidateCacheRange.m_TStart, candidateCacheRange.m_TEnd);
50 50 qDebug() << " DISTANCE" << std::distance(candidateDS->cbegin(), candidateDS->cend())
51 51 << std::distance(itRefs.first, itRefs.second);
52 52
53 53 return std::equal(candidateDS->cbegin(), candidateDS->cend(), itRefs.first, itRefs.second,
54 54 compareLambda);
55 55 }
56 56 else {
57 57 return false;
58 58 }
59 59 }
60 60 }
61 61
62 62 class TestAmdaAcquisition : public QObject {
63 63 Q_OBJECT
64 64
65 65 private slots:
66 66 /// Input data for @sa testAcquisition()
67 67 void testAcquisition_data();
68 68 void testAcquisition();
69 69 };
70 70
71 71 void TestAmdaAcquisition::testAcquisition_data()
72 72 {
73 73 // ////////////// //
74 74 // Test structure //
75 75 // ////////////// //
76 76
77 77 QTest::addColumn<QString>("dataFilename"); // File containing expected data of acquisitions
78 78 QTest::addColumn<SqpRange>("initialRange"); // First acquisition
79 79 QTest::addColumn<std::vector<SqpRange> >("operations"); // Acquisitions to make
80 80
81 81 // ////////// //
82 82 // Test cases //
83 83 // ////////// //
84 84
85 85 auto dateTime = [](int year, int month, int day, int hours, int minutes, int seconds) {
86 86 return DateUtils::secondsSinceEpoch(
87 87 QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC});
88 88 };
89 89
90 90
91 91 QTest::newRow("amda")
92 92 << "AmdaData-2012-01-01-12-00-00_2012-01-03-12-00-00.txt"
93 93 << SqpRange{dateTime(2012, 1, 2, 2, 3, 0), dateTime(2012, 1, 2, 2, 4, 0)}
94 94 << std::vector<SqpRange>{
95 95 // 2 : pan (jump) left for two min
96 96 SqpRange{dateTime(2012, 1, 2, 2, 1, 0), dateTime(2012, 1, 2, 2, 2, 0)},
97 97 // 3 : pan (jump) right for four min
98 98 SqpRange{dateTime(2012, 1, 2, 2, 5, 0), dateTime(2012, 1, 2, 2, 6, 0)},
99 99 // 4 : pan (overlay) right for 30 sec
100 100 /*SqpRange{dateTime(2012, 1, 2, 2, 5, 30), dateTime(2012, 1, 2, 2, 6, 30)},
101 101 // 5 : pan (overlay) left for 30 sec
102 102 SqpRange{dateTime(2012, 1, 2, 2, 5, 0), dateTime(2012, 1, 2, 2, 6, 0)},
103 103 // 6 : pan (overlay) left for 30 sec - BIS
104 104 SqpRange{dateTime(2012, 1, 2, 2, 4, 30), dateTime(2012, 1, 2, 2, 5, 30)},
105 105 // 7 : Zoom in Inside 20 sec range
106 106 SqpRange{dateTime(2012, 1, 2, 2, 4, 50), dateTime(2012, 1, 2, 2, 5, 10)},
107 107 // 8 : Zoom out Inside 20 sec range
108 108 SqpRange{dateTime(2012, 1, 2, 2, 4, 30), dateTime(2012, 1, 2, 2, 5, 30)}*/};
109 109 }
110 110
111 111 void TestAmdaAcquisition::testAcquisition()
112 112 {
113 113 /// @todo: update test to be compatible with AMDA v2
114 114
115 115 // Retrieves data file
116 116 QFETCH(QString, dataFilename);
117 117 auto filePath = QFileInfo{TESTS_RESOURCES_PATH, dataFilename}.absoluteFilePath();
118 auto results = AmdaResultParser::readTxt(filePath, AmdaResultParser::ValueType::SCALAR);
118 auto results = AmdaResultParser::readTxt(filePath, DataSeriesType::SCALAR);
119 119
120 120 /// Lambda used to validate a variable at each step
121 121 auto validateVariable = [results](std::shared_ptr<Variable> variable, const SqpRange &range) {
122 122 // Checks that the variable's range has changed
123 123 qInfo() << tr("Compare var range vs range") << variable->range() << range;
124 124 QCOMPARE(variable->range(), range);
125 125
126 126 // Checks the variable's data series
127 127 QVERIFY(compareDataSeries<ScalarSeries>(variable->dataSeries(), variable->cacheRange(),
128 128 results));
129 129 qInfo() << "\n";
130 130 };
131 131
132 132 // Creates variable
133 133 QFETCH(SqpRange, initialRange);
134 134 sqpApp->timeController().onTimeToUpdate(initialRange);
135 135 auto provider = std::make_shared<AmdaProvider>();
136 136 auto variable = sqpApp->variableController().createVariable(
137 137 "bx_gse", {{"dataType", "scalar"}, {"xml:id", "imf(0)"}}, provider);
138 138
139 139 QTest::qWait(OPERATION_DELAY);
140 140 validateVariable(variable, initialRange);
141 141
142 142 // Makes operations on the variable
143 143 QFETCH(std::vector<SqpRange>, operations);
144 144 for (const auto &operation : operations) {
145 145 // Asks request on the variable and waits during its execution
146 146 sqpApp->variableController().onRequestDataLoading({variable}, operation, false);
147 147
148 148 QTest::qWait(OPERATION_DELAY);
149 149 validateVariable(variable, operation);
150 150 }
151 151 }
152 152
153 153 int main(int argc, char *argv[])
154 154 {
155 155 SqpApplication app(argc, argv);
156 156 app.setAttribute(Qt::AA_Use96Dpi, true);
157 157 TestAmdaAcquisition tc;
158 158 QTEST_SET_MAIN_SOURCE_PATH
159 159 return QTest::qExec(&tc, argc, argv);
160 160 }
161 161
162 162 // QTEST_MAIN(TestAmdaAcquisition)
163 163
164 164 #include "TestAmdaAcquisition.moc"
@@ -1,579 +1,579
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include <Data/ScalarSeries.h>
4 4 #include <Data/SpectrogramSeries.h>
5 5 #include <Data/VectorSeries.h>
6 6
7 7 #include <QObject>
8 8 #include <QtTest>
9 9
10 10 namespace {
11 11
12 12 /// Path for the tests
13 13 const auto TESTS_RESOURCES_PATH
14 14 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaResultParser"}.absoluteFilePath();
15 15
16 16 QDateTime dateTime(int year, int month, int day, int hours, int minutes, int seconds)
17 17 {
18 18 return QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC};
19 19 }
20 20
21 21 QString inputFilePath(const QString &inputFileName)
22 22 {
23 23 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
24 24 }
25 25
26 26 template <typename T>
27 27 struct ExpectedResults {
28 28
29 29 ExpectedResults &setParsingOK(bool parsingOK)
30 30 {
31 31 m_ParsingOK = parsingOK;
32 32 return *this;
33 33 }
34 34
35 35 ExpectedResults &setXAxisUnit(Unit xAxisUnit)
36 36 {
37 37 m_XAxisUnit = std::move(xAxisUnit);
38 38 return *this;
39 39 }
40 40
41 41 ExpectedResults &setXAxisData(const QVector<QDateTime> &xAxisData)
42 42 {
43 43 m_XAxisData.clear();
44 44
45 45 // Converts QVector<QDateTime> to QVector<double>
46 46 std::transform(xAxisData.cbegin(), xAxisData.cend(), std::back_inserter(m_XAxisData),
47 47 [](const auto &dateTime) { return dateTime.toMSecsSinceEpoch() / 1000.; });
48 48
49 49 return *this;
50 50 }
51 51
52 52 ExpectedResults &setValuesUnit(Unit valuesUnit)
53 53 {
54 54 m_ValuesUnit = std::move(valuesUnit);
55 55 return *this;
56 56 }
57 57
58 58 ExpectedResults &setValuesData(QVector<double> valuesData)
59 59 {
60 60 m_ValuesData.clear();
61 61 m_ValuesData.push_back(std::move(valuesData));
62 62 return *this;
63 63 }
64 64
65 65 ExpectedResults &setValuesData(QVector<QVector<double> > valuesData)
66 66 {
67 67 m_ValuesData = std::move(valuesData);
68 68 return *this;
69 69 }
70 70
71 71 ExpectedResults &setYAxisEnabled(bool yAxisEnabled)
72 72 {
73 73 m_YAxisEnabled = yAxisEnabled;
74 74 return *this;
75 75 }
76 76
77 77 ExpectedResults &setYAxisUnit(Unit yAxisUnit)
78 78 {
79 79 m_YAxisUnit = std::move(yAxisUnit);
80 80 return *this;
81 81 }
82 82
83 83 ExpectedResults &setYAxisData(QVector<double> yAxisData)
84 84 {
85 85 m_YAxisData = std::move(yAxisData);
86 86 return *this;
87 87 }
88 88
89 89 /**
90 90 * Validates a DataSeries compared to the expected results
91 91 * @param results the DataSeries to validate
92 92 */
93 93 void validate(std::shared_ptr<IDataSeries> results)
94 94 {
95 95 if (m_ParsingOK) {
96 96 auto dataSeries = dynamic_cast<T *>(results.get());
97 97 if (dataSeries == nullptr) {
98 98
99 99 // No unit detected, parsink ok but data is nullptr
100 100 // TODO, improve the test to verify that the data is null
101 101 return;
102 102 }
103 103
104 104 // Checks units
105 105 QVERIFY(dataSeries->xAxisUnit() == m_XAxisUnit);
106 106 QVERIFY(dataSeries->valuesUnit() == m_ValuesUnit);
107 107
108 108 auto verifyRange = [dataSeries](const auto &expectedData, const auto &equalFun) {
109 109 QVERIFY(std::equal(dataSeries->cbegin(), dataSeries->cend(), expectedData.cbegin(),
110 110 expectedData.cend(),
111 111 [&equalFun](const auto &dataSeriesIt, const auto &expectedX) {
112 112 return equalFun(dataSeriesIt, expectedX);
113 113 }));
114 114 };
115 115
116 116 // Checks x-axis data
117 117 verifyRange(m_XAxisData, [](const auto &seriesIt, const auto &value) {
118 118 return seriesIt.x() == value;
119 119 });
120 120
121 121 // Checks values data of each component
122 122 for (auto i = 0; i < m_ValuesData.size(); ++i) {
123 123 verifyRange(m_ValuesData.at(i), [i](const auto &seriesIt, const auto &value) {
124 124 auto itValue = seriesIt.value(i);
125 125 return (std::isnan(itValue) && std::isnan(value)) || seriesIt.value(i) == value;
126 126 });
127 127 }
128 128
129 129 // Checks y-axis (if defined)
130 130 auto yAxis = dataSeries->yAxis();
131 131 QCOMPARE(yAxis.isDefined(), m_YAxisEnabled);
132 132
133 133 if (m_YAxisEnabled) {
134 134 // Unit
135 135 QCOMPARE(yAxis.unit(), m_YAxisUnit);
136 136
137 137 // Data
138 138 QVERIFY(std::equal(yAxis.cbegin(), yAxis.cend(), m_YAxisData.cbegin(),
139 139 m_YAxisData.cend(), [](const auto &it, const auto &expectedVal) {
140 140 return it.first() == expectedVal;
141 141 }));
142 142 }
143 143 }
144 144 else {
145 145 QVERIFY(results == nullptr);
146 146 }
147 147 }
148 148
149 149 // Parsing was successfully completed
150 150 bool m_ParsingOK{false};
151 151 // Expected x-axis unit
152 152 Unit m_XAxisUnit{};
153 153 // Expected x-axis data
154 154 QVector<double> m_XAxisData{};
155 155 // Expected values unit
156 156 Unit m_ValuesUnit{};
157 157 // Expected values data
158 158 QVector<QVector<double> > m_ValuesData{};
159 159 // Expected data series has y-axis
160 160 bool m_YAxisEnabled{false};
161 161 // Expected y-axis unit (if axis defined)
162 162 Unit m_YAxisUnit{};
163 163 // Expected y-axis data (if axis defined)
164 164 QVector<double> m_YAxisData{};
165 165 };
166 166
167 167 } // namespace
168 168
169 169 Q_DECLARE_METATYPE(ExpectedResults<ScalarSeries>)
170 170 Q_DECLARE_METATYPE(ExpectedResults<SpectrogramSeries>)
171 171 Q_DECLARE_METATYPE(ExpectedResults<VectorSeries>)
172 172
173 173 class TestAmdaResultParser : public QObject {
174 174 Q_OBJECT
175 175 private:
176 176 template <typename T>
177 177 void testReadDataStructure()
178 178 {
179 179 // ////////////// //
180 180 // Test structure //
181 181 // ////////////// //
182 182
183 183 // Name of TXT file to read
184 184 QTest::addColumn<QString>("inputFileName");
185 185 // Expected results
186 186 QTest::addColumn<ExpectedResults<T> >("expectedResults");
187 187 }
188 188
189 189 template <typename T>
190 void testRead(AmdaResultParser::ValueType valueType)
190 void testRead(DataSeriesType valueType)
191 191 {
192 192 QFETCH(QString, inputFileName);
193 193 QFETCH(ExpectedResults<T>, expectedResults);
194 194
195 195 // Parses file
196 196 auto filePath = inputFilePath(inputFileName);
197 197 auto results = AmdaResultParser::readTxt(filePath, valueType);
198 198
199 199 // ///////////////// //
200 200 // Validates results //
201 201 // ///////////////// //
202 202 expectedResults.validate(results);
203 203 }
204 204
205 205 private slots:
206 206 /// Input test data
207 207 /// @sa testReadScalarTxt()
208 208 void testReadScalarTxt_data();
209 209
210 210 /// Tests parsing scalar series of a TXT file
211 211 void testReadScalarTxt();
212 212
213 213 /// Input test data
214 214 /// @sa testReadSpectrogramTxt()
215 215 void testReadSpectrogramTxt_data();
216 216
217 217 /// Tests parsing spectrogram series of a TXT file
218 218 void testReadSpectrogramTxt();
219 219
220 220 /// Input test data
221 221 /// @sa testReadVectorTxt()
222 222 void testReadVectorTxt_data();
223 223
224 224 /// Tests parsing vector series of a TXT file
225 225 void testReadVectorTxt();
226 226 };
227 227
228 228 void TestAmdaResultParser::testReadScalarTxt_data()
229 229 {
230 230 testReadDataStructure<ScalarSeries>();
231 231
232 232 // ////////// //
233 233 // Test cases //
234 234 // ////////// //
235 235
236 236 // Valid files
237 237 QTest::newRow("Valid file")
238 238 << QStringLiteral("ValidScalar1.txt")
239 239 << ExpectedResults<ScalarSeries>{}
240 240 .setParsingOK(true)
241 241 .setXAxisUnit(Unit{"nT", true})
242 242 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
243 243 dateTime(2013, 9, 23, 9, 2, 30), dateTime(2013, 9, 23, 9, 3, 30),
244 244 dateTime(2013, 9, 23, 9, 4, 30), dateTime(2013, 9, 23, 9, 5, 30),
245 245 dateTime(2013, 9, 23, 9, 6, 30), dateTime(2013, 9, 23, 9, 7, 30),
246 246 dateTime(2013, 9, 23, 9, 8, 30), dateTime(2013, 9, 23, 9, 9, 30)})
247 247 .setValuesData({-2.83950, -2.71850, -2.52150, -2.57633, -2.58050, -2.48325, -2.63025,
248 248 -2.55800, -2.43250, -2.42200});
249 249
250 250 QTest::newRow("Valid file (value of first line is invalid but it is converted to NaN")
251 251 << QStringLiteral("WrongValue.txt")
252 252 << ExpectedResults<ScalarSeries>{}
253 253 .setParsingOK(true)
254 254 .setXAxisUnit(Unit{"nT", true})
255 255 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
256 256 dateTime(2013, 9, 23, 9, 2, 30)})
257 257 .setValuesData({std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150});
258 258
259 259 QTest::newRow("Valid file that contains NaN values")
260 260 << QStringLiteral("NaNValue.txt")
261 261 << ExpectedResults<ScalarSeries>{}
262 262 .setParsingOK(true)
263 263 .setXAxisUnit(Unit{("nT"), true})
264 264 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
265 265 dateTime(2013, 9, 23, 9, 2, 30)})
266 266 .setValuesData({std::numeric_limits<double>::quiet_NaN(), -2.71850, -2.52150});
267 267
268 268 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
269 269 QTest::newRow("No unit file")
270 270 << QStringLiteral("NoUnit.txt")
271 271 << ExpectedResults<ScalarSeries>{}.setParsingOK(true).setXAxisUnit(Unit{"", true});
272 272
273 273 QTest::newRow("Wrong unit file")
274 274 << QStringLiteral("WrongUnit.txt")
275 275 << ExpectedResults<ScalarSeries>{}
276 276 .setParsingOK(true)
277 277 .setXAxisUnit(Unit{"", true})
278 278 .setXAxisData({dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
279 279 dateTime(2013, 9, 23, 9, 2, 30)})
280 280 .setValuesData({-2.83950, -2.71850, -2.52150});
281 281
282 282 QTest::newRow("Wrong results file (date of first line is invalid")
283 283 << QStringLiteral("WrongDate.txt")
284 284 << ExpectedResults<ScalarSeries>{}
285 285 .setParsingOK(true)
286 286 .setXAxisUnit(Unit{"nT", true})
287 287 .setXAxisData({dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)})
288 288 .setValuesData({-2.71850, -2.52150});
289 289
290 290 QTest::newRow("Wrong results file (too many values for first line")
291 291 << QStringLiteral("TooManyValues.txt")
292 292 << ExpectedResults<ScalarSeries>{}
293 293 .setParsingOK(true)
294 294 .setXAxisUnit(Unit{"nT", true})
295 295 .setXAxisData({dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)})
296 296 .setValuesData({-2.71850, -2.52150});
297 297
298 298 QTest::newRow("Wrong results file (x of first line is NaN")
299 299 << QStringLiteral("NaNX.txt")
300 300 << ExpectedResults<ScalarSeries>{}
301 301 .setParsingOK(true)
302 302 .setXAxisUnit(Unit{"nT", true})
303 303 .setXAxisData({dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)})
304 304 .setValuesData({-2.71850, -2.52150});
305 305
306 306 QTest::newRow("Invalid file type (vector)")
307 307 << QStringLiteral("ValidVector1.txt")
308 308 << ExpectedResults<ScalarSeries>{}.setParsingOK(true).setXAxisUnit(Unit{"nT", true});
309 309
310 310 // Invalid files
311 311 QTest::newRow("Invalid file (unexisting file)")
312 312 << QStringLiteral("UnexistingFile.txt")
313 313 << ExpectedResults<ScalarSeries>{}.setParsingOK(false);
314 314
315 315 QTest::newRow("Invalid file (file not found on server)")
316 316 << QStringLiteral("FileNotFound.txt")
317 317 << ExpectedResults<ScalarSeries>{}.setParsingOK(false);
318 318 }
319 319
320 320 void TestAmdaResultParser::testReadScalarTxt()
321 321 {
322 testRead<ScalarSeries>(AmdaResultParser::ValueType::SCALAR);
322 testRead<ScalarSeries>(DataSeriesType::SCALAR);
323 323 }
324 324
325 325 void TestAmdaResultParser::testReadSpectrogramTxt_data()
326 326 {
327 327 testReadDataStructure<SpectrogramSeries>();
328 328
329 329 // ////////// //
330 330 // Test cases //
331 331 // ////////// //
332 332
333 333 // Valid files
334 334 QTest::newRow("Valid file (three bands)")
335 335 << QStringLiteral("spectro/ValidSpectrogram1.txt")
336 336 << ExpectedResults<SpectrogramSeries>{}
337 337 .setParsingOK(true)
338 338 .setXAxisUnit(Unit{"t", true})
339 339 .setXAxisData({dateTime(2012, 11, 6, 9, 14, 35), dateTime(2012, 11, 6, 9, 16, 10),
340 340 dateTime(2012, 11, 6, 9, 17, 45), dateTime(2012, 11, 6, 9, 19, 20),
341 341 dateTime(2012, 11, 6, 9, 20, 55)})
342 342 .setYAxisEnabled(true)
343 343 .setYAxisUnit(Unit{"eV"})
344 344 .setYAxisData({5.75, 7.6, 10.05}) // middle of the intervals of each band
345 345 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
346 346 .setValuesData(QVector<QVector<double> >{
347 347 {16313.780, 12631.465, 8223.368, 27595.301, 12820.613},
348 348 {15405.838, 11957.925, 15026.249, 25617.533, 11179.109},
349 349 {8946.475, 18133.158, 10875.621, 24051.619, 19283.221}});
350 350
351 351 auto fourBandsResult
352 352 = ExpectedResults<SpectrogramSeries>{}
353 353 .setParsingOK(true)
354 354 .setXAxisUnit(Unit{"t", true})
355 355 .setXAxisData({dateTime(2012, 11, 6, 9, 14, 35), dateTime(2012, 11, 6, 9, 16, 10),
356 356 dateTime(2012, 11, 6, 9, 17, 45), dateTime(2012, 11, 6, 9, 19, 20),
357 357 dateTime(2012, 11, 6, 9, 20, 55)})
358 358 .setYAxisEnabled(true)
359 359 .setYAxisUnit(Unit{"eV"})
360 360 .setYAxisData({5.75, 7.6, 10.05, 13.}) // middle of the intervals of each band
361 361 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
362 362 .setValuesData(QVector<QVector<double> >{
363 363 {16313.780, 12631.465, 8223.368, 27595.301, 12820.613},
364 364 {15405.838, 11957.925, 15026.249, 25617.533, 11179.109},
365 365 {8946.475, 18133.158, 10875.621, 24051.619, 19283.221},
366 366 {20907.664, 32076.725, 13008.381, 13142.759, 23226.998}});
367 367
368 368 QTest::newRow("Valid file (four bands)") << QStringLiteral("spectro/ValidSpectrogram2.txt")
369 369 << fourBandsResult;
370 370 QTest::newRow("Valid file (four unsorted bands)")
371 371 << QStringLiteral("spectro/ValidSpectrogram3.txt")
372 372 << fourBandsResult; // Bands and values are sorted
373 373
374 374 auto nan = std::numeric_limits<double>::quiet_NaN();
375 375
376 376 auto nanValuesResult
377 377 = ExpectedResults<SpectrogramSeries>{}
378 378 .setParsingOK(true)
379 379 .setXAxisUnit(Unit{"t", true})
380 380 .setXAxisData({dateTime(2012, 11, 6, 9, 14, 35), dateTime(2012, 11, 6, 9, 16, 10),
381 381 dateTime(2012, 11, 6, 9, 17, 45), dateTime(2012, 11, 6, 9, 19, 20),
382 382 dateTime(2012, 11, 6, 9, 20, 55)})
383 383 .setYAxisEnabled(true)
384 384 .setYAxisUnit(Unit{"eV"})
385 385 .setYAxisData({5.75, 7.6, 10.05, 13.}) // middle of the intervals of each band
386 386 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
387 387 .setValuesData(
388 388 QVector<QVector<double> >{{nan, 12631.465, 8223.368, 27595.301, 12820.613},
389 389 {15405.838, nan, nan, 25617.533, 11179.109},
390 390 {8946.475, 18133.158, 10875.621, 24051.619, 19283.221},
391 391 {nan, nan, nan, nan, nan}});
392 392
393 393 QTest::newRow("Valid file (containing NaN values)")
394 394 << QStringLiteral("spectro/ValidSpectrogramNaNValues.txt") << nanValuesResult;
395 395 QTest::newRow("Valid file (containing fill values)")
396 396 << QStringLiteral("spectro/ValidSpectrogramFillValues.txt")
397 397 << nanValuesResult; // Fill values are replaced by NaN values in the data series
398 398
399 399 QTest::newRow("Valid file (containing data holes, resolution = 3 minutes)")
400 400 << QStringLiteral("spectro/ValidSpectrogramDataHoles.txt")
401 401 << ExpectedResults<SpectrogramSeries>{}
402 402 .setParsingOK(true)
403 403 .setXAxisUnit(Unit{"t", true})
404 404 .setXAxisData({dateTime(2011, 12, 10, 12, 10, 54), //
405 405 dateTime(2011, 12, 10, 12, 13, 54), // Data hole
406 406 dateTime(2011, 12, 10, 12, 16, 54), // Data hole
407 407 dateTime(2011, 12, 10, 12, 17, 23), //
408 408 dateTime(2011, 12, 10, 12, 20, 23), // Data hole
409 409 dateTime(2011, 12, 10, 12, 23, 23), // Data hole
410 410 dateTime(2011, 12, 10, 12, 23, 51), //
411 411 dateTime(2011, 12, 10, 12, 26, 51), // Data hole
412 412 dateTime(2011, 12, 10, 12, 29, 51), // Data hole
413 413 dateTime(2011, 12, 10, 12, 30, 19), //
414 414 dateTime(2011, 12, 10, 12, 33, 19), // Data hole
415 415 dateTime(2011, 12, 10, 12, 35, 04), //
416 416 dateTime(2011, 12, 10, 12, 36, 41), //
417 417 dateTime(2011, 12, 10, 12, 38, 18), //
418 418 dateTime(2011, 12, 10, 12, 39, 55)})
419 419 .setYAxisEnabled(true)
420 420 .setYAxisUnit(Unit{"eV"})
421 421 .setYAxisData({16485.85, 20996.1}) // middle of the intervals of each band
422 422 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
423 423 .setValuesData(QVector<QVector<double> >{{2577578.000, //
424 424 nan, // Data hole
425 425 nan, // Data hole
426 426 2314121.500, //
427 427 nan, // Data hole
428 428 nan, // Data hole
429 429 2063608.750, //
430 430 nan, // Data hole
431 431 nan, // Data hole
432 432 2234525.500, //
433 433 nan, // Data hole
434 434 1670215.250, //
435 435 1689243.250, //
436 436 1654617.125, //
437 437 1504983.750},
438 438 {2336016.000, //
439 439 nan, // Data hole
440 440 nan, // Data hole
441 441 1712093.125, //
442 442 nan, // Data hole
443 443 nan, // Data hole
444 444 1614491.625, //
445 445 nan, // Data hole
446 446 nan, // Data hole
447 447 1764516.500, //
448 448 nan, // Data hole
449 449 1688078.500, //
450 450 1743183.500, //
451 451 1733603.250, //
452 452 1708356.500}});
453 453
454 454 QTest::newRow(
455 455 "Valid file (containing data holes at the beginning and the end, resolution = 4 minutes)")
456 456 << QStringLiteral("spectro/ValidSpectrogramDataHoles2.txt")
457 457 << ExpectedResults<SpectrogramSeries>{}
458 458 .setParsingOK(true)
459 459 .setXAxisUnit(Unit{"t", true})
460 460 .setXAxisData({
461 461 dateTime(2011, 12, 10, 12, 2, 54), // Data hole
462 462 dateTime(2011, 12, 10, 12, 6, 54), // Data hole
463 463 dateTime(2011, 12, 10, 12, 10, 54), //
464 464 dateTime(2011, 12, 10, 12, 14, 54), // Data hole
465 465 dateTime(2011, 12, 10, 12, 17, 23), //
466 466 dateTime(2011, 12, 10, 12, 21, 23), // Data hole
467 467 dateTime(2011, 12, 10, 12, 23, 51), //
468 468 dateTime(2011, 12, 10, 12, 27, 51), // Data hole
469 469 dateTime(2011, 12, 10, 12, 30, 19), //
470 470 dateTime(2011, 12, 10, 12, 34, 19), // Data hole
471 471 dateTime(2011, 12, 10, 12, 35, 04), //
472 472 dateTime(2011, 12, 10, 12, 36, 41), //
473 473 dateTime(2011, 12, 10, 12, 38, 18), //
474 474 dateTime(2011, 12, 10, 12, 39, 55),
475 475 dateTime(2011, 12, 10, 12, 43, 55), // Data hole
476 476 dateTime(2011, 12, 10, 12, 47, 55), // Data hole
477 477 dateTime(2011, 12, 10, 12, 51, 55), // Data hole
478 478 dateTime(2011, 12, 10, 12, 55, 55), // Data hole
479 479 dateTime(2011, 12, 10, 12, 59, 55) // Data hole
480 480 })
481 481 .setYAxisEnabled(true)
482 482 .setYAxisUnit(Unit{"eV"})
483 483 .setYAxisData({16485.85, 20996.1}) // middle of the intervals of each band
484 484 .setValuesUnit(Unit{"eV/(cm^2-s-sr-eV)"})
485 485 .setValuesData(QVector<QVector<double> >{{
486 486 nan, // Data hole
487 487 nan, // Data hole
488 488 2577578.000, //
489 489 nan, // Data hole
490 490 2314121.500, //
491 491 nan, // Data hole
492 492 2063608.750, //
493 493 nan, // Data hole
494 494 2234525.500, //
495 495 nan, // Data hole
496 496 1670215.250, //
497 497 1689243.250, //
498 498 1654617.125, //
499 499 1504983.750, //
500 500 nan, // Data hole
501 501 nan, // Data hole
502 502 nan, // Data hole
503 503 nan, // Data hole
504 504 nan // Data hole
505 505 },
506 506 {
507 507 nan, // Data hole
508 508 nan, // Data hole
509 509 2336016.000, //
510 510 nan, // Data hole
511 511 1712093.125, //
512 512 nan, // Data hole
513 513 1614491.625, //
514 514 nan, // Data hole
515 515 1764516.500, //
516 516 nan, // Data hole
517 517 1688078.500, //
518 518 1743183.500, //
519 519 1733603.250, //
520 520 1708356.500, //
521 521 nan, // Data hole
522 522 nan, // Data hole
523 523 nan, // Data hole
524 524 nan, // Data hole
525 525 nan // Data hole
526 526 }});
527 527
528 528 // Invalid files
529 529 QTest::newRow("Invalid file (inconsistent bands)")
530 530 << QStringLiteral("spectro/InvalidSpectrogramWrongBands.txt")
531 531 << ExpectedResults<SpectrogramSeries>{}.setParsingOK(false);
532 532 }
533 533
534 534 void TestAmdaResultParser::testReadSpectrogramTxt()
535 535 {
536 testRead<SpectrogramSeries>(AmdaResultParser::ValueType::SPECTROGRAM);
536 testRead<SpectrogramSeries>(DataSeriesType::SPECTROGRAM);
537 537 }
538 538
539 539 void TestAmdaResultParser::testReadVectorTxt_data()
540 540 {
541 541 testReadDataStructure<VectorSeries>();
542 542
543 543 // ////////// //
544 544 // Test cases //
545 545 // ////////// //
546 546
547 547 // Valid files
548 548 QTest::newRow("Valid file")
549 549 << QStringLiteral("ValidVector1.txt")
550 550 << ExpectedResults<VectorSeries>{}
551 551 .setParsingOK(true)
552 552 .setXAxisUnit(Unit{"nT", true})
553 553 .setXAxisData({dateTime(2013, 7, 2, 9, 13, 50), dateTime(2013, 7, 2, 9, 14, 6),
554 554 dateTime(2013, 7, 2, 9, 14, 22), dateTime(2013, 7, 2, 9, 14, 38),
555 555 dateTime(2013, 7, 2, 9, 14, 54), dateTime(2013, 7, 2, 9, 15, 10),
556 556 dateTime(2013, 7, 2, 9, 15, 26), dateTime(2013, 7, 2, 9, 15, 42),
557 557 dateTime(2013, 7, 2, 9, 15, 58), dateTime(2013, 7, 2, 9, 16, 14)})
558 558 .setValuesData(
559 559 {{-0.332, -1.011, -1.457, -1.293, -1.217, -1.443, -1.278, -1.202, -1.22, -1.259},
560 560 {3.206, 2.999, 2.785, 2.736, 2.612, 2.564, 2.892, 2.862, 2.859, 2.764},
561 561 {0.058, 0.496, 1.018, 1.485, 1.662, 1.505, 1.168, 1.244, 1.15, 1.358}});
562 562
563 563 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
564 564 QTest::newRow("Invalid file type (scalar)")
565 565 << QStringLiteral("ValidScalar1.txt")
566 566 << ExpectedResults<VectorSeries>{}
567 567 .setParsingOK(true)
568 568 .setXAxisUnit(Unit{"nT", true})
569 569 .setXAxisData({})
570 570 .setValuesData(QVector<QVector<double> >{{}, {}, {}});
571 571 }
572 572
573 573 void TestAmdaResultParser::testReadVectorTxt()
574 574 {
575 testRead<VectorSeries>(AmdaResultParser::ValueType::VECTOR);
575 testRead<VectorSeries>(DataSeriesType::VECTOR);
576 576 }
577 577
578 578 QTEST_MAIN(TestAmdaResultParser)
579 579 #include "TestAmdaResultParser.moc"
General Comments 0
You need to be logged in to leave comments. Login now