##// END OF EJS Templates
Merge pull request 196 from SCIQLOP-Initialisation develop...
perrinel -
r495:f81e8612a847 merge
parent child
Show More
@@ -0,0 +1,19
1 #ifndef SCIQLOP_DATEUTILS_H
2 #define SCIQLOP_DATEUTILS_H
3
4 #include "CoreGlobal.h"
5
6 #include <QDateTime>
7
8 /**
9 * Utility class with methods for dates
10 */
11 struct SCIQLOP_CORE_EXPORT DateUtils {
12 /// Converts seconds (since epoch) to datetime. By default, the datetime is in UTC
13 static QDateTime dateTime(double secs, Qt::TimeSpec timeSpec = Qt::UTC) noexcept;
14
15 /// Converts datetime to seconds since epoch
16 static double secondsSinceEpoch(const QDateTime &dateTime) noexcept;
17 };
18
19 #endif // SCIQLOP_DATEUTILS_H
@@ -0,0 +1,13
1 #include "Common/DateUtils.h"
2
3 QDateTime DateUtils::dateTime(double secs, Qt::TimeSpec timeSpec) noexcept
4 {
5 // Uses msecs to be Qt 4 compatible
6 return QDateTime::fromMSecsSinceEpoch(secs * 1000., timeSpec);
7 }
8
9 double DateUtils::secondsSinceEpoch(const QDateTime &dateTime) noexcept
10 {
11 // Uses msecs to be Qt 4 compatible
12 return dateTime.toMSecsSinceEpoch() / 1000.;
13 }
@@ -0,0 +1,20
1 #ifndef SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
2 #define SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
3
4 #include <Common/spimpl.h>
5
6 class QCustomPlot;
7 class QMouseEvent;
8
9 class VisualizationGraphRenderingDelegate {
10 public:
11 explicit VisualizationGraphRenderingDelegate(QCustomPlot &plot);
12
13 void onMouseMove(QMouseEvent *event) noexcept;
14
15 private:
16 class VisualizationGraphRenderingDelegatePrivate;
17 spimpl::unique_impl_ptr<VisualizationGraphRenderingDelegatePrivate> impl;
18 };
19
20 #endif // SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
@@ -0,0 +1,123
1 #include "Visualization/VisualizationGraphRenderingDelegate.h"
2 #include "Visualization/qcustomplot.h"
3
4 #include <Common/DateUtils.h>
5
6 namespace {
7
8 const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss:zzz");
9
10 const auto TEXT_TRACER_FORMAT = QStringLiteral("key: %1\nvalue: %2");
11
12 /// Timeout after which a tracer is displayed
13 const auto TRACER_TIMEOUT = 500;
14
15 /// Formats a data value according to the axis on which it is present
16 QString formatValue(double value, const QCPAxis &axis)
17 {
18 // If the axis is a time axis, formats the value as a date
19 if (auto axisTicker = qSharedPointerDynamicCast<QCPAxisTickerDateTime>(axis.ticker())) {
20 return DateUtils::dateTime(value, axisTicker->dateTimeSpec()).toString(DATETIME_FORMAT);
21 }
22 else {
23 return QString::number(value);
24 }
25 }
26
27 void initPointTracerStyle(QCPItemTracer &tracer) noexcept
28 {
29 tracer.setInterpolating(false);
30 tracer.setStyle(QCPItemTracer::tsPlus);
31 tracer.setPen(QPen(Qt::black));
32 tracer.setBrush(Qt::black);
33 tracer.setSize(10);
34 }
35
36 void initTextTracerStyle(QCPItemText &tracer) noexcept
37 {
38 tracer.setPen(QPen{Qt::gray});
39 tracer.setBrush(Qt::white);
40 tracer.setPadding(QMargins{6, 6, 6, 6});
41 tracer.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
42 tracer.setTextAlignment(Qt::AlignLeft);
43 }
44
45 } // namespace
46
47 struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate {
48 explicit VisualizationGraphRenderingDelegatePrivate(QCustomPlot &plot)
49 : m_Plot{plot},
50 m_PointTracer{new QCPItemTracer{&plot}},
51 m_TextTracer{new QCPItemText{&plot}},
52 m_TracerTimer{}
53 {
54 initPointTracerStyle(*m_PointTracer);
55 initTextTracerStyle(*m_TextTracer);
56
57 m_TracerTimer.setInterval(TRACER_TIMEOUT);
58 m_TracerTimer.setSingleShot(true);
59 }
60
61 QCustomPlot &m_Plot;
62 QCPItemTracer *m_PointTracer;
63 QCPItemText *m_TextTracer;
64 QTimer m_TracerTimer;
65 };
66
67 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(QCustomPlot &plot)
68 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(plot)}
69 {
70 }
71
72 void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept
73 {
74 // Cancels pending refresh
75 impl->m_TracerTimer.disconnect();
76
77 auto showTracers = [ eventPos = event->pos(), this ]()
78 {
79 // Lambda function to display a tracer
80 auto displayTracer = [this](auto &tracer) {
81 // Tracer is set on top of the plot's main layer
82 tracer.setLayer(impl->m_Plot.layer("main"));
83 tracer.setVisible(true);
84 impl->m_Plot.replot();
85 };
86
87 // Reinits tracers
88 impl->m_PointTracer->setGraph(nullptr);
89 impl->m_PointTracer->setVisible(false);
90 impl->m_TextTracer->setVisible(false);
91 impl->m_Plot.replot();
92
93 // Gets the graph under the mouse position
94 if (auto graph = qobject_cast<QCPGraph *>(impl->m_Plot.plottableAt(eventPos))) {
95 auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x());
96 auto graphData = graph->data();
97
98 // Gets the closest data point to the mouse
99 auto graphDataIt = graphData->findBegin(mouseKey);
100 if (graphDataIt != graphData->constEnd()) {
101 auto key = formatValue(graphDataIt->key, *graph->keyAxis());
102 auto value = formatValue(graphDataIt->value, *graph->valueAxis());
103 impl->m_TextTracer->setText(TEXT_TRACER_FORMAT.arg(key, value));
104
105 // Displays point tracer
106 impl->m_PointTracer->setGraph(graph);
107 impl->m_PointTracer->setGraphKey(graphDataIt->key);
108 displayTracer(*impl->m_PointTracer);
109
110 // Displays text tracer
111 auto tracerPosition = impl->m_TextTracer->position;
112 tracerPosition->setAxes(graph->keyAxis(), graph->valueAxis());
113 tracerPosition->setCoords(impl->m_PointTracer->position->key(),
114 impl->m_PointTracer->position->value());
115 displayTracer(*impl->m_TextTracer);
116 }
117 }
118 };
119
120 // Starts the timer to display tracers at timeout
121 QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTracers);
122 impl->m_TracerTimer.start();
123 }
@@ -1,255 +1,254
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 <DataSource/DataSourceController.h>
26 26 #include <DataSource/DataSourceWidget.h>
27 27 #include <Settings/SqpSettingsDialog.h>
28 28 #include <Settings/SqpSettingsGeneralWidget.h>
29 29 #include <SidePane/SqpSidePane.h>
30 30 #include <SqpApplication.h>
31 31 #include <Time/TimeController.h>
32 32 #include <TimeWidget/TimeWidget.h>
33 33 #include <Variable/Variable.h>
34 34 #include <Variable/VariableController.h>
35 35 #include <Visualization/VisualizationController.h>
36 36
37 37 #include <QAction>
38 38 #include <QDate>
39 #include <QDateTime>
40 39 #include <QDir>
41 40 #include <QFileDialog>
42 41 #include <QToolBar>
43 42 #include <QToolButton>
44 43 #include <memory.h>
45 44
46 45 #include "iostream"
47 46
48 47 Q_LOGGING_CATEGORY(LOG_MainWindow, "MainWindow")
49 48
50 49 namespace {
51 50 const auto LEFTMAININSPECTORWIDGETSPLITTERINDEX = 0;
52 51 const auto LEFTINSPECTORSIDEPANESPLITTERINDEX = 1;
53 52 const auto VIEWPLITTERINDEX = 2;
54 53 const auto RIGHTINSPECTORSIDEPANESPLITTERINDEX = 3;
55 54 const auto RIGHTMAININSPECTORWIDGETSPLITTERINDEX = 4;
56 55 }
57 56
58 57 class MainWindow::MainWindowPrivate {
59 58 public:
60 59 explicit MainWindowPrivate(MainWindow *mainWindow)
61 60 : m_LastOpenLeftInspectorSize{},
62 61 m_LastOpenRightInspectorSize{},
63 62 m_GeneralSettingsWidget{new SqpSettingsGeneralWidget{mainWindow}},
64 63 m_SettingsDialog{new SqpSettingsDialog{mainWindow}}
65 64 {
66 65 }
67 66
68 67 QSize m_LastOpenLeftInspectorSize;
69 68 QSize m_LastOpenRightInspectorSize;
70 69 /// General settings widget. MainWindow has the ownership
71 70 SqpSettingsGeneralWidget *m_GeneralSettingsWidget;
72 71 /// Settings dialog. MainWindow has the ownership
73 72 SqpSettingsDialog *m_SettingsDialog;
74 73 };
75 74
76 75 MainWindow::MainWindow(QWidget *parent)
77 76 : QMainWindow{parent},
78 77 m_Ui{new Ui::MainWindow},
79 78 impl{spimpl::make_unique_impl<MainWindowPrivate>(this)}
80 79 {
81 80 m_Ui->setupUi(this);
82 81
83 82 m_Ui->splitter->setCollapsible(LEFTINSPECTORSIDEPANESPLITTERINDEX, false);
84 83 m_Ui->splitter->setCollapsible(RIGHTINSPECTORSIDEPANESPLITTERINDEX, false);
85 84
86 85
87 86 auto leftSidePane = m_Ui->leftInspectorSidePane->sidePane();
88 87 auto openLeftInspectorAction = new QAction{QIcon{
89 88 ":/icones/previous.png",
90 89 },
91 90 tr("Show/hide the left inspector"), this};
92 91
93 92
94 93 auto spacerLeftTop = new QWidget{};
95 94 spacerLeftTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
96 95
97 96 auto spacerLeftBottom = new QWidget{};
98 97 spacerLeftBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
99 98
100 99 leftSidePane->addWidget(spacerLeftTop);
101 100 leftSidePane->addAction(openLeftInspectorAction);
102 101 leftSidePane->addWidget(spacerLeftBottom);
103 102
104 103
105 104 auto rightSidePane = m_Ui->rightInspectorSidePane->sidePane();
106 105 auto openRightInspectorAction = new QAction{QIcon{
107 106 ":/icones/next.png",
108 107 },
109 108 tr("Show/hide the right inspector"), this};
110 109
111 110 auto spacerRightTop = new QWidget{};
112 111 spacerRightTop->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
113 112
114 113 auto spacerRightBottom = new QWidget{};
115 114 spacerRightBottom->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
116 115
117 116 rightSidePane->addWidget(spacerRightTop);
118 117 rightSidePane->addAction(openRightInspectorAction);
119 118 rightSidePane->addWidget(spacerRightBottom);
120 119
121 120 openLeftInspectorAction->setCheckable(true);
122 121 openRightInspectorAction->setCheckable(true);
123 122
124 123 auto openInspector = [this](bool checked, bool right, auto action) {
125 124
126 125 action->setIcon(QIcon{(checked xor right) ? ":/icones/next.png" : ":/icones/previous.png"});
127 126
128 127 auto &lastInspectorSize
129 128 = right ? impl->m_LastOpenRightInspectorSize : impl->m_LastOpenLeftInspectorSize;
130 129
131 130 auto nextInspectorSize = right ? m_Ui->rightMainInspectorWidget->size()
132 131 : m_Ui->leftMainInspectorWidget->size();
133 132
134 133 // Update of the last opened geometry
135 134 if (checked) {
136 135 lastInspectorSize = nextInspectorSize;
137 136 }
138 137
139 138 auto startSize = lastInspectorSize;
140 139 auto endSize = startSize;
141 140 endSize.setWidth(0);
142 141
143 142 auto splitterInspectorIndex
144 143 = right ? RIGHTMAININSPECTORWIDGETSPLITTERINDEX : LEFTMAININSPECTORWIDGETSPLITTERINDEX;
145 144
146 145 auto currentSizes = m_Ui->splitter->sizes();
147 146 if (checked) {
148 147 // adjust sizes individually here, e.g.
149 148 currentSizes[splitterInspectorIndex] -= lastInspectorSize.width();
150 149 currentSizes[VIEWPLITTERINDEX] += lastInspectorSize.width();
151 150 m_Ui->splitter->setSizes(currentSizes);
152 151 }
153 152 else {
154 153 // adjust sizes individually here, e.g.
155 154 currentSizes[splitterInspectorIndex] += lastInspectorSize.width();
156 155 currentSizes[VIEWPLITTERINDEX] -= lastInspectorSize.width();
157 156 m_Ui->splitter->setSizes(currentSizes);
158 157 }
159 158
160 159 };
161 160
162 161
163 162 connect(openLeftInspectorAction, &QAction::triggered,
164 163 [openInspector, openLeftInspectorAction](bool checked) {
165 164 openInspector(checked, false, openLeftInspectorAction);
166 165 });
167 166 connect(openRightInspectorAction, &QAction::triggered,
168 167 [openInspector, openRightInspectorAction](bool checked) {
169 168 openInspector(checked, true, openRightInspectorAction);
170 169 });
171 170
172 171 // //// //
173 172 // Menu //
174 173 // //// //
175 174 this->menuBar()->addAction(tr("File"));
176 175 auto toolsMenu = this->menuBar()->addMenu(tr("Tools"));
177 176 toolsMenu->addAction(tr("Settings..."), [this]() {
178 177 // Loads settings
179 178 impl->m_SettingsDialog->loadSettings();
180 179
181 180 // Open settings dialog and save settings if the dialog is accepted
182 181 if (impl->m_SettingsDialog->exec() == QDialog::Accepted) {
183 182 impl->m_SettingsDialog->saveSettings();
184 183 }
185 184
186 185 });
187 186
188 187 auto mainToolBar = this->addToolBar(QStringLiteral("MainToolBar"));
189 188
190 189 auto timeWidget = new TimeWidget{};
191 190 mainToolBar->addWidget(timeWidget);
192 191
193 192 // //////// //
194 193 // Settings //
195 194 // //////// //
196 195
197 196 // Registers "general settings" widget to the settings dialog
198 197 impl->m_SettingsDialog->registerWidget(QStringLiteral("General"),
199 198 impl->m_GeneralSettingsWidget);
200 199
201 200 // /////////// //
202 201 // Connections //
203 202 // /////////// //
204 203
205 204 // Controllers / controllers connections
206 205 connect(&sqpApp->timeController(), SIGNAL(timeUpdated(SqpDateTime)),
207 206 &sqpApp->variableController(), SLOT(onDateTimeOnSelection(SqpDateTime)));
208 207
209 208 // Widgets / controllers connections
210 209
211 210 // DataSource
212 211 connect(&sqpApp->dataSourceController(), SIGNAL(dataSourceItemSet(DataSourceItem *)),
213 212 m_Ui->dataSourceWidget, SLOT(addDataSource(DataSourceItem *)));
214 213
215 214 // Time
216 215 connect(timeWidget, SIGNAL(timeUpdated(SqpDateTime)), &sqpApp->timeController(),
217 216 SLOT(onTimeToUpdate(SqpDateTime)));
218 217
219 218 // Visualization
220 219 connect(&sqpApp->visualizationController(),
221 220 SIGNAL(variableAboutToBeDeleted(std::shared_ptr<Variable>)), m_Ui->view,
222 221 SLOT(onVariableAboutToBeDeleted(std::shared_ptr<Variable>)));
223 222
224 223 connect(&sqpApp->visualizationController(),
225 224 SIGNAL(rangeChanged(std::shared_ptr<Variable>, const SqpDateTime &)), m_Ui->view,
226 225 SLOT(onRangeChanged(std::shared_ptr<Variable>, const SqpDateTime &)));
227 226
228 227 // Widgets / widgets connections
229 228
230 229 // For the following connections, we use DirectConnection to allow each widget that can
231 230 // potentially attach a menu to the variable's menu to do so before this menu is displayed.
232 231 // The order of connections is also important, since it determines the order in which each
233 232 // widget will attach its menu
234 233 connect(
235 234 m_Ui->variableInspectorWidget,
236 235 SIGNAL(tableMenuAboutToBeDisplayed(QMenu *, const QVector<std::shared_ptr<Variable> > &)),
237 236 m_Ui->view, SLOT(attachVariableMenu(QMenu *, const QVector<std::shared_ptr<Variable> > &)),
238 237 Qt::DirectConnection);
239 238 }
240 239
241 240 MainWindow::~MainWindow()
242 241 {
243 242 }
244 243
245 244 void MainWindow::changeEvent(QEvent *e)
246 245 {
247 246 QMainWindow::changeEvent(e);
248 247 switch (e->type()) {
249 248 case QEvent::LanguageChange:
250 249 m_Ui->retranslateUi(this);
251 250 break;
252 251 default:
253 252 break;
254 253 }
255 254 }
@@ -1,69 +1,69
1 1 #ifndef SCIQLOP_IDATASERIES_H
2 2 #define SCIQLOP_IDATASERIES_H
3 3
4 4 #include <Common/MetaTypes.h>
5 5
6 6 #include <memory>
7 7
8 8 #include <QString>
9 9
10 10 template <int Dim>
11 11 class ArrayData;
12 12
13 13 struct Unit {
14 14 explicit Unit(const QString &name = {}, bool timeUnit = false)
15 15 : m_Name{name}, m_TimeUnit{timeUnit}
16 16 {
17 17 }
18 18
19 19 inline bool operator==(const Unit &other) const
20 20 {
21 21 return std::tie(m_Name, m_TimeUnit) == std::tie(other.m_Name, other.m_TimeUnit);
22 22 }
23 23 inline bool operator!=(const Unit &other) const { return !(*this == other); }
24 24
25 25 QString m_Name; ///< Unit name
26 bool m_TimeUnit; ///< The unit is a unit of time
26 bool m_TimeUnit; ///< The unit is a unit of time (UTC)
27 27 };
28 28
29 29 /**
30 30 * @brief The IDataSeries aims to declare a data series.
31 31 *
32 32 * A data series is an entity that contains at least :
33 33 * - one dataset representing the x-axis
34 34 * - one dataset representing the values
35 35 *
36 36 * Each dataset is represented by an ArrayData, and is associated with a unit.
37 37 *
38 38 * An ArrayData can be unidimensional or two-dimensional, depending on the implementation of the
39 39 * IDataSeries. The x-axis dataset is always unidimensional.
40 40 *
41 41 * @sa ArrayData
42 42 */
43 43 class IDataSeries {
44 44 public:
45 45 virtual ~IDataSeries() noexcept = default;
46 46
47 47 /// Returns the x-axis dataset
48 48 virtual std::shared_ptr<ArrayData<1> > xAxisData() = 0;
49 49
50 50 /// Returns the x-axis dataset (as const)
51 51 virtual const std::shared_ptr<ArrayData<1> > xAxisData() const = 0;
52 52
53 53 virtual Unit xAxisUnit() const = 0;
54 54
55 55 virtual Unit valuesUnit() const = 0;
56 56
57 57 virtual void merge(IDataSeries *dataSeries) = 0;
58 58
59 59 virtual std::unique_ptr<IDataSeries> clone() const = 0;
60 60
61 61 virtual void lockRead() = 0;
62 62 virtual void lockWrite() = 0;
63 63 virtual void unlock() = 0;
64 64 };
65 65
66 66 // Required for using shared_ptr in signals/slots
67 67 SCIQLOP_REGISTER_META_TYPE(IDATASERIES_PTR_REGISTRY, std::shared_ptr<IDataSeries>)
68 68
69 69 #endif // SCIQLOP_IDATASERIES_H
@@ -1,44 +1,43
1 1 #ifndef SCIQLOP_SQPDATETIME_H
2 2 #define SCIQLOP_SQPDATETIME_H
3 3
4 4 #include <QObject>
5 5
6 #include <QDateTime>
7 6 #include <QDebug>
8 7
8 #include <Common/DateUtils.h>
9 9 #include <Common/MetaTypes.h>
10 10
11 11 /**
12 12 * @brief The SqpDateTime struct holds the information of time parameters
13 13 */
14 14 struct SqpDateTime {
15 /// Start time
15 /// Start time (UTC)
16 16 double m_TStart;
17 /// End time
17 /// End time (UTC)
18 18 double m_TEnd;
19 19
20 20 bool contains(const SqpDateTime &dateTime) const noexcept
21 21 {
22 22 return (m_TStart <= dateTime.m_TStart && m_TEnd >= dateTime.m_TEnd);
23 23 }
24 24
25 25 bool intersect(const SqpDateTime &dateTime) const noexcept
26 26 {
27 27 return (m_TEnd >= dateTime.m_TStart && m_TStart <= dateTime.m_TEnd);
28 28 }
29 29 };
30 30
31 31 inline QDebug operator<<(QDebug d, SqpDateTime obj)
32 32 {
33 auto tendDateTimeStart = QDateTime::fromMSecsSinceEpoch(obj.m_TStart * 1000);
34 auto tendDateTimeEnd = QDateTime::fromMSecsSinceEpoch(obj.m_TEnd * 1000);
33 auto tendDateTimeStart = DateUtils::dateTime(obj.m_TStart);
34 auto tendDateTimeEnd = DateUtils::dateTime(obj.m_TEnd);
35 35
36 // QDebug << "ts: " << tendDateTimeStart << " te: " << tendDateTimeEnd;
37 36 d << "ts: " << tendDateTimeStart << " te: " << tendDateTimeEnd;
38 37 return d;
39 38 }
40 39
41 40 // Required for using shared_ptr in signals/slots
42 41 SCIQLOP_REGISTER_META_TYPE(SQPDATETIME_REGISTRY, SqpDateTime)
43 42
44 43 #endif // SCIQLOP_SQPDATETIME_H
@@ -1,244 +1,243
1 1 #include <Variable/Variable.h>
2 2 #include <Variable/VariableCacheController.h>
3 3 #include <Variable/VariableController.h>
4 4 #include <Variable/VariableModel.h>
5 5
6 6 #include <Data/DataProviderParameters.h>
7 7 #include <Data/IDataProvider.h>
8 8 #include <Data/IDataSeries.h>
9 9 #include <Time/TimeController.h>
10 10
11 #include <QDateTime>
12 11 #include <QMutex>
13 12 #include <QThread>
14 13 #include <QUuid>
15 14 #include <QtCore/QItemSelectionModel>
16 15
17 16 #include <unordered_map>
18 17
19 18 Q_LOGGING_CATEGORY(LOG_VariableController, "VariableController")
20 19
21 20 struct VariableController::VariableControllerPrivate {
22 21 explicit VariableControllerPrivate(VariableController *parent)
23 22 : m_WorkingMutex{},
24 23 m_VariableModel{new VariableModel{parent}},
25 24 m_VariableSelectionModel{new QItemSelectionModel{m_VariableModel, parent}},
26 25 m_VariableCacheController{std::make_unique<VariableCacheController>()}
27 26 {
28 27 }
29 28
30 29 QMutex m_WorkingMutex;
31 30 /// Variable model. The VariableController has the ownership
32 31 VariableModel *m_VariableModel;
33 32 QItemSelectionModel *m_VariableSelectionModel;
34 33
35 34
36 35 TimeController *m_TimeController{nullptr};
37 36 std::unique_ptr<VariableCacheController> m_VariableCacheController;
38 37
39 38 std::unordered_map<std::shared_ptr<Variable>, std::shared_ptr<IDataProvider> >
40 39 m_VariableToProviderMap;
41 40 std::unordered_map<std::shared_ptr<Variable>, QUuid> m_VariableToIdentifierMap;
42 41 };
43 42
44 43 VariableController::VariableController(QObject *parent)
45 44 : QObject{parent}, impl{spimpl::make_unique_impl<VariableControllerPrivate>(this)}
46 45 {
47 46 qCDebug(LOG_VariableController()) << tr("VariableController construction")
48 47 << QThread::currentThread();
49 48
50 49 connect(impl->m_VariableModel, &VariableModel::abortProgessRequested, this,
51 50 &VariableController::onAbortProgressRequested);
52 51 }
53 52
54 53 VariableController::~VariableController()
55 54 {
56 55 qCDebug(LOG_VariableController()) << tr("VariableController destruction")
57 56 << QThread::currentThread();
58 57 this->waitForFinish();
59 58 }
60 59
61 60 VariableModel *VariableController::variableModel() noexcept
62 61 {
63 62 return impl->m_VariableModel;
64 63 }
65 64
66 65 QItemSelectionModel *VariableController::variableSelectionModel() noexcept
67 66 {
68 67 return impl->m_VariableSelectionModel;
69 68 }
70 69
71 70 void VariableController::setTimeController(TimeController *timeController) noexcept
72 71 {
73 72 impl->m_TimeController = timeController;
74 73 }
75 74
76 75 void VariableController::deleteVariable(std::shared_ptr<Variable> variable) noexcept
77 76 {
78 77 if (!variable) {
79 78 qCCritical(LOG_VariableController()) << "Can't delete variable: variable is null";
80 79 return;
81 80 }
82 81
83 82 // Spreads in SciQlop that the variable will be deleted, so that potential receivers can
84 83 // make some treatments before the deletion
85 84 emit variableAboutToBeDeleted(variable);
86 85
87 86 // Deletes identifier
88 87 impl->m_VariableToIdentifierMap.erase(variable);
89 88
90 89 // Deletes provider
91 90 auto nbProvidersDeleted = impl->m_VariableToProviderMap.erase(variable);
92 91 qCDebug(LOG_VariableController())
93 92 << tr("Number of providers deleted for variable %1: %2")
94 93 .arg(variable->name(), QString::number(nbProvidersDeleted));
95 94
96 95 // Clears cache
97 96 impl->m_VariableCacheController->clear(variable);
98 97
99 98 // Deletes from model
100 99 impl->m_VariableModel->deleteVariable(variable);
101 100 }
102 101
103 102 void VariableController::deleteVariables(
104 103 const QVector<std::shared_ptr<Variable> > &variables) noexcept
105 104 {
106 105 for (auto variable : qAsConst(variables)) {
107 106 deleteVariable(variable);
108 107 }
109 108 }
110 109
111 110 void VariableController::abortProgress(std::shared_ptr<Variable> variable)
112 111 {
113 112 }
114 113
115 114 void VariableController::createVariable(const QString &name, const QVariantHash &metadata,
116 115 std::shared_ptr<IDataProvider> provider) noexcept
117 116 {
118 117
119 118 if (!impl->m_TimeController) {
120 119 qCCritical(LOG_VariableController())
121 120 << tr("Impossible to create variable: The time controller is null");
122 121 return;
123 122 }
124 123
125 124 auto dateTime = impl->m_TimeController->dateTime();
126 125
127 126 if (auto newVariable = impl->m_VariableModel->createVariable(name, dateTime, metadata)) {
128 127 auto identifier = QUuid::createUuid();
129 128
130 129 // store the provider
131 130 impl->m_VariableToProviderMap[newVariable] = provider;
132 131 impl->m_VariableToIdentifierMap[newVariable] = identifier;
133 132
134 133 auto addDateTimeAcquired = [ this, varW = std::weak_ptr<Variable>{newVariable} ](
135 134 QUuid identifier, auto dataSeriesAcquired, auto dateTimeToPutInCache)
136 135 {
137 136 if (auto variable = varW.lock()) {
138 137 auto varIdentifier = impl->m_VariableToIdentifierMap.at(variable);
139 138 if (varIdentifier == identifier) {
140 139 impl->m_VariableCacheController->addDateTime(variable, dateTimeToPutInCache);
141 140 variable->setDataSeries(dataSeriesAcquired);
142 141 emit variable->updated();
143 142 }
144 143 }
145 144 };
146 145
147 146 connect(provider.get(), &IDataProvider::dataProvided, addDateTimeAcquired);
148 147 connect(provider.get(), &IDataProvider::dataProvidedProgress, this,
149 148 &VariableController::onVariableRetrieveDataInProgress);
150 149 this->onRequestDataLoading(newVariable, dateTime);
151 150 }
152 151 }
153 152
154 153 void VariableController::onDateTimeOnSelection(const SqpDateTime &dateTime)
155 154 {
156 155 qCDebug(LOG_VariableController()) << "VariableController::onDateTimeOnSelection"
157 156 << QThread::currentThread()->objectName();
158 157 auto selectedRows = impl->m_VariableSelectionModel->selectedRows();
159 158
160 159 for (const auto &selectedRow : qAsConst(selectedRows)) {
161 160 if (auto selectedVariable = impl->m_VariableModel->variable(selectedRow.row())) {
162 161 selectedVariable->setDateTime(dateTime);
163 162 this->onRequestDataLoading(selectedVariable, dateTime);
164 163
165 164 // notify that rescale operation has to be done
166 165 emit rangeChanged(selectedVariable, dateTime);
167 166 }
168 167 }
169 168 }
170 169
171 170 void VariableController::onVariableRetrieveDataInProgress(QUuid identifier, double progress)
172 171 {
173 172 auto findReply = [identifier](const auto &entry) { return identifier == entry.second; };
174 173
175 174 auto end = impl->m_VariableToIdentifierMap.cend();
176 175 auto it = std::find_if(impl->m_VariableToIdentifierMap.cbegin(), end, findReply);
177 176 if (it != end) {
178 177 impl->m_VariableModel->setDataProgress(it->first, progress);
179 178 }
180 179 }
181 180
182 181 void VariableController::onAbortProgressRequested(std::shared_ptr<Variable> variable)
183 182 {
184 183 qCDebug(LOG_VariableController()) << "TORM: VariableController::onAbortProgressRequested"
185 184 << QThread::currentThread()->objectName();
186 185
187 186 auto it = impl->m_VariableToIdentifierMap.find(variable);
188 187 if (it != impl->m_VariableToIdentifierMap.cend()) {
189 188 impl->m_VariableToProviderMap.at(variable)->requestDataAborting(it->second);
190 189 }
191 190 else {
192 191 qCWarning(LOG_VariableController())
193 192 << tr("Aborting progression of inexistant variable detected !!!")
194 193 << QThread::currentThread()->objectName();
195 194 }
196 195 }
197 196
198 197
199 198 void VariableController::onRequestDataLoading(std::shared_ptr<Variable> variable,
200 199 const SqpDateTime &dateTime)
201 200 {
202 201 qCDebug(LOG_VariableController()) << "VariableController::onRequestDataLoading"
203 202 << QThread::currentThread()->objectName();
204 203 // we want to load data of the variable for the dateTime.
205 204 // First we check if the cache contains some of them.
206 205 // For the other, we ask the provider to give them.
207 206 if (variable) {
208 207
209 208 auto dateTimeListNotInCache
210 209 = impl->m_VariableCacheController->provideNotInCacheDateTimeList(variable, dateTime);
211 210
212 211 if (!dateTimeListNotInCache.empty()) {
213 212 // Ask the provider for each data on the dateTimeListNotInCache
214 213 auto identifier = impl->m_VariableToIdentifierMap.at(variable);
215 214 impl->m_VariableToProviderMap.at(variable)->requestDataLoading(
216 215 identifier,
217 216 DataProviderParameters{std::move(dateTimeListNotInCache), variable->metadata()});
218 217 }
219 218 else {
220 219 emit variable->updated();
221 220 }
222 221 }
223 222 else {
224 223 qCCritical(LOG_VariableController()) << tr("Impossible to load data of a variable null");
225 224 }
226 225 }
227 226
228 227
229 228 void VariableController::initialize()
230 229 {
231 230 qCDebug(LOG_VariableController()) << tr("VariableController init") << QThread::currentThread();
232 231 impl->m_WorkingMutex.lock();
233 232 qCDebug(LOG_VariableController()) << tr("VariableController init END");
234 233 }
235 234
236 235 void VariableController::finalize()
237 236 {
238 237 impl->m_WorkingMutex.unlock();
239 238 }
240 239
241 240 void VariableController::waitForFinish()
242 241 {
243 242 QMutexLocker locker{&impl->m_WorkingMutex};
244 243 }
@@ -1,248 +1,249
1 1 #include <Variable/Variable.h>
2 2 #include <Variable/VariableModel.h>
3 3
4 #include <Common/DateUtils.h>
5
4 6 #include <Data/IDataSeries.h>
5 7
6 #include <QDateTime>
7 8 #include <QSize>
8 9 #include <unordered_map>
9 10
10 11 Q_LOGGING_CATEGORY(LOG_VariableModel, "VariableModel")
11 12
12 13 namespace {
13 14
14 15 // Column indexes
15 16 const auto NAME_COLUMN = 0;
16 17 const auto TSTART_COLUMN = 1;
17 18 const auto TEND_COLUMN = 2;
18 19 const auto NB_COLUMNS = 3;
19 20
20 21 // Column properties
21 22 const auto DEFAULT_HEIGHT = 25;
22 23 const auto DEFAULT_WIDTH = 100;
23 24
24 25 struct ColumnProperties {
25 26 ColumnProperties(const QString &name = {}, int width = DEFAULT_WIDTH,
26 27 int height = DEFAULT_HEIGHT)
27 28 : m_Name{name}, m_Width{width}, m_Height{height}
28 29 {
29 30 }
30 31
31 32 QString m_Name;
32 33 int m_Width;
33 34 int m_Height;
34 35 };
35 36
36 37 const auto COLUMN_PROPERTIES
37 38 = QHash<int, ColumnProperties>{{NAME_COLUMN, {QObject::tr("Name")}},
38 39 {TSTART_COLUMN, {QObject::tr("tStart"), 180}},
39 40 {TEND_COLUMN, {QObject::tr("tEnd"), 180}}};
40 41
41 42 /// Format for datetimes
42 43 const auto DATETIME_FORMAT = QStringLiteral("dd/MM/yyyy \nhh:mm:ss:zzz");
43 44
44 45
45 46 } // namespace
46 47
47 48 struct VariableModel::VariableModelPrivate {
48 49 /// Variables created in SciQlop
49 50 std::vector<std::shared_ptr<Variable> > m_Variables;
50 51 std::unordered_map<std::shared_ptr<Variable>, double> m_VariableToProgress;
51 52
52 53 /// Return the row index of the variable. -1 if it's not found
53 54 int indexOfVariable(Variable *variable) const noexcept;
54 55 };
55 56
56 57 VariableModel::VariableModel(QObject *parent)
57 58 : QAbstractTableModel{parent}, impl{spimpl::make_unique_impl<VariableModelPrivate>()}
58 59 {
59 60 }
60 61
61 62 std::shared_ptr<Variable> VariableModel::createVariable(const QString &name,
62 63 const SqpDateTime &dateTime,
63 64 const QVariantHash &metadata) noexcept
64 65 {
65 66 auto insertIndex = rowCount();
66 67 beginInsertRows({}, insertIndex, insertIndex);
67 68
68 69 auto variable = std::make_shared<Variable>(name, dateTime, metadata);
69 70
70 71 impl->m_Variables.push_back(variable);
71 72 connect(variable.get(), &Variable::updated, this, &VariableModel::onVariableUpdated);
72 73
73 74 endInsertRows();
74 75
75 76 return variable;
76 77 }
77 78
78 79 void VariableModel::deleteVariable(std::shared_ptr<Variable> variable) noexcept
79 80 {
80 81 if (!variable) {
81 82 qCCritical(LOG_Variable()) << "Can't delete a null variable from the model";
82 83 return;
83 84 }
84 85
85 86 // Finds variable in the model
86 87 auto begin = impl->m_Variables.cbegin();
87 88 auto end = impl->m_Variables.cend();
88 89 auto it = std::find(begin, end, variable);
89 90 if (it != end) {
90 91 auto removeIndex = std::distance(begin, it);
91 92
92 93 // Deletes variable
93 94 beginRemoveRows({}, removeIndex, removeIndex);
94 95 impl->m_Variables.erase(it);
95 96 endRemoveRows();
96 97 }
97 98 else {
98 99 qCritical(LOG_VariableModel())
99 100 << tr("Can't delete variable %1 from the model: the variable is not in the model")
100 101 .arg(variable->name());
101 102 }
102 103
103 104 // Removes variable from progress map
104 105 impl->m_VariableToProgress.erase(variable);
105 106 }
106 107
107 108
108 109 std::shared_ptr<Variable> VariableModel::variable(int index) const
109 110 {
110 111 return (index >= 0 && index < impl->m_Variables.size()) ? impl->m_Variables[index] : nullptr;
111 112 }
112 113
113 114 void VariableModel::setDataProgress(std::shared_ptr<Variable> variable, double progress)
114 115 {
115 116 if (progress > 0.0) {
116 117 impl->m_VariableToProgress[variable] = progress;
117 118 }
118 119 else {
119 120 impl->m_VariableToProgress.erase(variable);
120 121 }
121 122 auto modelIndex = createIndex(impl->indexOfVariable(variable.get()), NAME_COLUMN);
122 123
123 124 emit dataChanged(modelIndex, modelIndex);
124 125 }
125 126
126 127 int VariableModel::columnCount(const QModelIndex &parent) const
127 128 {
128 129 Q_UNUSED(parent);
129 130
130 131 return NB_COLUMNS;
131 132 }
132 133
133 134 int VariableModel::rowCount(const QModelIndex &parent) const
134 135 {
135 136 Q_UNUSED(parent);
136 137
137 138 return impl->m_Variables.size();
138 139 }
139 140
140 141 QVariant VariableModel::data(const QModelIndex &index, int role) const
141 142 {
142 143 if (!index.isValid()) {
143 144 return QVariant{};
144 145 }
145 146
146 147 if (index.row() < 0 || index.row() >= rowCount()) {
147 148 return QVariant{};
148 149 }
149 150
150 151 if (role == Qt::DisplayRole) {
151 152 if (auto variable = impl->m_Variables.at(index.row()).get()) {
152 153 /// Lambda function that builds the variant to return for a time value
153 auto dateTimeVariant = [](double time) {
154 auto dateTime = QDateTime::fromMSecsSinceEpoch(time * 1000.);
154 auto dateTimeVariant = [](double secs) {
155 auto dateTime = DateUtils::dateTime(secs);
155 156 return dateTime.toString(DATETIME_FORMAT);
156 157 };
157 158
158 159 switch (index.column()) {
159 160 case NAME_COLUMN:
160 161 return variable->name();
161 162 case TSTART_COLUMN:
162 163 return dateTimeVariant(variable->dateTime().m_TStart);
163 164 case TEND_COLUMN:
164 165 return dateTimeVariant(variable->dateTime().m_TEnd);
165 166 default:
166 167 // No action
167 168 break;
168 169 }
169 170
170 171 qWarning(LOG_VariableModel())
171 172 << tr("Can't get data (unknown column %1)").arg(index.column());
172 173 }
173 174 else {
174 175 qWarning(LOG_VariableModel()) << tr("Can't get data (no variable)");
175 176 }
176 177 }
177 178 else if (role == VariableRoles::ProgressRole) {
178 179 if (auto variable = impl->m_Variables.at(index.row())) {
179 180
180 181 auto it = impl->m_VariableToProgress.find(variable);
181 182 if (it != impl->m_VariableToProgress.cend()) {
182 183 return it->second;
183 184 }
184 185 }
185 186 }
186 187
187 188 return QVariant{};
188 189 }
189 190
190 191 QVariant VariableModel::headerData(int section, Qt::Orientation orientation, int role) const
191 192 {
192 193 if (role != Qt::DisplayRole && role != Qt::SizeHintRole) {
193 194 return QVariant{};
194 195 }
195 196
196 197 if (orientation == Qt::Horizontal) {
197 198 auto propertiesIt = COLUMN_PROPERTIES.find(section);
198 199 if (propertiesIt != COLUMN_PROPERTIES.cend()) {
199 200 // Role is either DisplayRole or SizeHintRole
200 201 return (role == Qt::DisplayRole)
201 202 ? QVariant{propertiesIt->m_Name}
202 203 : QVariant{QSize{propertiesIt->m_Width, propertiesIt->m_Height}};
203 204 }
204 205 else {
205 206 qWarning(LOG_VariableModel())
206 207 << tr("Can't get header data (unknown column %1)").arg(section);
207 208 }
208 209 }
209 210
210 211 return QVariant{};
211 212 }
212 213
213 214 void VariableModel::abortProgress(const QModelIndex &index)
214 215 {
215 216 if (auto variable = impl->m_Variables.at(index.row())) {
216 217 emit abortProgessRequested(variable);
217 218 }
218 219 }
219 220
220 221 void VariableModel::onVariableUpdated() noexcept
221 222 {
222 223 // Finds variable that has been updated in the model
223 224 if (auto updatedVariable = dynamic_cast<Variable *>(sender())) {
224 225 auto updatedVariableIndex = impl->indexOfVariable(updatedVariable);
225 226
226 227 if (updatedVariableIndex > -1) {
227 228 emit dataChanged(createIndex(updatedVariableIndex, 0),
228 229 createIndex(updatedVariableIndex, columnCount() - 1));
229 230 }
230 231 }
231 232 }
232 233
233 234 int VariableModel::VariableModelPrivate::indexOfVariable(Variable *variable) const noexcept
234 235 {
235 236 auto begin = std::cbegin(m_Variables);
236 237 auto end = std::cend(m_Variables);
237 238 auto it
238 239 = std::find_if(begin, end, [variable](const auto &var) { return var.get() == variable; });
239 240
240 241 if (it != end) {
241 242 // Gets the index of the variable in the model: we assume here that views have the same
242 243 // order as the model
243 244 return std::distance(begin, it);
244 245 }
245 246 else {
246 247 return -1;
247 248 }
248 249 }
@@ -1,82 +1,84
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
3 3
4 4 #include "Visualization/IVisualizationWidget.h"
5 5
6 6 #include <QLoggingCategory>
7 7 #include <QWidget>
8 8
9 9 #include <memory>
10 10
11 11 #include <Common/spimpl.h>
12 12
13 13 Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationGraphWidget)
14 14
15 15 class QCPRange;
16 16 class SqpDateTime;
17 17 class Variable;
18 18
19 19 /**
20 20 * Possible types of zoom operation
21 21 */
22 22 enum class VisualizationGraphWidgetZoomType { ZoomOut, ZoomIn, PanRight, PanLeft, Unknown };
23 23
24 24 namespace Ui {
25 25 class VisualizationGraphWidget;
26 26 } // namespace Ui
27 27
28 28 class VisualizationGraphWidget : public QWidget, public IVisualizationWidget {
29 29 Q_OBJECT
30 30
31 31 public:
32 32 explicit VisualizationGraphWidget(const QString &name = {}, QWidget *parent = 0);
33 33 virtual ~VisualizationGraphWidget();
34 34
35 35 void enableSynchronize(bool enable);
36 36
37 37 void addVariable(std::shared_ptr<Variable> variable);
38 38 void addVariableUsingGraph(std::shared_ptr<Variable> variable);
39 39 /// Removes a variable from the graph
40 40 void removeVariable(std::shared_ptr<Variable> variable) noexcept;
41 41
42 42 void setRange(std::shared_ptr<Variable> variable, const SqpDateTime &range);
43 43 SqpDateTime graphRange() const noexcept;
44 44 void setGraphRange(const SqpDateTime &range);
45 45
46 46 // IVisualizationWidget interface
47 47 void accept(IVisualizationWidgetVisitor *visitor) override;
48 48 bool canDrop(const Variable &variable) const override;
49 49 bool contains(const Variable &variable) const override;
50 50 QString name() const override;
51 51
52 52
53 53 signals:
54 54 void requestDataLoading(std::shared_ptr<Variable> variable, const SqpDateTime &dateTime);
55 55 void synchronize(const SqpDateTime &dateTime, const SqpDateTime &oldDateTime,
56 56 VisualizationGraphWidgetZoomType zoomType);
57 57
58 58
59 59 private:
60 60 Ui::VisualizationGraphWidget *ui;
61 61
62 62 class VisualizationGraphWidgetPrivate;
63 63 spimpl::unique_impl_ptr<VisualizationGraphWidgetPrivate> impl;
64 64
65 65 private slots:
66 66 /// Slot called when right clicking on the graph (displays a menu)
67 67 void onGraphMenuRequested(const QPoint &pos) noexcept;
68 68
69 69 /// Rescale the X axe to range parameter
70 70 void onRangeChanged(const QCPRange &t1, const QCPRange &t2);
71 71
72 /// Slot called when a mouse move was made
73 void onMouseMove(QMouseEvent *event) noexcept;
72 74 /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done
73 75 void onMouseWheel(QWheelEvent *event) noexcept;
74 76 /// Slot called when a mouse press was made, to activate the calibration of a graph
75 77 void onMousePress(QMouseEvent *event) noexcept;
76 78 /// Slot called when a mouse release was made, to deactivate the calibration of a graph
77 79 void onMouseRelease(QMouseEvent *event) noexcept;
78 80
79 81 void onDataCacheVariableUpdated();
80 82 };
81 83
82 84 #endif // SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
@@ -1,48 +1,50
1 1 #include "TimeWidget/TimeWidget.h"
2 2 #include "ui_TimeWidget.h"
3 3
4 #include <Common/DateUtils.h>
4 5 #include <SqpApplication.h>
5 6 #include <Time/TimeController.h>
6 7
7 8 TimeWidget::TimeWidget(QWidget *parent) : QWidget{parent}, ui{new Ui::TimeWidget}
8 9 {
9 10 ui->setupUi(this);
10 11
11 12 ui->applyToolButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_DialogApplyButton));
12 13
13 14 // Connection
14 15 connect(ui->startDateTimeEdit, &QDateTimeEdit::dateTimeChanged, this,
15 16 &TimeWidget::onTimeUpdateRequested);
16 17
17 18 connect(ui->endDateTimeEdit, &QDateTimeEdit::dateTimeChanged, this,
18 19 &TimeWidget::onTimeUpdateRequested);
19 20
20 21
21 22 connect(ui->applyToolButton, &QToolButton::clicked, &sqpApp->timeController(),
22 23 &TimeController::onTimeNotify);
23 24
24 25 // Initialisation
25 ui->startDateTimeEdit->setDateTime(
26 QDateTime::currentDateTime().addSecs(-3600)); // one hour berefore
27 ui->endDateTimeEdit->setDateTime(QDateTime::currentDateTime());
26 auto endDateTime = QDateTime::currentDateTimeUtc();
27 auto startDateTime = endDateTime.addSecs(-3600); // one hour before
28
29 ui->startDateTimeEdit->setDateTime(startDateTime);
30 ui->endDateTimeEdit->setDateTime(endDateTime);
31
32 auto dateTime = SqpDateTime{DateUtils::secondsSinceEpoch(startDateTime),
33 DateUtils::secondsSinceEpoch(endDateTime)};
28 34
29 auto dateTime
30 = SqpDateTime{QDateTime::currentDateTime().addSecs(-3600).toMSecsSinceEpoch() / 1000.0,
31 QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000.0};
32 35 sqpApp->timeController().onTimeToUpdate(dateTime);
33 36 }
34 37
35 38
36 39 TimeWidget::~TimeWidget()
37 40 {
38 41 delete ui;
39 42 }
40 43
41 44 void TimeWidget::onTimeUpdateRequested()
42 45 {
43 auto dateTime = SqpDateTime{
44 static_cast<double>(ui->startDateTimeEdit->dateTime().toMSecsSinceEpoch() / 1000.),
45 static_cast<double>(ui->endDateTimeEdit->dateTime().toMSecsSinceEpoch()) / 1000.};
46 auto dateTime = SqpDateTime{DateUtils::secondsSinceEpoch(ui->startDateTimeEdit->dateTime()),
47 DateUtils::secondsSinceEpoch(ui->endDateTimeEdit->dateTime())};
46 48
47 49 emit timeUpdated(std::move(dateTime));
48 50 }
@@ -1,161 +1,162
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 4 #include <Data/ScalarSeries.h>
5 5
6 6 #include <Variable/Variable.h>
7 7
8 8 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
9 9
10 10 namespace {
11 11
12 12 class SqpDataContainer : public QCPGraphDataContainer {
13 13 public:
14 14 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
15 15 };
16 16
17 17
18 18 /// Format for datetimes on a axis
19 19 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
20 20
21 21 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
22 22 /// non-time data
23 23 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis)
24 24 {
25 25 if (isTimeAxis) {
26 26 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
27 27 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
28 dateTicker->setDateTimeSpec(Qt::UTC);
28 29
29 30 return dateTicker;
30 31 }
31 32 else {
32 33 // default ticker
33 34 return QSharedPointer<QCPAxisTicker>::create();
34 35 }
35 36 }
36 37
37 38 void updateScalarData(QCPAbstractPlottable *component, ScalarSeries &scalarSeries,
38 39 const SqpDateTime &dateTime)
39 40 {
40 41 qCDebug(LOG_VisualizationGraphHelper()) << "TORM: updateScalarData"
41 42 << QThread::currentThread()->objectName();
42 43 if (auto qcpGraph = dynamic_cast<QCPGraph *>(component)) {
43 44 scalarSeries.lockRead();
44 45 {
45 46 const auto &xData = scalarSeries.xAxisData()->cdata();
46 47 const auto &valuesData = scalarSeries.valuesData()->cdata();
47 48
48 49 auto xDataBegin = xData.cbegin();
49 50 auto xDataEnd = xData.cend();
50 51
51 52 qCInfo(LOG_VisualizationGraphHelper()) << "TORM: Current points in cache"
52 53 << xData.count();
53 54
54 55 auto sqpDataContainer = QSharedPointer<SqpDataContainer>::create();
55 56 qcpGraph->setData(sqpDataContainer);
56 57
57 58 auto lowerIt = std::lower_bound(xDataBegin, xDataEnd, dateTime.m_TStart);
58 59 auto upperIt = std::upper_bound(xDataBegin, xDataEnd, dateTime.m_TEnd);
59 60 auto distance = std::distance(xDataBegin, lowerIt);
60 61
61 62 auto valuesDataIt = valuesData.cbegin() + distance;
62 63 for (auto xAxisDataIt = lowerIt; xAxisDataIt != upperIt;
63 64 ++xAxisDataIt, ++valuesDataIt) {
64 65 sqpDataContainer->appendGraphData(QCPGraphData(*xAxisDataIt, *valuesDataIt));
65 66 }
66 67
67 68 qCInfo(LOG_VisualizationGraphHelper()) << "TORM: Current points displayed"
68 69 << sqpDataContainer->size();
69 70 }
70 71 scalarSeries.unlock();
71 72
72 73
73 74 // Display all data
74 75 component->parentPlot()->replot();
75 76 }
76 77 else {
77 78 /// @todo DEBUG
78 79 }
79 80 }
80 81
81 82 QCPAbstractPlottable *createScalarSeriesComponent(ScalarSeries &scalarSeries, QCustomPlot &plot,
82 83 const SqpDateTime &dateTime)
83 84 {
84 85 auto component = plot.addGraph();
85 86
86 87 if (component) {
87 88 // // Graph data
88 89 component->setData(scalarSeries.xAxisData()->data(), scalarSeries.valuesData()->data(),
89 90 true);
90 91
91 92 updateScalarData(component, scalarSeries, dateTime);
92 93
93 94 // Axes properties
94 95 /// @todo : for the moment, no control is performed on the axes: the units and the tickers
95 96 /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph
96 97
97 98 auto setAxisProperties = [](auto axis, const auto &unit) {
98 99 // label (unit name)
99 100 axis->setLabel(unit.m_Name);
100 101
101 102 // ticker (depending on the type of unit)
102 103 axis->setTicker(axisTicker(unit.m_TimeUnit));
103 104 };
104 105 setAxisProperties(plot.xAxis, scalarSeries.xAxisUnit());
105 106 setAxisProperties(plot.yAxis, scalarSeries.valuesUnit());
106 107
107 108 // Display all data
108 109 component->rescaleAxes();
109 110 plot.replot();
110 111 }
111 112 else {
112 113 qCDebug(LOG_VisualizationGraphHelper())
113 114 << QObject::tr("Can't create graph for the scalar series");
114 115 }
115 116
116 117 return component;
117 118 }
118 119
119 120 } // namespace
120 121
121 122 QVector<QCPAbstractPlottable *> VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
122 123 QCustomPlot &plot) noexcept
123 124 {
124 125 auto result = QVector<QCPAbstractPlottable *>{};
125 126
126 127 if (variable) {
127 128 // Gets the data series of the variable to call the creation of the right components
128 129 // according to its type
129 130 if (auto scalarSeries = dynamic_cast<ScalarSeries *>(variable->dataSeries())) {
130 131 result.append(createScalarSeriesComponent(*scalarSeries, plot, variable->dateTime()));
131 132 }
132 133 else {
133 134 qCDebug(LOG_VisualizationGraphHelper())
134 135 << QObject::tr("Can't create graph plottables : unmanaged data series type");
135 136 }
136 137 }
137 138 else {
138 139 qCDebug(LOG_VisualizationGraphHelper())
139 140 << QObject::tr("Can't create graph plottables : the variable is null");
140 141 }
141 142
142 143 return result;
143 144 }
144 145
145 146 void VisualizationGraphHelper::updateData(QVector<QCPAbstractPlottable *> plotableVect,
146 147 IDataSeries *dataSeries, const SqpDateTime &dateTime)
147 148 {
148 149 if (auto scalarSeries = dynamic_cast<ScalarSeries *>(dataSeries)) {
149 150 if (plotableVect.size() == 1) {
150 151 updateScalarData(plotableVect.at(0), *scalarSeries, dateTime);
151 152 }
152 153 else {
153 154 qCCritical(LOG_VisualizationGraphHelper()) << QObject::tr(
154 155 "Can't update Data of a scalarSeries because there is not only one component "
155 156 "associated");
156 157 }
157 158 }
158 159 else {
159 160 /// @todo DEBUG
160 161 }
161 162 }
@@ -1,412 +1,428
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationGraphHelper.h"
4 #include "Visualization/VisualizationGraphRenderingDelegate.h"
4 5 #include "ui_VisualizationGraphWidget.h"
5 6
6 7 #include <Data/ArrayData.h>
7 8 #include <Data/IDataSeries.h>
8 9 #include <Settings/SqpSettingsDefs.h>
9 10 #include <SqpApplication.h>
10 11 #include <Variable/Variable.h>
11 12 #include <Variable/VariableController.h>
12 13
13 14 #include <unordered_map>
14 15
15 16 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
16 17
17 18 namespace {
18 19
19 20 /// Key pressed to enable zoom on horizontal axis
20 21 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::NoModifier;
21 22
22 23 /// Key pressed to enable zoom on vertical axis
23 24 const auto VERTICAL_ZOOM_MODIFIER = Qt::ControlModifier;
24 25
25 26 /// Gets a tolerance value from application settings. If the setting can't be found, the default
26 27 /// value passed in parameter is returned
27 28 double toleranceValue(const QString &key, double defaultValue) noexcept
28 29 {
29 30 return QSettings{}.value(key, defaultValue).toDouble();
30 31 }
31 32
32 33 } // namespace
33 34
34 35 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
35 36
36 explicit VisualizationGraphWidgetPrivate() : m_DoSynchronize{true}, m_IsCalibration{false} {}
37
37 explicit VisualizationGraphWidgetPrivate()
38 : m_DoSynchronize{true}, m_IsCalibration{false}, m_RenderingDelegate{nullptr}
39 {
40 }
38 41
39 42 // Return the operation when range changed
40 43 VisualizationGraphWidgetZoomType getZoomType(const QCPRange &t1, const QCPRange &t2);
41 44
42 45 // 1 variable -> n qcpplot
43 46 std::multimap<std::shared_ptr<Variable>, QCPAbstractPlottable *> m_VariableToPlotMultiMap;
44
45 47 bool m_DoSynchronize;
46 48 bool m_IsCalibration;
49 QCPItemTracer *m_TextTracer;
50 /// Delegate used to attach rendering features to the plot
51 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
47 52 };
48 53
49 54 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
50 55 : QWidget{parent},
51 56 ui{new Ui::VisualizationGraphWidget},
52 57 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>()}
53 58 {
54 59 ui->setupUi(this);
55 60
61 // The delegate must be initialized after the ui as it uses the plot
62 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*ui->widget);
63
56 64 ui->graphNameLabel->setText(name);
57 65
58 66 // 'Close' options : widget is deleted when closed
59 67 setAttribute(Qt::WA_DeleteOnClose);
60 68 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationGraphWidget::close);
61 69 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
62 70
63 71 // Set qcpplot properties :
64 72 // - Drag (on x-axis) and zoom are enabled
65 73 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
66 74 ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
67 75 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal);
76
68 77 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
69 78 connect(ui->widget, &QCustomPlot::mouseRelease, this,
70 79 &VisualizationGraphWidget::onMouseRelease);
80 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
71 81 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
72 82 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
73 83 &QCPAxis::rangeChanged),
74 84 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
75 85
76 86 // Activates menu when right clicking on the graph
77 87 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
78 88 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
79 89 &VisualizationGraphWidget::onGraphMenuRequested);
80 90
81 91 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
82 92 &VariableController::onRequestDataLoading);
83 93 }
84 94
85 95
86 96 VisualizationGraphWidget::~VisualizationGraphWidget()
87 97 {
88 98 delete ui;
89 99 }
90 100
91 101 void VisualizationGraphWidget::enableSynchronize(bool enable)
92 102 {
93 103 impl->m_DoSynchronize = enable;
94 104 }
95 105
96 106 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable)
97 107 {
98 108 // Uses delegate to create the qcpplot components according to the variable
99 109 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
100 110
101 111 for (auto createdPlottable : qAsConst(createdPlottables)) {
102 112 impl->m_VariableToPlotMultiMap.insert({variable, createdPlottable});
103 113 }
104 114
105 115 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
106 116 }
107 117 void VisualizationGraphWidget::addVariableUsingGraph(std::shared_ptr<Variable> variable)
108 118 {
109 119
110 120 // when adding a variable, we need to set its time range to the current graph range
111 121 auto grapheRange = ui->widget->xAxis->range();
112 122 auto dateTime = SqpDateTime{grapheRange.lower, grapheRange.upper};
113 123 variable->setDateTime(dateTime);
114 124
115 125 auto variableDateTimeWithTolerance = dateTime;
116 126
117 127 // add tolerance for each side
118 128 auto toleranceFactor
119 129 = toleranceValue(GENERAL_TOLERANCE_AT_INIT_KEY, GENERAL_TOLERANCE_AT_INIT_DEFAULT_VALUE);
120 130 auto tolerance = toleranceFactor * (dateTime.m_TEnd - dateTime.m_TStart);
121 131 variableDateTimeWithTolerance.m_TStart -= tolerance;
122 132 variableDateTimeWithTolerance.m_TEnd += tolerance;
123 133
124 134 // Uses delegate to create the qcpplot components according to the variable
125 135 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
126 136
127 137 for (auto createdPlottable : qAsConst(createdPlottables)) {
128 138 impl->m_VariableToPlotMultiMap.insert({variable, createdPlottable});
129 139 }
130 140
131 141 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
132 142
133 143 // CHangement detected, we need to ask controller to request data loading
134 144 emit requestDataLoading(variable, variableDateTimeWithTolerance);
135 145 }
136 146
137 147 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
138 148 {
139 149 // Each component associated to the variable :
140 150 // - is removed from qcpplot (which deletes it)
141 151 // - is no longer referenced in the map
142 152 auto componentsIt = impl->m_VariableToPlotMultiMap.equal_range(variable);
143 153 for (auto it = componentsIt.first; it != componentsIt.second;) {
144 154 ui->widget->removePlottable(it->second);
145 155 it = impl->m_VariableToPlotMultiMap.erase(it);
146 156 }
147 157
148 158 // Updates graph
149 159 ui->widget->replot();
150 160 }
151 161
152 162 void VisualizationGraphWidget::setRange(std::shared_ptr<Variable> variable,
153 163 const SqpDateTime &range)
154 164 {
155 165 // Note: in case of different axes that depends on variable, we could start with a code like
156 166 // that:
157 167 // auto componentsIt = impl->m_VariableToPlotMultiMap.equal_range(variable);
158 168 // for (auto it = componentsIt.first; it != componentsIt.second;) {
159 169 // }
160 170 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
161 171 ui->widget->replot();
162 172 }
163 173
164 174 SqpDateTime VisualizationGraphWidget::graphRange() const noexcept
165 175 {
166 176 auto grapheRange = ui->widget->xAxis->range();
167 177 return SqpDateTime{grapheRange.lower, grapheRange.upper};
168 178 }
169 179
170 180 void VisualizationGraphWidget::setGraphRange(const SqpDateTime &range)
171 181 {
172 182 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
173 183 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
174 184 ui->widget->replot();
175 185 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
176 186 }
177 187
178 188 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
179 189 {
180 190 if (visitor) {
181 191 visitor->visit(this);
182 192 }
183 193 else {
184 194 qCCritical(LOG_VisualizationGraphWidget())
185 195 << tr("Can't visit widget : the visitor is null");
186 196 }
187 197 }
188 198
189 199 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
190 200 {
191 201 /// @todo : for the moment, a graph can always accomodate a variable
192 202 Q_UNUSED(variable);
193 203 return true;
194 204 }
195 205
196 206 bool VisualizationGraphWidget::contains(const Variable &variable) const
197 207 {
198 208 // Finds the variable among the keys of the map
199 209 auto variablePtr = &variable;
200 210 auto findVariable
201 211 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
202 212
203 213 auto end = impl->m_VariableToPlotMultiMap.cend();
204 214 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
205 215 return it != end;
206 216 }
207 217
208 218 QString VisualizationGraphWidget::name() const
209 219 {
210 220 return ui->graphNameLabel->text();
211 221 }
212 222
213 223 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
214 224 {
215 225 QMenu graphMenu{};
216 226
217 227 // Iterates on variables (unique keys)
218 228 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
219 229 end = impl->m_VariableToPlotMultiMap.cend();
220 230 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
221 231 // 'Remove variable' action
222 232 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
223 233 [ this, var = it->first ]() { removeVariable(var); });
224 234 }
225 235
226 236 if (!graphMenu.isEmpty()) {
227 237 graphMenu.exec(mapToGlobal(pos));
228 238 }
229 239 }
230 240
231 241 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
232 242 {
233 243 qCInfo(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::onRangeChanged")
234 244 << QThread::currentThread()->objectName();
235 245
236 246 auto dateTimeRange = SqpDateTime{t1.lower, t1.upper};
237 247
238 248 auto zoomType = impl->getZoomType(t1, t2);
239 249 for (auto it = impl->m_VariableToPlotMultiMap.cbegin();
240 250 it != impl->m_VariableToPlotMultiMap.cend(); ++it) {
241 251
242 252 auto variable = it->first;
243 253 auto currentDateTime = dateTimeRange;
244 254
245 255 auto toleranceFactor = toleranceValue(GENERAL_TOLERANCE_AT_UPDATE_KEY,
246 256 GENERAL_TOLERANCE_AT_UPDATE_DEFAULT_VALUE);
247 257 auto tolerance = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
248 258 auto variableDateTimeWithTolerance = currentDateTime;
249 259 variableDateTimeWithTolerance.m_TStart -= tolerance;
250 260 variableDateTimeWithTolerance.m_TEnd += tolerance;
251 261
252 262 qCDebug(LOG_VisualizationGraphWidget()) << "r" << currentDateTime;
253 263 qCDebug(LOG_VisualizationGraphWidget()) << "t" << variableDateTimeWithTolerance;
254 264 qCDebug(LOG_VisualizationGraphWidget()) << "v" << variable->dateTime();
255 265 // If new range with tol is upper than variable datetime parameters. we need to request new
256 266 // data
257 267 if (!variable->contains(variableDateTimeWithTolerance)) {
258 268
259 269 auto variableDateTimeWithTolerance = currentDateTime;
260 270 if (!variable->isInside(currentDateTime)) {
261 271 auto variableDateTime = variable->dateTime();
262 272 if (variable->contains(variableDateTimeWithTolerance)) {
263 273 qCDebug(LOG_VisualizationGraphWidget())
264 274 << tr("TORM: Detection zoom in that need request:");
265 275 // add tolerance for each side
266 276 tolerance
267 277 = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
268 278 variableDateTimeWithTolerance.m_TStart -= tolerance;
269 279 variableDateTimeWithTolerance.m_TEnd += tolerance;
270 280 }
271 281 else if (variableDateTime.m_TStart < currentDateTime.m_TStart) {
272 282 qCInfo(LOG_VisualizationGraphWidget()) << tr("TORM: Detection pan to right:");
273 283
274 284 auto diffEndToKeepDelta = currentDateTime.m_TEnd - variableDateTime.m_TEnd;
275 285 currentDateTime.m_TStart = variableDateTime.m_TStart + diffEndToKeepDelta;
276 286 // Tolerance have to be added to the right
277 287 // add tolerance for right (end) side
278 288 tolerance
279 289 = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
280 290 variableDateTimeWithTolerance.m_TEnd += tolerance;
281 291 }
282 292 else if (variableDateTime.m_TEnd > currentDateTime.m_TEnd) {
283 293 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: Detection pan to left: ");
284 294 auto diffStartToKeepDelta
285 295 = variableDateTime.m_TStart - currentDateTime.m_TStart;
286 296 currentDateTime.m_TEnd = variableDateTime.m_TEnd - diffStartToKeepDelta;
287 297 // Tolerance have to be added to the left
288 298 // add tolerance for left (start) side
289 299 tolerance
290 300 = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
291 301 variableDateTimeWithTolerance.m_TStart -= tolerance;
292 302 }
293 303 else {
294 304 qCCritical(LOG_VisualizationGraphWidget())
295 305 << tr("Detection anormal zoom detection: ");
296 306 }
297 307 }
298 308 else {
299 309 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: Detection zoom out: ");
300 310 // add tolerance for each side
301 311 tolerance = toleranceFactor * (currentDateTime.m_TEnd - currentDateTime.m_TStart);
302 312 variableDateTimeWithTolerance.m_TStart -= tolerance;
303 313 variableDateTimeWithTolerance.m_TEnd += tolerance;
304 314 zoomType = VisualizationGraphWidgetZoomType::ZoomOut;
305 315 }
306 316 if (!variable->contains(dateTimeRange)) {
307 317 qCDebug(LOG_VisualizationGraphWidget())
308 318 << "TORM: Modif on variable datetime detected" << currentDateTime;
309 319 variable->setDateTime(currentDateTime);
310 320 }
311 321
312 322 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: Request data detection: ");
313 323 // CHangement detected, we need to ask controller to request data loading
314 324 emit requestDataLoading(variable, variableDateTimeWithTolerance);
315 325 }
316 326 else {
317 327 qCInfo(LOG_VisualizationGraphWidget())
318 328 << tr("TORM: Detection zoom in that doesn't need request: ");
319 329 zoomType = VisualizationGraphWidgetZoomType::ZoomIn;
320 330 }
321 331 }
322 332
323 333 if (impl->m_DoSynchronize && !impl->m_IsCalibration) {
324 334 auto oldDateTime = SqpDateTime{t2.lower, t2.upper};
325 335 qCDebug(LOG_VisualizationGraphWidget())
326 336 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
327 337 << QThread::currentThread()->objectName();
328 338 emit synchronize(dateTimeRange, oldDateTime, zoomType);
329 339 }
330 340 }
331 341
342 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
343 {
344 // Handles plot rendering when mouse is moving
345 impl->m_RenderingDelegate->onMouseMove(event);
346 }
347
332 348 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
333 349 {
334 350 auto zoomOrientations = QFlags<Qt::Orientation>{};
335 351
336 352 // Lambda that enables a zoom orientation if the key modifier related to this orientation
337 353 // has
338 354 // been pressed
339 355 auto enableOrientation
340 356 = [&zoomOrientations, event](const auto &orientation, const auto &modifier) {
341 357 auto orientationEnabled = event->modifiers().testFlag(modifier);
342 358 zoomOrientations.setFlag(orientation, orientationEnabled);
343 359 };
344 360 enableOrientation(Qt::Vertical, VERTICAL_ZOOM_MODIFIER);
345 361 enableOrientation(Qt::Horizontal, HORIZONTAL_ZOOM_MODIFIER);
346 362
347 363 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
348 364 }
349 365
350 366 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
351 367 {
352 368 impl->m_IsCalibration = event->modifiers().testFlag(Qt::ControlModifier);
353 369 }
354 370
355 371 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
356 372 {
357 373 impl->m_IsCalibration = false;
358 374 }
359 375
360 376 void VisualizationGraphWidget::onDataCacheVariableUpdated()
361 377 {
362 378 // NOTE:
363 379 // We don't want to call the method for each component of a variable unitarily, but for
364 380 // all
365 381 // its components at once (eg its three components in the case of a vector).
366 382
367 383 // The unordered_multimap does not do this easily, so the question is whether to:
368 384 // - use an ordered_multimap and the algos of std to group the values by key
369 385 // - use a map (unique keys) and store as values directly the list of components
370 386
371 387 auto grapheRange = ui->widget->xAxis->range();
372 388 auto dateTime = SqpDateTime{grapheRange.lower, grapheRange.upper};
373 389
374 390 for (auto it = impl->m_VariableToPlotMultiMap.cbegin();
375 391 it != impl->m_VariableToPlotMultiMap.cend(); ++it) {
376 392 auto variable = it->first;
377 393 qCDebug(LOG_VisualizationGraphWidget())
378 394 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S"
379 395 << variable->dateTime();
380 396 qCDebug(LOG_VisualizationGraphWidget())
381 397 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
382 398 if (dateTime.contains(variable->dateTime()) || dateTime.intersect(variable->dateTime())) {
383 399
384 400 VisualizationGraphHelper::updateData(QVector<QCPAbstractPlottable *>{} << it->second,
385 401 variable->dataSeries(), variable->dateTime());
386 402 }
387 403 }
388 404 }
389 405
390 406 VisualizationGraphWidgetZoomType
391 407 VisualizationGraphWidget::VisualizationGraphWidgetPrivate::getZoomType(const QCPRange &t1,
392 408 const QCPRange &t2)
393 409 {
394 410 // t1.lower <= t2.lower && t2.upper <= t1.upper
395 411 auto zoomType = VisualizationGraphWidgetZoomType::Unknown;
396 412 if (t1.lower <= t2.lower && t2.upper <= t1.upper) {
397 413 zoomType = VisualizationGraphWidgetZoomType::ZoomOut;
398 414 }
399 415 else if (t1.lower > t2.lower && t1.upper > t2.upper) {
400 416 zoomType = VisualizationGraphWidgetZoomType::PanRight;
401 417 }
402 418 else if (t1.lower < t2.lower && t1.upper < t2.upper) {
403 419 zoomType = VisualizationGraphWidgetZoomType::PanLeft;
404 420 }
405 421 else if (t1.lower > t2.lower && t2.upper > t1.upper) {
406 422 zoomType = VisualizationGraphWidgetZoomType::ZoomIn;
407 423 }
408 424 else {
409 425 qCCritical(LOG_VisualizationGraphWidget()) << "getZoomType: Unknown type detected";
410 426 }
411 427 return zoomType;
412 428 }
@@ -1,92 +1,98
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <ui version="4.0">
3 3 <class>TimeWidget</class>
4 4 <widget class="QWidget" name="TimeWidget">
5 5 <property name="geometry">
6 6 <rect>
7 7 <x>0</x>
8 8 <y>0</y>
9 9 <width>716</width>
10 10 <height>48</height>
11 11 </rect>
12 12 </property>
13 13 <property name="sizePolicy">
14 14 <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
15 15 <horstretch>0</horstretch>
16 16 <verstretch>0</verstretch>
17 17 </sizepolicy>
18 18 </property>
19 19 <property name="windowTitle">
20 20 <string>Form</string>
21 21 </property>
22 22 <layout class="QHBoxLayout" name="horizontalLayout_2">
23 23 <item>
24 24 <widget class="QLabel" name="label">
25 25 <property name="sizePolicy">
26 26 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
27 27 <horstretch>0</horstretch>
28 28 <verstretch>0</verstretch>
29 29 </sizepolicy>
30 30 </property>
31 31 <property name="text">
32 32 <string>TStart :</string>
33 33 </property>
34 34 </widget>
35 35 </item>
36 36 <item>
37 37 <widget class="QDateTimeEdit" name="startDateTimeEdit">
38 38 <property name="sizePolicy">
39 39 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
40 40 <horstretch>0</horstretch>
41 41 <verstretch>0</verstretch>
42 42 </sizepolicy>
43 43 </property>
44 44 <property name="displayFormat">
45 45 <string>dd/MM/yyyy HH:mm:ss:zzz</string>
46 46 </property>
47 47 <property name="calendarPopup">
48 48 <bool>true</bool>
49 49 </property>
50 <property name="timeSpec">
51 <enum>Qt::UTC</enum>
52 </property>
50 53 </widget>
51 54 </item>
52 55 <item>
53 56 <widget class="QLabel" name="label_2">
54 57 <property name="sizePolicy">
55 58 <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
56 59 <horstretch>0</horstretch>
57 60 <verstretch>0</verstretch>
58 61 </sizepolicy>
59 62 </property>
60 63 <property name="text">
61 64 <string>TEnd :</string>
62 65 </property>
63 66 </widget>
64 67 </item>
65 68 <item>
66 69 <widget class="QDateTimeEdit" name="endDateTimeEdit">
67 70 <property name="sizePolicy">
68 71 <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
69 72 <horstretch>0</horstretch>
70 73 <verstretch>0</verstretch>
71 74 </sizepolicy>
72 75 </property>
73 76 <property name="displayFormat">
74 77 <string>dd/MM/yyyy HH:mm:ss:zzz</string>
75 78 </property>
76 79 <property name="calendarPopup">
77 80 <bool>true</bool>
78 81 </property>
82 <property name="timeSpec">
83 <enum>Qt::UTC</enum>
84 </property>
79 85 </widget>
80 86 </item>
81 87 <item>
82 88 <widget class="QToolButton" name="applyToolButton">
83 89 <property name="text">
84 90 <string>...</string>
85 91 </property>
86 92 </widget>
87 93 </item>
88 94 </layout>
89 95 </widget>
90 96 <resources/>
91 97 <connections/>
92 98 </ui>
@@ -1,147 +1,148
1 1 #include "AmdaProvider.h"
2 2 #include "AmdaDefs.h"
3 3 #include "AmdaResultParser.h"
4 4
5 #include <Common/DateUtils.h>
5 6 #include <Data/DataProviderParameters.h>
6 7 #include <Network/NetworkController.h>
7 8 #include <SqpApplication.h>
8 9 #include <Variable/Variable.h>
9 10
10 11 #include <QNetworkAccessManager>
11 12 #include <QNetworkReply>
12 13 #include <QTemporaryFile>
13 14 #include <QThread>
14 15
15 16 Q_LOGGING_CATEGORY(LOG_AmdaProvider, "AmdaProvider")
16 17
17 18 namespace {
18 19
19 20 /// URL format for a request on AMDA server. The parameters are as follows:
20 21 /// - %1: start date
21 22 /// - %2: end date
22 23 /// - %3: parameter id
23 24 const auto AMDA_URL_FORMAT = QStringLiteral(
24 25 "http://amda.irap.omp.eu/php/rest/"
25 26 "getParameter.php?startTime=%1&stopTime=%2&parameterID=%3&outputFormat=ASCII&"
26 27 "timeFormat=ISO8601&gzip=0");
27 28
28 29 /// Dates format passed in the URL (e.g 2013-09-23T09:00)
29 30 const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss");
30 31
31 32 /// Formats a time to a date that can be passed in URL
32 33 QString dateFormat(double sqpDateTime) noexcept
33 34 {
34 auto dateTime = QDateTime::fromMSecsSinceEpoch(sqpDateTime * 1000.);
35 auto dateTime = DateUtils::dateTime(sqpDateTime);
35 36 return dateTime.toString(AMDA_TIME_FORMAT);
36 37 }
37 38
38 39 } // namespace
39 40
40 41 AmdaProvider::AmdaProvider()
41 42 {
42 43 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::AmdaProvider") << QThread::currentThread();
43 44 if (auto app = sqpApp) {
44 45 auto &networkController = app->networkController();
45 46 connect(this, SIGNAL(requestConstructed(QNetworkRequest, QUuid,
46 47 std::function<void(QNetworkReply *, QUuid)>)),
47 48 &networkController,
48 49 SLOT(onProcessRequested(QNetworkRequest, QUuid,
49 50 std::function<void(QNetworkReply *, QUuid)>)));
50 51
51 52
52 53 connect(&sqpApp->networkController(), SIGNAL(replyDownloadProgress(QUuid, double)), this,
53 54 SIGNAL(dataProvidedProgress(QUuid, double)));
54 55 }
55 56 }
56 57
57 58 void AmdaProvider::requestDataLoading(QUuid token, const DataProviderParameters &parameters)
58 59 {
59 60 // NOTE: Try to use multithread if possible
60 61 const auto times = parameters.m_Times;
61 62 const auto data = parameters.m_Data;
62 63 for (const auto &dateTime : qAsConst(times)) {
63 64 retrieveData(token, dateTime, data);
64 65 }
65 66 }
66 67
67 68 void AmdaProvider::requestDataAborting(QUuid identifier)
68 69 {
69 70 if (auto app = sqpApp) {
70 71 auto &networkController = app->networkController();
71 72 networkController.onReplyCanceled(identifier);
72 73 }
73 74 }
74 75
75 76 void AmdaProvider::retrieveData(QUuid token, const SqpDateTime &dateTime, const QVariantHash &data)
76 77 {
77 78 // Retrieves product ID from data: if the value is invalid, no request is made
78 79 auto productId = data.value(AMDA_XML_ID_KEY).toString();
79 80 if (productId.isNull()) {
80 81 qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve data: unknown product id");
81 82 return;
82 83 }
83 84 qCInfo(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData") << dateTime;
84 85
85 86 // /////////// //
86 87 // Creates URL //
87 88 // /////////// //
88 89
89 90 auto startDate = dateFormat(dateTime.m_TStart);
90 91 auto endDate = dateFormat(dateTime.m_TEnd);
91 92
92 93 auto url = QUrl{QString{AMDA_URL_FORMAT}.arg(startDate, endDate, productId)};
93 94 qCInfo(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData url:") << url;
94 95 auto tempFile = std::make_shared<QTemporaryFile>();
95 96
96 97 // LAMBDA
97 98 auto httpDownloadFinished
98 99 = [this, dateTime, tempFile, token](QNetworkReply *reply, QUuid dataId) noexcept {
99 100 Q_UNUSED(dataId);
100 101
101 102 // Don't do anything if the reply was abort
102 103 if (reply->error() != QNetworkReply::OperationCanceledError) {
103 104
104 105 if (tempFile) {
105 106 auto replyReadAll = reply->readAll();
106 107 if (!replyReadAll.isEmpty()) {
107 108 tempFile->write(replyReadAll);
108 109 }
109 110 tempFile->close();
110 111
111 112 // Parse results file
112 113 if (auto dataSeries = AmdaResultParser::readTxt(tempFile->fileName())) {
113 114 emit dataProvided(token, dataSeries, dateTime);
114 115 }
115 116 else {
116 117 /// @todo ALX : debug
117 118 }
118 119 }
119 120 }
120 121
121 122 };
122 123 auto httpFinishedLambda
123 124 = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
124 125
125 126 // Don't do anything if the reply was abort
126 127 if (reply->error() != QNetworkReply::OperationCanceledError) {
127 128 auto downloadFileUrl = QUrl{QString{reply->readAll()}};
128 129
129 130
130 131 qCInfo(LOG_AmdaProvider()) << tr("AmdaProvider::retrieveData downloadFileUrl:")
131 132 << downloadFileUrl;
132 133 // Executes request for downloading file //
133 134
134 135 // Creates destination file
135 136 if (tempFile->open()) {
136 137 // Executes request
137 138 emit requestConstructed(QNetworkRequest{downloadFileUrl}, dataId,
138 139 httpDownloadFinished);
139 140 }
140 141 }
141 142 };
142 143
143 144 // //////////////// //
144 145 // Executes request //
145 146 // //////////////// //
146 147 emit requestConstructed(QNetworkRequest{url}, token, httpFinishedLambda);
147 148 }
@@ -1,145 +1,152
1 1 #include "AmdaResultParser.h"
2 2
3 #include <Common/DateUtils.h>
3 4 #include <Data/ScalarSeries.h>
4 5
5 6 #include <QDateTime>
6 7 #include <QFile>
7 8 #include <QRegularExpression>
8 9
9 10 #include <cmath>
10 11
11 12 Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser")
12 13
13 14 namespace {
14 15
15 16 /// Message in result file when the file was not found on server
16 17 const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found");
17 18
18 19 /// Format for dates in result files
19 20 const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz");
20 21
21 22 /// Separator between values in a result line
22 23 const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")};
23 24
24 25 /// Regex to find unit in a line. Examples of valid lines:
25 26 /// ... - Units : nT - ...
26 27 /// ... -Units:nT- ...
27 28 /// ... -Units: mΒ²- ...
28 29 /// ... - Units : m/s - ...
29 30 const auto UNIT_REGEX = QRegularExpression{QStringLiteral("-\\s*Units\\s*:\\s*(.+?)\\s*-")};
30 31
31 32 /// Converts a string date to a double date
32 33 /// @return a double that represents the date in seconds, NaN if the string date can't be converted
33 34 double doubleDate(const QString &stringDate) noexcept
34 35 {
35 36 auto dateTime = QDateTime::fromString(stringDate, DATE_FORMAT);
36 return dateTime.isValid() ? (dateTime.toMSecsSinceEpoch() / 1000.)
37 dateTime.setTimeSpec(Qt::UTC);
38 return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime)
37 39 : std::numeric_limits<double>::quiet_NaN();
38 40 }
39 41
42 /// Checks if a line is a comment line
43 bool isCommentLine(const QString &line)
44 {
45 return line.startsWith("#");
46 }
47
40 48 /**
41 49 * Reads stream to retrieve x-axis unit
42 50 * @param stream the stream to read
43 51 * @return the unit that has been read in the stream, a default unit (time unit with no label) if an
44 52 * error occured during reading
45 53 */
46 54 Unit readXAxisUnit(QTextStream &stream)
47 55 {
48 56 QString line{};
49 57
50 if (stream.readLineInto(&line)) {
58 // Searches unit in the comment lines
59 while (stream.readLineInto(&line) && isCommentLine(line)) {
51 60 auto match = UNIT_REGEX.match(line);
52 61 if (match.hasMatch()) {
53 62 return Unit{match.captured(1), true};
54 63 }
55 else {
56 qCWarning(LOG_AmdaResultParser())
57 << QObject::tr("Can't read unit: invalid line %1").arg(line);
58 }
59 }
60 else {
61 qCWarning(LOG_AmdaResultParser()) << QObject::tr("Can't read unit: end of file");
62 64 }
63 65
66 qCWarning(LOG_AmdaResultParser()) << QObject::tr("The unit could not be found in the file");
67
64 68 // Error cases
65 69 return Unit{{}, true};
66 70 }
67 71
68 72 /**
69 73 * Reads stream to retrieve results
70 74 * @param stream the stream to read
71 75 * @return the pair of vectors x-axis data/values data that has been read in the stream
72 76 */
73 77 QPair<QVector<double>, QVector<double> > readResults(QTextStream &stream)
74 78 {
75 79 auto xData = QVector<double>{};
76 80 auto valuesData = QVector<double>{};
77 81
78 82 QString line{};
83
79 84 while (stream.readLineInto(&line)) {
80 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
81 if (lineData.size() == 2) {
82 // X : the data is converted from date to double (in secs)
83 auto x = doubleDate(lineData.at(0));
84
85 // Value
86 bool valueOk;
87 auto value = lineData.at(1).toDouble(&valueOk);
88
89 // Adds result only if x and value are valid
90 if (!std::isnan(x) && !std::isnan(value) && valueOk) {
91 xData.push_back(x);
92 valuesData.push_back(value);
85 // Ignore comment lines
86 if (!isCommentLine(line)) {
87 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
88 if (lineData.size() == 2) {
89 // X : the data is converted from date to double (in secs)
90 auto x = doubleDate(lineData.at(0));
91
92 // Value
93 bool valueOk;
94 auto value = lineData.at(1).toDouble(&valueOk);
95
96 // Adds result only if x and value are valid
97 if (!std::isnan(x) && !std::isnan(value) && valueOk) {
98 xData.push_back(x);
99 valuesData.push_back(value);
100 }
101 else {
102 qCWarning(LOG_AmdaResultParser())
103 << QObject::tr(
104 "Can't retrieve results from line %1: x and/or value are invalid")
105 .arg(line);
106 }
93 107 }
94 108 else {
95 109 qCWarning(LOG_AmdaResultParser())
96 << QObject::tr(
97 "Can't retrieve results from line %1: x and/or value are invalid")
98 .arg(line);
110 << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line);
99 111 }
100 112 }
101 else {
102 qCWarning(LOG_AmdaResultParser())
103 << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line);
104 }
105 113 }
106 114
107 115 return qMakePair(std::move(xData), std::move(valuesData));
108 116 }
109 117
110 118 } // namespace
111 119
112 120 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath) noexcept
113 121 {
114 122 QFile file{filePath};
115 123
116 124 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
117 125 qCCritical(LOG_AmdaResultParser())
118 126 << QObject::tr("Can't retrieve AMDA data from file %1: %2")
119 127 .arg(filePath, file.errorString());
120 128 return nullptr;
121 129 }
122 130
123 131 QTextStream stream{&file};
124 132
125 133 // Checks if the file was found on the server
126 134 auto firstLine = stream.readLine();
127 135 if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0) {
128 136 qCCritical(LOG_AmdaResultParser())
129 137 << QObject::tr("Can't retrieve AMDA data from file %1: file was not found on server")
130 138 .arg(filePath);
131 139 return nullptr;
132 140 }
133 141
134 // Ignore comments lines
135 stream.readLine();
136
137 142 // Reads x-axis unit
143 stream.seek(0); // returns to the beginning of the file
138 144 auto xAxisUnit = readXAxisUnit(stream);
139 145
140 146 // Reads results
147 stream.seek(0); // returns to the beginning of the file
141 148 auto results = readResults(stream);
142 149
143 150 return std::make_shared<ScalarSeries>(std::move(results.first), std::move(results.second),
144 151 xAxisUnit, Unit{});
145 152 }
@@ -1,182 +1,182
1 1 #include "AmdaResultParser.h"
2 2
3 3 #include <Data/ScalarSeries.h>
4 4
5 5 #include <QObject>
6 6 #include <QtTest>
7 7
8 8 namespace {
9 9
10 10 /// Path for the tests
11 11 const auto TESTS_RESOURCES_PATH
12 12 = QFileInfo{QString{AMDA_TESTS_RESOURCES_DIR}, "TestAmdaResultParser"}.absoluteFilePath();
13 13
14 14 QString inputFilePath(const QString &inputFileName)
15 15 {
16 16 return QFileInfo{TESTS_RESOURCES_PATH, inputFileName}.absoluteFilePath();
17 17 }
18 18
19 19 struct ExpectedResults {
20 20 explicit ExpectedResults() = default;
21 21
22 22 /// Ctor with QVector<QDateTime> as x-axis data. Datetimes are converted to doubles
23 23 explicit ExpectedResults(Unit xAxisUnit, Unit valuesUnit, const QVector<QDateTime> &xAxisData,
24 24 QVector<double> valuesData)
25 25 : m_ParsingOK{true},
26 26 m_XAxisUnit{xAxisUnit},
27 27 m_ValuesUnit{valuesUnit},
28 28 m_XAxisData{},
29 29 m_ValuesData{std::move(valuesData)}
30 30 {
31 31 // Converts QVector<QDateTime> to QVector<double>
32 32 std::transform(xAxisData.cbegin(), xAxisData.cend(), std::back_inserter(m_XAxisData),
33 33 [](const auto &dateTime) { return dateTime.toMSecsSinceEpoch() / 1000.; });
34 34 }
35 35
36 36 /**
37 37 * Validates a DataSeries compared to the expected results
38 38 * @param results the DataSeries to validate
39 39 */
40 40 void validate(std::shared_ptr<IDataSeries> results)
41 41 {
42 42 if (m_ParsingOK) {
43 43 auto scalarSeries = dynamic_cast<ScalarSeries *>(results.get());
44 44 QVERIFY(scalarSeries != nullptr);
45 45
46 46 // Checks units
47 47 QVERIFY(scalarSeries->xAxisUnit() == m_XAxisUnit);
48 48 QVERIFY(scalarSeries->valuesUnit() == m_ValuesUnit);
49 49
50 50 // Checks values
51 51 QVERIFY(scalarSeries->xAxisData()->data() == m_XAxisData);
52 52 QVERIFY(scalarSeries->valuesData()->data() == m_ValuesData);
53 53 }
54 54 else {
55 55 QVERIFY(results == nullptr);
56 56 }
57 57 }
58 58
59 59 // Parsing was successfully completed
60 60 bool m_ParsingOK{false};
61 61 // Expected x-axis unit
62 62 Unit m_XAxisUnit{};
63 63 // Expected values unit
64 64 Unit m_ValuesUnit{};
65 65 // Expected x-axis data
66 66 QVector<double> m_XAxisData{};
67 67 // Expected values data
68 68 QVector<double> m_ValuesData{};
69 69 };
70 70
71 71 } // namespace
72 72
73 73 Q_DECLARE_METATYPE(ExpectedResults)
74 74
75 75 class TestAmdaResultParser : public QObject {
76 76 Q_OBJECT
77 77 private slots:
78 78 /// Input test data
79 79 /// @sa testTxtJson()
80 80 void testReadTxt_data();
81 81
82 82 /// Tests parsing of a TXT file
83 83 void testReadTxt();
84 84 };
85 85
86 86 void TestAmdaResultParser::testReadTxt_data()
87 87 {
88 88 // ////////////// //
89 89 // Test structure //
90 90 // ////////////// //
91 91
92 92 // Name of TXT file to read
93 93 QTest::addColumn<QString>("inputFileName");
94 94 // Expected results
95 95 QTest::addColumn<ExpectedResults>("expectedResults");
96 96
97 97 // ////////// //
98 98 // Test cases //
99 99 // ////////// //
100 100
101 101 auto dateTime = [](int year, int month, int day, int hours, int minutes, int seconds) {
102 return QDateTime{{year, month, day}, {hours, minutes, seconds}};
102 return QDateTime{{year, month, day}, {hours, minutes, seconds}, Qt::UTC};
103 103 };
104 104
105 105 // Valid file
106 106 QTest::newRow("Valid file")
107 107 << QStringLiteral("ValidScalar1.txt")
108 108 << ExpectedResults{
109 109 Unit{QStringLiteral("nT"), true}, Unit{},
110 110 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30), dateTime(2013, 9, 23, 9, 1, 30),
111 111 dateTime(2013, 9, 23, 9, 2, 30), dateTime(2013, 9, 23, 9, 3, 30),
112 112 dateTime(2013, 9, 23, 9, 4, 30), dateTime(2013, 9, 23, 9, 5, 30),
113 113 dateTime(2013, 9, 23, 9, 6, 30), dateTime(2013, 9, 23, 9, 7, 30),
114 114 dateTime(2013, 9, 23, 9, 8, 30), dateTime(2013, 9, 23, 9, 9, 30)},
115 115 QVector<double>{-2.83950, -2.71850, -2.52150, -2.57633, -2.58050, -2.48325, -2.63025,
116 116 -2.55800, -2.43250, -2.42200}};
117 117
118 118 // Valid files but with some invalid lines (wrong unit, wrong values, etc.)
119 119 QTest::newRow("No unit file") << QStringLiteral("NoUnit.txt")
120 120 << ExpectedResults{Unit{QStringLiteral(""), true}, Unit{},
121 121 QVector<QDateTime>{}, QVector<double>{}};
122 122 QTest::newRow("Wrong unit file")
123 123 << QStringLiteral("WrongUnit.txt")
124 124 << ExpectedResults{Unit{QStringLiteral(""), true}, Unit{},
125 125 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 0, 30),
126 126 dateTime(2013, 9, 23, 9, 1, 30),
127 127 dateTime(2013, 9, 23, 9, 2, 30)},
128 128 QVector<double>{-2.83950, -2.71850, -2.52150}};
129 129
130 130 QTest::newRow("Wrong results file (date of first line is invalid")
131 131 << QStringLiteral("WrongDate.txt")
132 132 << ExpectedResults{
133 133 Unit{QStringLiteral("nT"), true}, Unit{},
134 134 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
135 135 QVector<double>{-2.71850, -2.52150}};
136 136
137 137 QTest::newRow("Wrong results file (too many values for first line")
138 138 << QStringLiteral("TooManyValues.txt")
139 139 << ExpectedResults{
140 140 Unit{QStringLiteral("nT"), true}, Unit{},
141 141 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
142 142 QVector<double>{-2.71850, -2.52150}};
143 143
144 144 QTest::newRow("Wrong results file (value of first line is invalid")
145 145 << QStringLiteral("WrongValue.txt")
146 146 << ExpectedResults{
147 147 Unit{QStringLiteral("nT"), true}, Unit{},
148 148 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
149 149 QVector<double>{-2.71850, -2.52150}};
150 150
151 151 QTest::newRow("Wrong results file (value of first line is NaN")
152 152 << QStringLiteral("NaNValue.txt")
153 153 << ExpectedResults{
154 154 Unit{QStringLiteral("nT"), true}, Unit{},
155 155 QVector<QDateTime>{dateTime(2013, 9, 23, 9, 1, 30), dateTime(2013, 9, 23, 9, 2, 30)},
156 156 QVector<double>{-2.71850, -2.52150}};
157 157
158 158 // Invalid files
159 159 QTest::newRow("Invalid file (unexisting file)") << QStringLiteral("UnexistingFile.txt")
160 160 << ExpectedResults{};
161 161
162 162 QTest::newRow("Invalid file (file not found on server)") << QStringLiteral("FileNotFound.txt")
163 163 << ExpectedResults{};
164 164 }
165 165
166 166 void TestAmdaResultParser::testReadTxt()
167 167 {
168 168 QFETCH(QString, inputFileName);
169 169 QFETCH(ExpectedResults, expectedResults);
170 170
171 171 // Parses file
172 172 auto filePath = inputFilePath(inputFileName);
173 173 auto results = AmdaResultParser::readTxt(filePath);
174 174
175 175 // ///////////////// //
176 176 // Validates results //
177 177 // ///////////////// //
178 178 expectedResults.validate(results);
179 179 }
180 180
181 181 QTEST_MAIN(TestAmdaResultParser)
182 182 #include "TestAmdaResultParser.moc"
@@ -1,101 +1,100
1 1 #include "CosinusProvider.h"
2 2
3 3 #include <Data/DataProviderParameters.h>
4 4 #include <Data/ScalarSeries.h>
5 5
6 6 #include <cmath>
7 7
8 #include <QDateTime>
9 8 #include <QFuture>
10 9 #include <QThread>
11 10 #include <QtConcurrent/QtConcurrent>
12 11
13 12 Q_LOGGING_CATEGORY(LOG_CosinusProvider, "CosinusProvider")
14 13
15 14 std::shared_ptr<IDataSeries> CosinusProvider::retrieveData(QUuid token, const SqpDateTime &dateTime)
16 15 {
17 16 // TODO: Add Mutex
18 17 auto dataIndex = 0;
19 18
20 19 // Gets the timerange from the parameters
21 20 double freq = 100.0;
22 21 double start = std::ceil(dateTime.m_TStart * freq); // 100 htz
23 22 double end = std::floor(dateTime.m_TEnd * freq); // 100 htz
24 23
25 24 // We assure that timerange is valid
26 25 if (end < start) {
27 26 std::swap(start, end);
28 27 }
29 28
30 29 // Generates scalar series containing cosinus values (one value per second)
31 30 auto dataCount = end - start;
32 31
33 32 auto xAxisData = QVector<double>{};
34 33 xAxisData.resize(dataCount);
35 34
36 35 auto valuesData = QVector<double>{};
37 36 valuesData.resize(dataCount);
38 37
39 38 int progress = 0;
40 39 auto progressEnd = dataCount;
41 40 for (auto time = start; time < end; ++time, ++dataIndex) {
42 41 auto it = m_VariableToEnableProvider.find(token);
43 42 if (it != m_VariableToEnableProvider.end() && it.value()) {
44 43 const auto timeOnFreq = time / freq;
45 44
46 45 xAxisData.replace(dataIndex, timeOnFreq);
47 46 valuesData.replace(dataIndex, std::cos(timeOnFreq));
48 47
49 48 // progression
50 49 int currentProgress = (time - start) * 100.0 / progressEnd;
51 50 if (currentProgress != progress) {
52 51 progress = currentProgress;
53 52
54 53 emit dataProvidedProgress(token, progress);
55 54 }
56 55 }
57 56 else {
58 57 if (!it.value()) {
59 58 qCDebug(LOG_CosinusProvider())
60 59 << "CosinusProvider::retrieveData: ARRET De l'acquisition detectΓ©"
61 60 << end - time;
62 61 }
63 62 }
64 63 }
65 64 emit dataProvidedProgress(token, 0.0);
66 65
67 66 return std::make_shared<ScalarSeries>(std::move(xAxisData), std::move(valuesData),
68 67 Unit{QStringLiteral("t"), true}, Unit{});
69 68 }
70 69
71 70 void CosinusProvider::requestDataLoading(QUuid token, const DataProviderParameters &parameters)
72 71 {
73 72 // TODO: Add Mutex
74 73 m_VariableToEnableProvider[token] = true;
75 74 qCDebug(LOG_CosinusProvider()) << "CosinusProvider::requestDataLoading"
76 75 << QThread::currentThread()->objectName();
77 76 // NOTE: Try to use multithread if possible
78 77 const auto times = parameters.m_Times;
79 78
80 79 for (const auto &dateTime : qAsConst(times)) {
81 80 if (m_VariableToEnableProvider[token]) {
82 81 auto scalarSeries = this->retrieveData(token, dateTime);
83 82 emit dataProvided(token, scalarSeries, dateTime);
84 83 }
85 84 }
86 85 }
87 86
88 87 void CosinusProvider::requestDataAborting(QUuid identifier)
89 88 {
90 89 // TODO: Add Mutex
91 90 qCDebug(LOG_CosinusProvider()) << "CosinusProvider::requestDataAborting" << identifier
92 91 << QThread::currentThread()->objectName();
93 92 auto it = m_VariableToEnableProvider.find(identifier);
94 93 if (it != m_VariableToEnableProvider.end()) {
95 94 it.value() = false;
96 95 }
97 96 else {
98 97 qCWarning(LOG_CosinusProvider())
99 98 << tr("Aborting progression of inexistant identifier detected !!!");
100 99 }
101 100 }
General Comments 0
You need to be logged in to leave comments. Login now