##// END OF EJS Templates
Removed old IDataProvider IF, some small steps toward new VC integration...
jeandet -
r1351:d755f1f0a484
parent child
Show More
@@ -1,1 +1,1
1 Subproject commit c08d1b8ad2976824def52cf0fca26a72b1069356
1 Subproject commit a05b0ab234936e28b6ba79535ccaee297d83a1e8
@@ -1,158 +1,157
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 DateTimeRange;
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, DateTimeRange 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 std::vector<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 DateTimeRange graphRange() const noexcept;
72 72 void setGraphRange(const DateTimeRange &range, bool calibration = false);
73 73 void setAutoRangeOnVariableInitialization(bool value);
74 74
75 75 // Zones
76 76 /// Returns the ranges of all the selection zones on the graph
77 77 QVector<DateTimeRange> selectionZoneRanges() const;
78 78 /// Adds new selection zones in the graph
79 79 void addSelectionZones(const QVector<DateTimeRange> &ranges);
80 80 /// Adds a new selection zone in the graph
81 81 VisualizationSelectionZoneItem *addSelectionZone(const QString &name, const DateTimeRange &range);
82 82 /// Removes the specified selection zone
83 83 void removeSelectionZone(VisualizationSelectionZoneItem *selectionZone);
84 84
85 85 /// Undo the last zoom done with a zoom box
86 86 void undoZoom();
87 87
88 88 // IVisualizationWidget interface
89 89 void accept(IVisualizationWidgetVisitor *visitor) override;
90 90 bool canDrop(const Variable &variable) const override;
91 91 bool contains(const Variable &variable) const override;
92 92 QString name() const override;
93 93
94 94 // VisualisationDragWidget
95 95 QMimeData *mimeData(const QPoint &position) const override;
96 96 QPixmap customDragPixmap(const QPoint &dragPosition) override;
97 97 bool isDragAllowed() const override;
98 98 void highlightForMerge(bool highlighted) override;
99 99
100 100 // Cursors
101 101 /// Adds or moves the vertical cursor at the specified value on the x-axis
102 102 void addVerticalCursor(double time);
103 103 /// Adds or moves the vertical cursor at the specified value on the x-axis
104 104 void addVerticalCursorAtViewportPosition(double position);
105 105 void removeVerticalCursor();
106 106 /// Adds or moves the vertical cursor at the specified value on the y-axis
107 107 void addHorizontalCursor(double value);
108 108 /// Adds or moves the vertical cursor at the specified value on the y-axis
109 109 void addHorizontalCursorAtViewportPosition(double position);
110 110 void removeHorizontalCursor();
111 111
112 112 signals:
113 113 void synchronize(const DateTimeRange &range, const DateTimeRange &oldRange);
114 void requestDataLoading(QVector<std::shared_ptr<Variable> > variable, const DateTimeRange &range,
115 bool synchronise);
114 void changeRange(const std::shared_ptr<Variable>& variable, const DateTimeRange &range);
116 115
117 116 /// Signal emitted when the variable is about to be removed from the graph
118 117 void variableAboutToBeRemoved(std::shared_ptr<Variable> var);
119 118 /// Signal emitted when the variable has been added to the graph
120 119 void variableAdded(std::shared_ptr<Variable> var);
121 120
122 121 protected:
123 122 void closeEvent(QCloseEvent *event) override;
124 123 void enterEvent(QEvent *event) override;
125 124 void leaveEvent(QEvent *event) override;
126 125
127 126 QCustomPlot &plot() const noexcept;
128 127
129 128 private:
130 129 Ui::VisualizationGraphWidget *ui;
131 130
132 131 class VisualizationGraphWidgetPrivate;
133 132 spimpl::unique_impl_ptr<VisualizationGraphWidgetPrivate> impl;
134 133
135 134 private slots:
136 135 /// Slot called when right clicking on the graph (displays a menu)
137 136 void onGraphMenuRequested(const QPoint &pos) noexcept;
138 137
139 138 /// Rescale the X axe to range parameter
140 139 void onRangeChanged(const QCPRange &t1, const QCPRange &t2);
141 140
142 141 /// Slot called when a mouse double click was made
143 142 void onMouseDoubleClick(QMouseEvent *event) noexcept;
144 143 /// Slot called when a mouse move was made
145 144 void onMouseMove(QMouseEvent *event) noexcept;
146 145 /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done
147 146 void onMouseWheel(QWheelEvent *event) noexcept;
148 147 /// Slot called when a mouse press was made, to activate the calibration of a graph
149 148 void onMousePress(QMouseEvent *event) noexcept;
150 149 /// Slot called when a mouse release was made, to deactivate the calibration of a graph
151 150 void onMouseRelease(QMouseEvent *event) noexcept;
152 151
153 152 void onDataCacheVariableUpdated();
154 153
155 154 void onUpdateVarDisplaying(std::shared_ptr<Variable> variable, const DateTimeRange &range);
156 155 };
157 156
158 157 #endif // SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
@@ -1,199 +1,196
1 1 #include "SqpApplication.h"
2 2
3 3 #include <Actions/ActionsGuiController.h>
4 4 #include <Catalogue/CatalogueController.h>
5 5 #include <Data/IDataProvider.h>
6 6 #include <DataSource/DataSourceController.h>
7 7 #include <DragAndDrop/DragDropGuiController.h>
8 8 #include <Network/NetworkController.h>
9 9 #include <QThread>
10 10 #include <Time/TimeController.h>
11 11 #include <Variable/Variable.h>
12 12 #include <Variable/VariableController2.h>
13 13 #include <Variable/VariableModel2.h>
14 14 #include <Visualization/VisualizationController.h>
15 15
16 16 Q_LOGGING_CATEGORY(LOG_SqpApplication, "SqpApplication")
17 17
18 18 class SqpApplication::SqpApplicationPrivate {
19 19 public:
20 20 SqpApplicationPrivate()
21 21 : m_VariableController{std::make_shared<VariableController2>()},
22 22 m_PlotInterractionMode(SqpApplication::PlotsInteractionMode::None),
23 23 m_PlotCursorMode(SqpApplication::PlotsCursorMode::NoCursor)
24 24 {
25 25 // /////////////////////////////// //
26 26 // Connections between controllers //
27 27 // /////////////////////////////// //
28 28
29 29 // VariableController <-> DataSourceController
30 30 connect(&m_DataSourceController,
31 31 &DataSourceController::createVariable,[](const QString &variableName,
32 32 const QVariantHash &variableMetadata,
33 33 std::shared_ptr<IDataProvider> variableProvider)
34 34 {
35 35 sqpApp->variableController().createVariable(variableName,variableMetadata,variableProvider,sqpApp->timeController().dateTime());
36 36 });
37 37
38 // connect(m_VariableController->variableModel(), &VariableModel::requestVariable,
39 // m_DataSourceController.get(), &DataSourceController::requestVariable);
40
41 38 // VariableController <-> VisualizationController
42 39 // connect(m_VariableController.get(),
43 40 // SIGNAL(variableAboutToBeDeleted(std::shared_ptr<Variable>)),
44 41 // m_VisualizationController.get(),
45 42 // SIGNAL(variableAboutToBeDeleted(std::shared_ptr<Variable>)), Qt::DirectConnection);
46 43
47 44 // connect(m_VariableController.get(),
48 45 // SIGNAL(rangeChanged(std::shared_ptr<Variable>, const DateTimeRange &)),
49 46 // m_VisualizationController.get(),
50 47 // SIGNAL(rangeChanged(std::shared_ptr<Variable>, const DateTimeRange &)));
51 48
52 49
53 50 m_DataSourceController.moveToThread(&m_DataSourceControllerThread);
54 51 m_DataSourceControllerThread.setObjectName("DataSourceControllerThread");
55 52 m_NetworkController.moveToThread(&m_NetworkControllerThread);
56 53 m_NetworkControllerThread.setObjectName("NetworkControllerThread");
57 54 m_VisualizationController.moveToThread(&m_VisualizationControllerThread);
58 55 m_VisualizationControllerThread.setObjectName("VsualizationControllerThread");
59 56
60 57 // Additionnal init
61 58 //m_VariableController->setTimeController(m_TimeController.get());
62 59 }
63 60
64 61 virtual ~SqpApplicationPrivate()
65 62 {
66 63 m_DataSourceControllerThread.quit();
67 64 m_DataSourceControllerThread.wait();
68 65
69 66 m_NetworkControllerThread.quit();
70 67 m_NetworkControllerThread.wait();
71 68
72 69 m_VisualizationControllerThread.quit();
73 70 m_VisualizationControllerThread.wait();
74 71 }
75 72
76 73 DataSourceController m_DataSourceController;
77 74 std::shared_ptr<VariableController2> m_VariableController;
78 75 TimeController m_TimeController;
79 76 NetworkController m_NetworkController;
80 77 VisualizationController m_VisualizationController;
81 78 CatalogueController m_CatalogueController;
82 79
83 80 QThread m_DataSourceControllerThread;
84 81 QThread m_NetworkControllerThread;
85 82 QThread m_VisualizationControllerThread;
86 83
87 84 DragDropGuiController m_DragDropGuiController;
88 85 ActionsGuiController m_ActionsGuiController;
89 86
90 87 SqpApplication::PlotsInteractionMode m_PlotInterractionMode;
91 88 SqpApplication::PlotsCursorMode m_PlotCursorMode;
92 89 };
93 90
94 91
95 92 SqpApplication::SqpApplication(int &argc, char **argv)
96 93 : QApplication{argc, argv}, impl{spimpl::make_unique_impl<SqpApplicationPrivate>()}
97 94 {
98 95 qCDebug(LOG_SqpApplication()) << tr("SqpApplication construction") << QThread::currentThread();
99 96
100 97 QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
101 98
102 99 connect(&impl->m_DataSourceControllerThread, &QThread::started,
103 100 &impl->m_DataSourceController, &DataSourceController::initialize);
104 101 connect(&impl->m_DataSourceControllerThread, &QThread::finished,
105 102 &impl->m_DataSourceController, &DataSourceController::finalize);
106 103
107 104 connect(&impl->m_NetworkControllerThread, &QThread::started, &impl->m_NetworkController,
108 105 &NetworkController::initialize);
109 106 connect(&impl->m_NetworkControllerThread, &QThread::finished, &impl->m_NetworkController,
110 107 &NetworkController::finalize);
111 108
112 109 connect(&impl->m_VisualizationControllerThread, &QThread::started,
113 110 &impl->m_VisualizationController, &VisualizationController::initialize);
114 111 connect(&impl->m_VisualizationControllerThread, &QThread::finished,
115 112 &impl->m_VisualizationController, &VisualizationController::finalize);
116 113
117 114 impl->m_DataSourceControllerThread.start();
118 115 impl->m_NetworkControllerThread.start();
119 116 impl->m_VisualizationControllerThread.start();
120 117 impl->m_CatalogueController.initialize();
121 118 }
122 119
123 120 SqpApplication::~SqpApplication()
124 121 {
125 122 }
126 123
127 124 void SqpApplication::initialize()
128 125 {
129 126 }
130 127
131 128 DataSourceController &SqpApplication::dataSourceController() noexcept
132 129 {
133 130 return impl->m_DataSourceController;
134 131 }
135 132
136 133 NetworkController &SqpApplication::networkController() noexcept
137 134 {
138 135 return impl->m_NetworkController;
139 136 }
140 137
141 138 TimeController &SqpApplication::timeController() noexcept
142 139 {
143 140 return impl->m_TimeController;
144 141 }
145 142
146 143 VariableController2 &SqpApplication::variableController() noexcept
147 144 {
148 145 return *impl->m_VariableController;
149 146 }
150 147
151 148 std::shared_ptr<VariableController2> SqpApplication::variableControllerOwner() noexcept
152 149 {
153 150 return impl->m_VariableController;
154 151 }
155 152
156 153 //VariableModel2 &SqpApplication::variableModel() noexcept
157 154 //{
158 155 // return impl->m_VariableModel;
159 156 //}
160 157
161 158 VisualizationController &SqpApplication::visualizationController() noexcept
162 159 {
163 160 return impl->m_VisualizationController;
164 161 }
165 162
166 163 CatalogueController &SqpApplication::catalogueController() noexcept
167 164 {
168 165 return impl->m_CatalogueController;
169 166 }
170 167
171 168 DragDropGuiController &SqpApplication::dragDropGuiController() noexcept
172 169 {
173 170 return impl->m_DragDropGuiController;
174 171 }
175 172
176 173 ActionsGuiController &SqpApplication::actionsGuiController() noexcept
177 174 {
178 175 return impl->m_ActionsGuiController;
179 176 }
180 177
181 178 SqpApplication::PlotsInteractionMode SqpApplication::plotsInteractionMode() const
182 179 {
183 180 return impl->m_PlotInterractionMode;
184 181 }
185 182
186 183 void SqpApplication::setPlotsInteractionMode(SqpApplication::PlotsInteractionMode mode)
187 184 {
188 185 impl->m_PlotInterractionMode = mode;
189 186 }
190 187
191 188 SqpApplication::PlotsCursorMode SqpApplication::plotsCursorMode() const
192 189 {
193 190 return impl->m_PlotCursorMode;
194 191 }
195 192
196 193 void SqpApplication::setPlotsCursorMode(SqpApplication::PlotsCursorMode mode)
197 194 {
198 195 impl->m_PlotCursorMode = mode;
199 196 }
@@ -1,155 +1,156
1 1 #include "TimeWidget/TimeWidget.h"
2 2 #include "ui_TimeWidget.h"
3 3
4 4 #include <Common/DateUtils.h>
5 5 #include <Common/MimeTypesDef.h>
6 6
7 7 #include <DragAndDrop/DragDropGuiController.h>
8 8 #include <SqpApplication.h>
9 9 #include <Time/TimeController.h>
10 10
11 11 #include <QDrag>
12 12 #include <QDragEnterEvent>
13 13 #include <QDropEvent>
14 14 #include <QMimeData>
15 15 #include <QStyle>
16 16
17 17
18 18 struct TimeWidget::TimeWidgetPrivate {
19 19
20 20 explicit TimeWidgetPrivate() {}
21 21
22 22 QPoint m_DragStartPosition;
23 23 };
24 24
25 25 TimeWidget::TimeWidget(QWidget *parent)
26 26 : QWidget{parent},
27 27 ui{new Ui::TimeWidget},
28 28 impl{spimpl::make_unique_impl<TimeWidgetPrivate>()}
29 29 {
30 30 ui->setupUi(this);
31 31
32 32 ui->applyToolButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_DialogApplyButton));
33 33
34 34 // Connection
35 35 connect(ui->startDateTimeEdit, &QDateTimeEdit::dateTimeChanged, this,
36 36 &TimeWidget::onTimeUpdateRequested);
37 37
38 38 connect(ui->endDateTimeEdit, &QDateTimeEdit::dateTimeChanged, this,
39 39 &TimeWidget::onTimeUpdateRequested);
40 40
41 41
42 42 connect(ui->applyToolButton, &QToolButton::clicked, &sqpApp->timeController(),
43 43 &TimeController::onTimeNotify);
44 44
45 45 // Initialisation
46 46 auto endDateTime = QDateTime::currentDateTimeUtc();
47 47 auto startDateTime = endDateTime.addSecs(-3600); // one hour before
48 48
49 49 ui->startDateTimeEdit->setDateTime(startDateTime);
50 50 ui->endDateTimeEdit->setDateTime(endDateTime);
51 51
52 52 auto dateTime = DateTimeRange{DateUtils::secondsSinceEpoch(startDateTime),
53 53 DateUtils::secondsSinceEpoch(endDateTime)};
54 54
55 55 sqpApp->timeController().setDateTimeRange(dateTime);
56 56 }
57 57
58 58
59 59 TimeWidget::~TimeWidget()
60 60 {
61 61 delete ui;
62 62 }
63 63
64 64 void TimeWidget::setTimeRange(DateTimeRange time)
65 65 {
66 66 auto startDateTime = DateUtils::dateTime(time.m_TStart);
67 67 auto endDateTime = DateUtils::dateTime(time.m_TEnd);
68 68
69 69 ui->startDateTimeEdit->setDateTime(startDateTime);
70 70 ui->endDateTimeEdit->setDateTime(endDateTime);
71 71 }
72 72
73 73 DateTimeRange TimeWidget::timeRange() const
74 74 {
75 75 return DateTimeRange{DateUtils::secondsSinceEpoch(ui->startDateTimeEdit->dateTime()),
76 76 DateUtils::secondsSinceEpoch(ui->endDateTimeEdit->dateTime())};
77 77 }
78 78
79 79 void TimeWidget::onTimeUpdateRequested()
80 80 {
81 81 auto dateTime = timeRange();
82 82 emit timeUpdated(std::move(dateTime));
83 sqpApp->timeController().setDateTimeRange(dateTime);
83 84 }
84 85
85 86 void TimeWidget::dragEnterEvent(QDragEnterEvent *event)
86 87 {
87 88 if (event->mimeData()->hasFormat(MIME_TYPE_TIME_RANGE)) {
88 89 event->acceptProposedAction();
89 90 setStyleSheet("QDateTimeEdit{background-color: #BBD5EE; border:2px solid #2A7FD4}");
90 91 }
91 92 else {
92 93 event->ignore();
93 94 }
94 95 }
95 96
96 97 void TimeWidget::dragLeaveEvent(QDragLeaveEvent *event)
97 98 {
98 99 setStyleSheet(QString{});
99 100 }
100 101
101 102 void TimeWidget::dropEvent(QDropEvent *event)
102 103 {
103 104 if (event->mimeData()->hasFormat(MIME_TYPE_TIME_RANGE)) {
104 105 auto mimeData = event->mimeData()->data(MIME_TYPE_TIME_RANGE);
105 106 auto timeRange = TimeController::timeRangeForMimeData(mimeData);
106 107
107 108 setTimeRange(timeRange);
108 109 }
109 110 else {
110 111 event->ignore();
111 112 }
112 113
113 114 setStyleSheet(QString{});
114 115 }
115 116
116 117
117 118 void TimeWidget::mousePressEvent(QMouseEvent *event)
118 119 {
119 120 if (event->button() == Qt::LeftButton) {
120 121 impl->m_DragStartPosition = event->pos();
121 122 }
122 123
123 124 QWidget::mousePressEvent(event);
124 125 }
125 126
126 127 void TimeWidget::mouseMoveEvent(QMouseEvent *event)
127 128 {
128 129 if (!(event->buttons() & Qt::LeftButton)) {
129 130 return;
130 131 }
131 132
132 133 if ((event->pos() - impl->m_DragStartPosition).manhattanLength()
133 134 < QApplication::startDragDistance()) {
134 135 return;
135 136 }
136 137
137 138 // Note: The management of the drag object is done by Qt
138 139 auto drag = new QDrag{this};
139 140
140 141 auto mimeData = new QMimeData;
141 142 auto timeData = TimeController::mimeDataForTimeRange(timeRange());
142 143 mimeData->setData(MIME_TYPE_TIME_RANGE, timeData);
143 144
144 145 drag->setMimeData(mimeData);
145 146
146 147 auto pixmap = QPixmap{":/icones/time.png"};
147 148 drag->setPixmap(pixmap.scaledToWidth(22));
148 149
149 150 sqpApp->dragDropGuiController().resetDragAndDrop();
150 151
151 152 // Note: The exec() is blocking on windows but not on linux and macOS
152 153 drag->exec(Qt::MoveAction | Qt::CopyAction);
153 154
154 155 QWidget::mouseMoveEvent(event);
155 156 }
@@ -1,1083 +1,1042
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationCursorItem.h"
4 4 #include "Visualization/VisualizationDefs.h"
5 5 #include "Visualization/VisualizationGraphHelper.h"
6 6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
7 7 #include "Visualization/VisualizationMultiZoneSelectionDialog.h"
8 8 #include "Visualization/VisualizationSelectionZoneItem.h"
9 9 #include "Visualization/VisualizationSelectionZoneManager.h"
10 10 #include "Visualization/VisualizationWidget.h"
11 11 #include "Visualization/VisualizationZoneWidget.h"
12 12 #include "ui_VisualizationGraphWidget.h"
13 13
14 14 #include <Actions/ActionsGuiController.h>
15 15 #include <Actions/FilteringAction.h>
16 16 #include <Common/MimeTypesDef.h>
17 17 #include <Data/ArrayData.h>
18 18 #include <Data/IDataSeries.h>
19 19 #include <Data/SpectrogramSeries.h>
20 20 #include <DragAndDrop/DragDropGuiController.h>
21 21 #include <Settings/SqpSettingsDefs.h>
22 22 #include <SqpApplication.h>
23 23 #include <Time/TimeController.h>
24 24 #include <Variable/Variable.h>
25 25 #include <Variable/VariableController2.h>
26 26
27 27 #include <unordered_map>
28 28
29 29 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
30 30
31 31 namespace {
32 32
33 33 /// Key pressed to enable drag&drop in all modes
34 34 const auto DRAG_DROP_MODIFIER = Qt::AltModifier;
35 35
36 36 /// Key pressed to enable zoom on horizontal axis
37 37 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
38 38
39 39 /// Key pressed to enable zoom on vertical axis
40 40 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
41 41
42 42 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
43 43 const auto PAN_SPEED = 5;
44 44
45 45 /// Key pressed to enable a calibration pan
46 46 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
47 47
48 48 /// Key pressed to enable multi selection of selection zones
49 49 const auto MULTI_ZONE_SELECTION_MODIFIER = Qt::ControlModifier;
50 50
51 51 /// Minimum size for the zoom box, in percentage of the axis range
52 52 const auto ZOOM_BOX_MIN_SIZE = 0.8;
53 53
54 54 /// Format of the dates appearing in the label of a cursor
55 55 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
56 56
57 57 } // namespace
58 58
59 59 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
60 60
61 61 explicit VisualizationGraphWidgetPrivate(const QString &name)
62 62 : m_Name{name},
63 63 m_Flags{GraphFlag::EnableAll},
64 64 m_IsCalibration{false},
65 65 m_RenderingDelegate{nullptr}
66 66 {
67 67 }
68 68
69 69 void updateData(PlottablesMap &plottables, std::shared_ptr<Variable> variable,
70 70 const DateTimeRange &range)
71 71 {
72 72 VisualizationGraphHelper::updateData(plottables, variable, range);
73 73
74 74 // Prevents that data has changed to update rendering
75 75 m_RenderingDelegate->onPlotUpdated();
76 76 }
77 77
78 78 QString m_Name;
79 79 // 1 variable -> n qcpplot
80 80 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
81 81 GraphFlags m_Flags;
82 82 bool m_IsCalibration;
83 83 /// Delegate used to attach rendering features to the plot
84 84 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
85 85
86 86 QCPItemRect *m_DrawingZoomRect = nullptr;
87 87 QStack<QPair<QCPRange, QCPRange> > m_ZoomStack;
88 88
89 89 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
90 90 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
91 91
92 92 VisualizationSelectionZoneItem *m_DrawingZone = nullptr;
93 93 VisualizationSelectionZoneItem *m_HoveredZone = nullptr;
94 94 QVector<VisualizationSelectionZoneItem *> m_SelectionZones;
95 95
96 96 bool m_HasMovedMouse = false; // Indicates if the mouse moved in a releaseMouse even
97 97
98 98 bool m_VariableAutoRangeOnInit = true;
99 99
100 100 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
101 101 {
102 102 removeDrawingRect(plot);
103 103
104 104 auto axisPos = posToAxisPos(pos, plot);
105 105
106 106 m_DrawingZoomRect = new QCPItemRect{&plot};
107 107 QPen p;
108 108 p.setWidth(2);
109 109 m_DrawingZoomRect->setPen(p);
110 110
111 111 m_DrawingZoomRect->topLeft->setCoords(axisPos);
112 112 m_DrawingZoomRect->bottomRight->setCoords(axisPos);
113 113 }
114 114
115 115 void removeDrawingRect(QCustomPlot &plot)
116 116 {
117 117 if (m_DrawingZoomRect) {
118 118 plot.removeItem(m_DrawingZoomRect); // the item is deleted by QCustomPlot
119 119 m_DrawingZoomRect = nullptr;
120 120 plot.replot(QCustomPlot::rpQueuedReplot);
121 121 }
122 122 }
123 123
124 124 void startDrawingZone(const QPoint &pos, VisualizationGraphWidget *graph)
125 125 {
126 126 endDrawingZone(graph);
127 127
128 128 auto axisPos = posToAxisPos(pos, graph->plot());
129 129
130 130 m_DrawingZone = new VisualizationSelectionZoneItem{&graph->plot()};
131 131 m_DrawingZone->setRange(axisPos.x(), axisPos.x());
132 132 m_DrawingZone->setEditionEnabled(false);
133 133 }
134 134
135 135 void endDrawingZone(VisualizationGraphWidget *graph)
136 136 {
137 137 if (m_DrawingZone) {
138 138 auto drawingZoneRange = m_DrawingZone->range();
139 139 if (qAbs(drawingZoneRange.m_TEnd - drawingZoneRange.m_TStart) > 0) {
140 140 m_DrawingZone->setEditionEnabled(true);
141 141 addSelectionZone(m_DrawingZone);
142 142 }
143 143 else {
144 144 graph->plot().removeItem(m_DrawingZone); // the item is deleted by QCustomPlot
145 145 }
146 146
147 147 graph->plot().replot(QCustomPlot::rpQueuedReplot);
148 148 m_DrawingZone = nullptr;
149 149 }
150 150 }
151 151
152 152 void setSelectionZonesEditionEnabled(bool value)
153 153 {
154 154 for (auto s : m_SelectionZones) {
155 155 s->setEditionEnabled(value);
156 156 }
157 157 }
158 158
159 159 void addSelectionZone(VisualizationSelectionZoneItem *zone) { m_SelectionZones << zone; }
160 160
161 161 VisualizationSelectionZoneItem *selectionZoneAt(const QPoint &pos,
162 162 const QCustomPlot &plot) const
163 163 {
164 164 VisualizationSelectionZoneItem *selectionZoneItemUnderCursor = nullptr;
165 165 auto minDistanceToZone = -1;
166 166 for (auto zone : m_SelectionZones) {
167 167 auto distanceToZone = zone->selectTest(pos, false);
168 168 if ((minDistanceToZone < 0 || distanceToZone <= minDistanceToZone)
169 169 && distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
170 170 selectionZoneItemUnderCursor = zone;
171 171 }
172 172 }
173 173
174 174 return selectionZoneItemUnderCursor;
175 175 }
176 176
177 177 QVector<VisualizationSelectionZoneItem *> selectionZonesAt(const QPoint &pos,
178 178 const QCustomPlot &plot) const
179 179 {
180 180 QVector<VisualizationSelectionZoneItem *> zones;
181 181 for (auto zone : m_SelectionZones) {
182 182 auto distanceToZone = zone->selectTest(pos, false);
183 183 if (distanceToZone >= 0 && distanceToZone < plot.selectionTolerance()) {
184 184 zones << zone;
185 185 }
186 186 }
187 187
188 188 return zones;
189 189 }
190 190
191 191 void moveSelectionZoneOnTop(VisualizationSelectionZoneItem *zone, QCustomPlot &plot)
192 192 {
193 193 if (!m_SelectionZones.isEmpty() && m_SelectionZones.last() != zone) {
194 194 zone->moveToTop();
195 195 m_SelectionZones.removeAll(zone);
196 196 m_SelectionZones.append(zone);
197 197 }
198 198 }
199 199
200 200 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
201 201 {
202 202 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
203 203 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
204 204 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
205 205 }
206 206
207 207 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
208 208 {
209 209 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
210 210 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
211 211 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
212 212 }
213 213 };
214 214
215 215 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
216 216 : VisualizationDragWidget{parent},
217 217 ui{new Ui::VisualizationGraphWidget},
218 218 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
219 219 {
220 220 ui->setupUi(this);
221 221
222 222 // 'Close' options : widget is deleted when closed
223 223 setAttribute(Qt::WA_DeleteOnClose);
224 224
225 225 // Set qcpplot properties :
226 226 // - zoom is enabled
227 227 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
228 228 ui->widget->setInteractions(QCP::iRangeZoom);
229 229 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal | Qt::Vertical);
230 230
231 231 // The delegate must be initialized after the ui as it uses the plot
232 232 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
233 233
234 234 // Init the cursors
235 235 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
236 236 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
237 237 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
238 238 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
239 239
240 240 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
241 241 connect(ui->widget, &QCustomPlot::mouseRelease, this,
242 242 &VisualizationGraphWidget::onMouseRelease);
243 243 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
244 244 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
245 245 connect(ui->widget, &QCustomPlot::mouseDoubleClick, this,
246 246 &VisualizationGraphWidget::onMouseDoubleClick);
247 247 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
248 248 &QCPAxis::rangeChanged),
249 249 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
250 250
251 251 // Activates menu when right clicking on the graph
252 252 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
253 253 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
254 254 &VisualizationGraphWidget::onGraphMenuRequested);
255 255
256 256 //@TODO implement this :)
257 257 // connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
258 258 // &VariableController::onRequestDataLoading);
259 259
260 260 // connect(&sqpApp->variableController(), &VariableController2::updateVarDisplaying, this,
261 261 // &VisualizationGraphWidget::onUpdateVarDisplaying);
262 262
263 263 // Necessary for all platform since Qt::AA_EnableHighDpiScaling is enable.
264 264 plot().setPlottingHint(QCP::phFastPolylines, true);
265 265 }
266 266
267 267
268 268 VisualizationGraphWidget::~VisualizationGraphWidget()
269 269 {
270 270 delete ui;
271 271 }
272 272
273 273 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
274 274 {
275 275 auto parent = parentWidget();
276 276 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
277 277 parent = parent->parentWidget();
278 278 }
279 279
280 280 return qobject_cast<VisualizationZoneWidget *>(parent);
281 281 }
282 282
283 283 VisualizationWidget *VisualizationGraphWidget::parentVisualizationWidget() const
284 284 {
285 285 auto parent = parentWidget();
286 286 while (parent != nullptr && !qobject_cast<VisualizationWidget *>(parent)) {
287 287 parent = parent->parentWidget();
288 288 }
289 289
290 290 return qobject_cast<VisualizationWidget *>(parent);
291 291 }
292 292
293 293 void VisualizationGraphWidget::setFlags(GraphFlags flags)
294 294 {
295 295 impl->m_Flags = std::move(flags);
296 296 }
297 297
298 298 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, DateTimeRange range)
299 299 {
300 /// Lambda used to set graph's units and range according to the variable passed in parameter
301 auto loadRange = [this](std::shared_ptr<Variable> variable, const DateTimeRange &range) {
302 impl->m_RenderingDelegate->setAxesUnits(*variable);
303
304 this->setFlags(GraphFlag::DisableAll);
305 setGraphRange(range);
306 this->setFlags(GraphFlag::EnableAll);
307 emit requestDataLoading({variable}, range, false);
308 };
309
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);
325 }
326 },
327 Qt::QueuedConnection);
328
329 300 // Uses delegate to create the qcpplot components according to the variable
330 301 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
331 302
332 303 // Sets graph properties
333 304 impl->m_RenderingDelegate->setGraphProperties(*variable, createdPlottables);
334 305
335 306 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
336 307
337 308 // If the variable already has its data loaded, load its units and its range in the graph
338 309 if (variable->dataSeries() != nullptr) {
339 loadRange(variable, range);
310 impl->m_RenderingDelegate->setAxesUnits(*variable);
311 this->setFlags(GraphFlag::DisableAll);
312 setGraphRange(range);
313 this->setFlags(GraphFlag::EnableAll);
340 314 }
341
315 connect(variable.get(),&Variable::updated,
316 [variable=variable,this]()
317 {
318 QMetaObject::invokeMethod(this,[variable=variable,this](){this->onUpdateVarDisplaying(variable,this->graphRange());});
319 });
320 this->onUpdateVarDisplaying(variable,range);//My bullshit
342 321 emit variableAdded(variable);
343 322 }
344 323
345 324 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
346 325 {
347 326 // Each component associated to the variable :
348 327 // - is removed from qcpplot (which deletes it)
349 328 // - is no longer referenced in the map
350 329 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
351 330 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
352 331 emit variableAboutToBeRemoved(variable);
353 332
354 333 auto &plottablesMap = variableIt->second;
355 334
356 335 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
357 336 plottableIt != plottableEnd;) {
358 337 ui->widget->removePlottable(plottableIt->second);
359 338 plottableIt = plottablesMap.erase(plottableIt);
360 339 }
361 340
362 341 impl->m_VariableToPlotMultiMap.erase(variableIt);
363 342 }
364 343
365 344 // Updates graph
366 345 ui->widget->replot();
367 346 }
368 347
369 348 std::vector<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
370 349 {
371 350 auto variables = std::vector<std::shared_ptr<Variable> >{};
372 351 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
373 352 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
374 353 variables.push_back (it->first);
375 354 }
376 355
377 356 return variables;
378 357 }
379 358
380 359 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
381 360 {
382 361 if (!variable) {
383 362 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
384 363 return;
385 364 }
386 365
387 366 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
388 367 }
389 368
390 369 DateTimeRange VisualizationGraphWidget::graphRange() const noexcept
391 370 {
392 371 auto graphRange = ui->widget->xAxis->range();
393 372 return DateTimeRange{graphRange.lower, graphRange.upper};
394 373 }
395 374
396 375 void VisualizationGraphWidget::setGraphRange(const DateTimeRange &range, bool calibration)
397 376 {
398 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
399
400 377 if (calibration) {
401 378 impl->m_IsCalibration = true;
402 379 }
403 380
404 381 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
405 382 ui->widget->replot();
406 383
407 384 if (calibration) {
408 385 impl->m_IsCalibration = false;
409 386 }
410
411 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
412 387 }
413 388
414 389 void VisualizationGraphWidget::setAutoRangeOnVariableInitialization(bool value)
415 390 {
416 391 impl->m_VariableAutoRangeOnInit = value;
417 392 }
418 393
419 394 QVector<DateTimeRange> VisualizationGraphWidget::selectionZoneRanges() const
420 395 {
421 396 QVector<DateTimeRange> ranges;
422 397 for (auto zone : impl->m_SelectionZones) {
423 398 ranges << zone->range();
424 399 }
425 400
426 401 return ranges;
427 402 }
428 403
429 404 void VisualizationGraphWidget::addSelectionZones(const QVector<DateTimeRange> &ranges)
430 405 {
431 406 for (const auto &range : ranges) {
432 407 // note: ownership is transfered to QCustomPlot
433 408 auto zone = new VisualizationSelectionZoneItem(&plot());
434 409 zone->setRange(range.m_TStart, range.m_TEnd);
435 410 impl->addSelectionZone(zone);
436 411 }
437 412
438 413 plot().replot(QCustomPlot::rpQueuedReplot);
439 414 }
440 415
441 416 VisualizationSelectionZoneItem *VisualizationGraphWidget::addSelectionZone(const QString &name,
442 417 const DateTimeRange &range)
443 418 {
444 419 // note: ownership is transfered to QCustomPlot
445 420 auto zone = new VisualizationSelectionZoneItem(&plot());
446 421 zone->setName(name);
447 422 zone->setRange(range.m_TStart, range.m_TEnd);
448 423 impl->addSelectionZone(zone);
449 424
450 425 plot().replot(QCustomPlot::rpQueuedReplot);
451 426
452 427 return zone;
453 428 }
454 429
455 430 void VisualizationGraphWidget::removeSelectionZone(VisualizationSelectionZoneItem *selectionZone)
456 431 {
457 432 parentVisualizationWidget()->selectionZoneManager().setSelected(selectionZone, false);
458 433
459 434 if (impl->m_HoveredZone == selectionZone) {
460 435 impl->m_HoveredZone = nullptr;
461 436 setCursor(Qt::ArrowCursor);
462 437 }
463 438
464 439 impl->m_SelectionZones.removeAll(selectionZone);
465 440 plot().removeItem(selectionZone);
466 441 plot().replot(QCustomPlot::rpQueuedReplot);
467 442 }
468 443
469 444 void VisualizationGraphWidget::undoZoom()
470 445 {
471 446 auto zoom = impl->m_ZoomStack.pop();
472 447 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
473 448 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
474 449
475 450 axisX->setRange(zoom.first);
476 451 axisY->setRange(zoom.second);
477 452
478 453 plot().replot(QCustomPlot::rpQueuedReplot);
479 454 }
480 455
481 456 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
482 457 {
483 458 if (visitor) {
484 459 visitor->visit(this);
485 460 }
486 461 else {
487 462 qCCritical(LOG_VisualizationGraphWidget())
488 463 << tr("Can't visit widget : the visitor is null");
489 464 }
490 465 }
491 466
492 467 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
493 468 {
494 469 auto isSpectrogram = [](const auto &variable) {
495 470 return std::dynamic_pointer_cast<SpectrogramSeries>(variable.dataSeries()) != nullptr;
496 471 };
497 472
498 473 // - A spectrogram series can't be dropped on graph with existing plottables
499 474 // - No data series can be dropped on graph with existing spectrogram series
500 475 return isSpectrogram(variable)
501 476 ? impl->m_VariableToPlotMultiMap.empty()
502 477 : std::none_of(
503 478 impl->m_VariableToPlotMultiMap.cbegin(), impl->m_VariableToPlotMultiMap.cend(),
504 479 [isSpectrogram](const auto &entry) { return isSpectrogram(*entry.first); });
505 480 }
506 481
507 482 bool VisualizationGraphWidget::contains(const Variable &variable) const
508 483 {
509 484 // Finds the variable among the keys of the map
510 485 auto variablePtr = &variable;
511 486 auto findVariable
512 487 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
513 488
514 489 auto end = impl->m_VariableToPlotMultiMap.cend();
515 490 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
516 491 return it != end;
517 492 }
518 493
519 494 QString VisualizationGraphWidget::name() const
520 495 {
521 496 return impl->m_Name;
522 497 }
523 498
524 499 QMimeData *VisualizationGraphWidget::mimeData(const QPoint &position) const
525 500 {
526 501 auto mimeData = new QMimeData;
527 502
528 503 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(position, plot());
529 504 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
530 505 && selectionZoneItemUnderCursor) {
531 506 mimeData->setData(MIME_TYPE_TIME_RANGE, TimeController::mimeDataForTimeRange(
532 507 selectionZoneItemUnderCursor->range()));
533 508 mimeData->setData(MIME_TYPE_SELECTION_ZONE, TimeController::mimeDataForTimeRange(
534 509 selectionZoneItemUnderCursor->range()));
535 510 }
536 511 else {
537 512 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
538 513
539 514 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
540 515 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
541 516 }
542 517
543 518 return mimeData;
544 519 }
545 520
546 521 QPixmap VisualizationGraphWidget::customDragPixmap(const QPoint &dragPosition)
547 522 {
548 523 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(dragPosition, plot());
549 524 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones
550 525 && selectionZoneItemUnderCursor) {
551 526
552 527 auto zoneTopLeft = selectionZoneItemUnderCursor->topLeft->pixelPosition();
553 528 auto zoneBottomRight = selectionZoneItemUnderCursor->bottomRight->pixelPosition();
554 529
555 530 auto zoneSize = QSizeF{qAbs(zoneBottomRight.x() - zoneTopLeft.x()),
556 531 qAbs(zoneBottomRight.y() - zoneTopLeft.y())}
557 532 .toSize();
558 533
559 534 auto pixmap = QPixmap(zoneSize);
560 535 render(&pixmap, QPoint(), QRegion{QRect{zoneTopLeft.toPoint(), zoneSize}});
561 536
562 537 return pixmap;
563 538 }
564 539
565 540 return QPixmap();
566 541 }
567 542
568 543 bool VisualizationGraphWidget::isDragAllowed() const
569 544 {
570 545 return true;
571 546 }
572 547
573 548 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
574 549 {
575 550 if (highlighted) {
576 551 plot().setBackground(QBrush(QColor("#BBD5EE")));
577 552 }
578 553 else {
579 554 plot().setBackground(QBrush(Qt::white));
580 555 }
581 556
582 557 plot().update();
583 558 }
584 559
585 560 void VisualizationGraphWidget::addVerticalCursor(double time)
586 561 {
587 562 impl->m_VerticalCursor->setPosition(time);
588 563 impl->m_VerticalCursor->setVisible(true);
589 564
590 565 auto text
591 566 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
592 567 impl->m_VerticalCursor->setLabelText(text);
593 568 }
594 569
595 570 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
596 571 {
597 572 impl->m_VerticalCursor->setAbsolutePosition(position);
598 573 impl->m_VerticalCursor->setVisible(true);
599 574
600 575 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
601 576 auto text
602 577 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
603 578 impl->m_VerticalCursor->setLabelText(text);
604 579 }
605 580
606 581 void VisualizationGraphWidget::removeVerticalCursor()
607 582 {
608 583 impl->m_VerticalCursor->setVisible(false);
609 584 plot().replot(QCustomPlot::rpQueuedReplot);
610 585 }
611 586
612 587 void VisualizationGraphWidget::addHorizontalCursor(double value)
613 588 {
614 589 impl->m_HorizontalCursor->setPosition(value);
615 590 impl->m_HorizontalCursor->setVisible(true);
616 591 impl->m_HorizontalCursor->setLabelText(QString::number(value));
617 592 }
618 593
619 594 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
620 595 {
621 596 impl->m_HorizontalCursor->setAbsolutePosition(position);
622 597 impl->m_HorizontalCursor->setVisible(true);
623 598
624 599 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
625 600 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
626 601 }
627 602
628 603 void VisualizationGraphWidget::removeHorizontalCursor()
629 604 {
630 605 impl->m_HorizontalCursor->setVisible(false);
631 606 plot().replot(QCustomPlot::rpQueuedReplot);
632 607 }
633 608
634 609 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
635 610 {
636 611 Q_UNUSED(event);
637 612
638 613 for (auto i : impl->m_SelectionZones) {
639 614 parentVisualizationWidget()->selectionZoneManager().setSelected(i, false);
640 615 }
641 616
642 617 // Prevents that all variables will be removed from graph when it will be closed
643 618 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
644 619 emit variableAboutToBeRemoved(variableEntry.first);
645 620 }
646 621 }
647 622
648 623 void VisualizationGraphWidget::enterEvent(QEvent *event)
649 624 {
650 625 Q_UNUSED(event);
651 626 impl->m_RenderingDelegate->showGraphOverlay(true);
652 627 }
653 628
654 629 void VisualizationGraphWidget::leaveEvent(QEvent *event)
655 630 {
656 631 Q_UNUSED(event);
657 632 impl->m_RenderingDelegate->showGraphOverlay(false);
658 633
659 634 if (auto parentZone = parentZoneWidget()) {
660 635 parentZone->notifyMouseLeaveGraph(this);
661 636 }
662 637 else {
663 638 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
664 639 }
665 640
666 641 if (impl->m_HoveredZone) {
667 642 impl->m_HoveredZone->setHovered(false);
668 643 impl->m_HoveredZone = nullptr;
669 644 }
670 645 }
671 646
672 647 QCustomPlot &VisualizationGraphWidget::plot() const noexcept
673 648 {
674 649 return *ui->widget;
675 650 }
676 651
677 652 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
678 653 {
679 654 QMenu graphMenu{};
680 655
681 656 // Iterates on variables (unique keys)
682 657 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
683 658 end = impl->m_VariableToPlotMultiMap.cend();
684 659 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
685 660 // 'Remove variable' action
686 661 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
687 662 [ this, var = it->first ]() { removeVariable(var); });
688 663 }
689 664
690 665 if (!impl->m_ZoomStack.isEmpty()) {
691 666 if (!graphMenu.isEmpty()) {
692 667 graphMenu.addSeparator();
693 668 }
694 669
695 670 graphMenu.addAction(tr("Undo Zoom"), [this]() { undoZoom(); });
696 671 }
697 672
698 673 // Selection Zone Actions
699 674 auto selectionZoneItem = impl->selectionZoneAt(pos, plot());
700 675 if (selectionZoneItem) {
701 676 auto selectedItems = parentVisualizationWidget()->selectionZoneManager().selectedItems();
702 677 selectedItems.removeAll(selectionZoneItem);
703 678 selectedItems.prepend(selectionZoneItem); // Put the current selection zone first
704 679
705 680 auto zoneActions = sqpApp->actionsGuiController().selectionZoneActions();
706 681 if (!zoneActions.isEmpty() && !graphMenu.isEmpty()) {
707 682 graphMenu.addSeparator();
708 683 }
709 684
710 685 QHash<QString, QMenu *> subMenus;
711 686 QHash<QString, bool> subMenusEnabled;
712 687 QHash<QString, FilteringAction *> filteredMenu;
713 688
714 689 for (auto zoneAction : zoneActions) {
715 690
716 691 auto isEnabled = zoneAction->isEnabled(selectedItems);
717 692
718 693 auto menu = &graphMenu;
719 694 QString menuPath;
720 695 for (auto subMenuName : zoneAction->subMenuList()) {
721 696 menuPath += '/';
722 697 menuPath += subMenuName;
723 698
724 699 if (!subMenus.contains(menuPath)) {
725 700 menu = menu->addMenu(subMenuName);
726 701 subMenus[menuPath] = menu;
727 702 subMenusEnabled[menuPath] = isEnabled;
728 703 }
729 704 else {
730 705 menu = subMenus.value(menuPath);
731 706 if (isEnabled) {
732 707 // The sub menu is enabled if at least one of its actions is enabled
733 708 subMenusEnabled[menuPath] = true;
734 709 }
735 710 }
736 711 }
737 712
738 713 FilteringAction *filterAction = nullptr;
739 714 if (sqpApp->actionsGuiController().isMenuFiltered(zoneAction->subMenuList())) {
740 715 filterAction = filteredMenu.value(menuPath);
741 716 if (!filterAction) {
742 717 filterAction = new FilteringAction{this};
743 718 filteredMenu[menuPath] = filterAction;
744 719 menu->addAction(filterAction);
745 720 }
746 721 }
747 722
748 723 auto action = menu->addAction(zoneAction->name());
749 724 action->setEnabled(isEnabled);
750 725 action->setShortcut(zoneAction->displayedShortcut());
751 726 QObject::connect(action, &QAction::triggered,
752 727 [zoneAction, selectedItems]() { zoneAction->execute(selectedItems); });
753 728
754 729 if (filterAction && zoneAction->isFilteringAllowed()) {
755 730 filterAction->addActionToFilter(action);
756 731 }
757 732 }
758 733
759 734 for (auto it = subMenus.cbegin(); it != subMenus.cend(); ++it) {
760 735 it.value()->setEnabled(subMenusEnabled[it.key()]);
761 736 }
762 737 }
763 738
764 739 if (!graphMenu.isEmpty()) {
765 740 graphMenu.exec(QCursor::pos());
766 741 }
767 742 }
768 743
769 744 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
770 745 {
771 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
772 << QThread::currentThread()->objectName() << "DoAcqui"
773 << impl->m_Flags.testFlag(GraphFlag::EnableAcquisition);
774
775 746 auto graphRange = DateTimeRange{t1.lower, t1.upper};
776 747 auto oldGraphRange = DateTimeRange{t2.lower, t2.upper};
777 748
778 749 if (impl->m_Flags.testFlag(GraphFlag::EnableAcquisition)) {
779 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
780
781 750 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
782 751 end = impl->m_VariableToPlotMultiMap.end();
783 752 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
784 variableUnderGraphVector.push_back(it->first);
753 sqpApp->variableController().asyncChangeRange(it->first, graphRange);
785 754 }
786 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
787 !impl->m_IsCalibration);
788 755 }
789 756
790 if (impl->m_Flags.testFlag(GraphFlag::EnableSynchronization) && !impl->m_IsCalibration) {
791 qCDebug(LOG_VisualizationGraphWidget())
792 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
793 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
757 if (impl->m_Flags.testFlag(GraphFlag::EnableSynchronization) && !impl->m_IsCalibration)
758 {
794 759 emit synchronize(graphRange, oldGraphRange);
795 760 }
796 761
797 762 auto pos = mapFromGlobal(QCursor::pos());
798 763 auto axisPos = impl->posToAxisPos(pos, plot());
799 764 if (auto parentZone = parentZoneWidget()) {
800 765 if (impl->pointIsInAxisRect(axisPos, plot())) {
801 766 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
802 767 }
803 768 else {
804 769 parentZone->notifyMouseLeaveGraph(this);
805 770 }
806 771 }
807 else {
808 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
809 }
810 772
811 773 // Quits calibration
812 774 impl->m_IsCalibration = false;
813 775 }
814 776
815 777 void VisualizationGraphWidget::onMouseDoubleClick(QMouseEvent *event) noexcept
816 778 {
817 779 impl->m_RenderingDelegate->onMouseDoubleClick(event);
818 780 }
819 781
820 782 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
821 783 {
822 784 // Handles plot rendering when mouse is moving
823 785 impl->m_RenderingDelegate->onMouseMove(event);
824 786
825 787 auto axisPos = impl->posToAxisPos(event->pos(), plot());
826 788
827 789 // Zoom box and zone drawing
828 790 if (impl->m_DrawingZoomRect) {
829 791 impl->m_DrawingZoomRect->bottomRight->setCoords(axisPos);
830 792 }
831 793 else if (impl->m_DrawingZone) {
832 794 impl->m_DrawingZone->setEnd(axisPos.x());
833 795 }
834 796
835 797 // Cursor
836 798 if (auto parentZone = parentZoneWidget()) {
837 799 if (impl->pointIsInAxisRect(axisPos, plot())) {
838 800 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
839 801 }
840 802 else {
841 803 parentZone->notifyMouseLeaveGraph(this);
842 804 }
843 805 }
844 else {
845 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
846 }
847 806
848 807 // Search for the selection zone under the mouse
849 808 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
850 809 if (selectionZoneItemUnderCursor && !impl->m_DrawingZone
851 810 && sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones) {
852 811
853 812 // Sets the appropriate cursor shape
854 813 auto cursorShape = selectionZoneItemUnderCursor->curshorShapeForPosition(event->pos());
855 814 setCursor(cursorShape);
856 815
857 816 // Manages the hovered zone
858 817 if (selectionZoneItemUnderCursor != impl->m_HoveredZone) {
859 818 if (impl->m_HoveredZone) {
860 819 impl->m_HoveredZone->setHovered(false);
861 820 }
862 821 selectionZoneItemUnderCursor->setHovered(true);
863 822 impl->m_HoveredZone = selectionZoneItemUnderCursor;
864 823 plot().replot(QCustomPlot::rpQueuedReplot);
865 824 }
866 825 }
867 826 else {
868 827 // There is no zone under the mouse or the interaction mode is not "selection zones"
869 828 if (impl->m_HoveredZone) {
870 829 impl->m_HoveredZone->setHovered(false);
871 830 impl->m_HoveredZone = nullptr;
872 831 }
873 832
874 833 setCursor(Qt::ArrowCursor);
875 834 }
876 835
877 836 impl->m_HasMovedMouse = true;
878 837 VisualizationDragWidget::mouseMoveEvent(event);
879 838 }
880 839
881 840 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
882 841 {
883 842 // Processes event only if the wheel occurs on axis rect
884 843 if (!dynamic_cast<QCPAxisRect *>(ui->widget->layoutElementAt(event->posF()))) {
885 844 return;
886 845 }
887 846
888 847 auto value = event->angleDelta().x() + event->angleDelta().y();
889 848 if (value != 0) {
890 849
891 850 auto direction = value > 0 ? 1.0 : -1.0;
892 851 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
893 852 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
894 853 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
895 854
896 855 auto zoomOrientations = QFlags<Qt::Orientation>{};
897 856 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
898 857 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
899 858
900 859 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
901 860
902 861 if (!isZoomX && !isZoomY) {
903 862 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
904 863 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
905 864
906 865 axis->setRange(axis->range() + diff);
907 866
908 867 if (plot().noAntialiasingOnDrag()) {
909 868 plot().setNotAntialiasedElements(QCP::aeAll);
910 869 }
911 870
912 plot().replot(QCustomPlot::rpQueuedReplot);
871 //plot().replot(QCustomPlot::rpQueuedReplot);
913 872 }
914 873 }
915 874 }
916 875
917 876 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
918 877 {
919 878 auto isDragDropClick = event->modifiers().testFlag(DRAG_DROP_MODIFIER);
920 879 auto isSelectionZoneMode
921 880 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
922 881 auto isLeftClick = event->buttons().testFlag(Qt::LeftButton);
923 882
924 883 if (!isDragDropClick && isLeftClick) {
925 884 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
926 885 // Starts a zoom box
927 886 impl->startDrawingRect(event->pos(), plot());
928 887 }
929 888 else if (isSelectionZoneMode && impl->m_DrawingZone == nullptr) {
930 889 // Starts a new selection zone
931 890 auto zoneAtPos = impl->selectionZoneAt(event->pos(), plot());
932 891 if (!zoneAtPos) {
933 892 impl->startDrawingZone(event->pos(), this);
934 893 }
935 894 }
936 895 }
937 896
938 897 // Allows mouse panning only in default mode
939 898 plot().setInteraction(QCP::iRangeDrag, sqpApp->plotsInteractionMode()
940 899 == SqpApplication::PlotsInteractionMode::None
941 900 && !isDragDropClick);
942 901
943 902 // Allows zone edition only in selection zone mode without drag&drop
944 903 impl->setSelectionZonesEditionEnabled(isSelectionZoneMode && !isDragDropClick);
945 904
946 905 // Selection / Deselection
947 906 if (isSelectionZoneMode) {
948 907 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
949 908 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
950 909
951 910
952 911 if (selectionZoneItemUnderCursor && !selectionZoneItemUnderCursor->selected()
953 912 && !isMultiSelectionClick) {
954 913 parentVisualizationWidget()->selectionZoneManager().select(
955 914 {selectionZoneItemUnderCursor});
956 915 }
957 916 else if (!selectionZoneItemUnderCursor && !isMultiSelectionClick && isLeftClick) {
958 917 parentVisualizationWidget()->selectionZoneManager().clearSelection();
959 918 }
960 919 else {
961 920 // No selection change
962 921 }
963 922
964 923 if (selectionZoneItemUnderCursor && isLeftClick) {
965 924 selectionZoneItemUnderCursor->setAssociatedEditedZones(
966 925 parentVisualizationWidget()->selectionZoneManager().selectedItems());
967 926 }
968 927 }
969 928
970 929
971 930 impl->m_HasMovedMouse = false;
972 931 VisualizationDragWidget::mousePressEvent(event);
973 932 }
974 933
975 934 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
976 935 {
977 936 if (impl->m_DrawingZoomRect) {
978 937
979 938 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
980 939 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
981 940
982 941 auto newAxisXRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().x(),
983 942 impl->m_DrawingZoomRect->bottomRight->coords().x()};
984 943
985 944 auto newAxisYRange = QCPRange{impl->m_DrawingZoomRect->topLeft->coords().y(),
986 945 impl->m_DrawingZoomRect->bottomRight->coords().y()};
987 946
988 947 impl->removeDrawingRect(plot());
989 948
990 949 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
991 950 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
992 951 impl->m_ZoomStack.push(qMakePair(axisX->range(), axisY->range()));
993 952 axisX->setRange(newAxisXRange);
994 953 axisY->setRange(newAxisYRange);
995 954
996 955 plot().replot(QCustomPlot::rpQueuedReplot);
997 956 }
998 957 }
999 958
1000 959 impl->endDrawingZone(this);
1001 960
1002 961 // Selection / Deselection
1003 962 auto isSelectionZoneMode
1004 963 = sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::SelectionZones;
1005 964 if (isSelectionZoneMode) {
1006 965 auto isMultiSelectionClick = event->modifiers().testFlag(MULTI_ZONE_SELECTION_MODIFIER);
1007 966 auto selectionZoneItemUnderCursor = impl->selectionZoneAt(event->pos(), plot());
1008 967 if (selectionZoneItemUnderCursor && event->button() == Qt::LeftButton
1009 968 && !impl->m_HasMovedMouse) {
1010 969
1011 970 auto zonesUnderCursor = impl->selectionZonesAt(event->pos(), plot());
1012 971 if (zonesUnderCursor.count() > 1) {
1013 972 // There are multiple zones under the mouse.
1014 973 // Performs the selection with a selection dialog.
1015 974 VisualizationMultiZoneSelectionDialog dialog{this};
1016 975 dialog.setZones(zonesUnderCursor);
1017 976 dialog.move(mapToGlobal(event->pos() - QPoint(dialog.width() / 2, 20)));
1018 977 dialog.activateWindow();
1019 978 dialog.raise();
1020 979 if (dialog.exec() == QDialog::Accepted) {
1021 980 auto selection = dialog.selectedZones();
1022 981
1023 982 if (!isMultiSelectionClick) {
1024 983 parentVisualizationWidget()->selectionZoneManager().clearSelection();
1025 984 }
1026 985
1027 986 for (auto it = selection.cbegin(); it != selection.cend(); ++it) {
1028 987 auto zone = it.key();
1029 988 auto isSelected = it.value();
1030 989 parentVisualizationWidget()->selectionZoneManager().setSelected(zone,
1031 990 isSelected);
1032 991
1033 992 if (isSelected) {
1034 993 // Puts the zone on top of the stack so it can be moved or resized
1035 994 impl->moveSelectionZoneOnTop(zone, plot());
1036 995 }
1037 996 }
1038 997 }
1039 998 }
1040 999 else {
1041 1000 if (!isMultiSelectionClick) {
1042 1001 parentVisualizationWidget()->selectionZoneManager().select(
1043 1002 {selectionZoneItemUnderCursor});
1044 1003 impl->moveSelectionZoneOnTop(selectionZoneItemUnderCursor, plot());
1045 1004 }
1046 1005 else {
1047 1006 parentVisualizationWidget()->selectionZoneManager().setSelected(
1048 1007 selectionZoneItemUnderCursor, !selectionZoneItemUnderCursor->selected()
1049 1008 || event->button() == Qt::RightButton);
1050 1009 }
1051 1010 }
1052 1011 }
1053 1012 else {
1054 1013 // No selection change
1055 1014 }
1056 1015 }
1057 1016 }
1058 1017
1059 1018 void VisualizationGraphWidget::onDataCacheVariableUpdated()
1060 1019 {
1061 1020 auto graphRange = ui->widget->xAxis->range();
1062 1021 auto dateTime = DateTimeRange{graphRange.lower, graphRange.upper};
1063 1022
1064 1023 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
1065 1024 auto variable = variableEntry.first;
1066 1025 qCDebug(LOG_VisualizationGraphWidget())
1067 1026 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
1068 1027 qCDebug(LOG_VisualizationGraphWidget())
1069 1028 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
1070 1029 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
1071 1030 impl->updateData(variableEntry.second, variable, variable->range());
1072 1031 }
1073 1032 }
1074 1033 }
1075 1034
1076 1035 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
1077 1036 const DateTimeRange &range)
1078 1037 {
1079 1038 auto it = impl->m_VariableToPlotMultiMap.find(variable);
1080 1039 if (it != impl->m_VariableToPlotMultiMap.end()) {
1081 1040 impl->updateData(it->second, variable, range);
1082 1041 }
1083 1042 }
@@ -1,44 +1,30
1 1 #ifndef SCIQLOP_AMDAPROVIDER_H
2 2 #define SCIQLOP_AMDAPROVIDER_H
3 3
4 4 #include "AmdaGlobal.h"
5 5
6 6 #include <Data/IDataProvider.h>
7 7
8 8 #include <QLoggingCategory>
9 9
10 10 #include <map>
11 11
12 12 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaProvider)
13 13
14 14 class QNetworkReply;
15 15 class QNetworkRequest;
16 16
17 17 /**
18 18 * @brief The AmdaProvider class is an example of how a data provider can generate data
19 19 */
20 20 class SCIQLOP_AMDA_EXPORT AmdaProvider : public IDataProvider {
21 21 Q_OBJECT
22 22 public:
23 23 explicit AmdaProvider();
24 24 std::shared_ptr<IDataProvider> clone() const override;
25 25
26 void requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters) override;
26 virtual IDataSeries *getData(const DataProviderParameters &parameters)override;
27 27
28 void requestDataAborting(QUuid acqIdentifier) override;
29
30 private:
31 void retrieveData(QUuid token, const DateTimeRange &dateTime, const QVariantHash &data);
32
33 void updateRequestProgress(QUuid acqIdentifier, std::shared_ptr<QNetworkRequest> request,
34 double progress);
35
36 std::map<QUuid, std::map<std::shared_ptr<QNetworkRequest>, double> >
37 m_AcqIdToRequestProgressMap;
38
39 private slots:
40 void onReplyDownloadProgress(QUuid acqIdentifier,
41 std::shared_ptr<QNetworkRequest> networkRequest, double progress);
42 28 };
43 29
44 30 #endif // SCIQLOP_AMDAPROVIDER_H
@@ -1,21 +1,23
1 1 #ifndef SCIQLOP_AMDARESULTPARSER_H
2 2 #define SCIQLOP_AMDARESULTPARSER_H
3 3
4 4 #include "AmdaGlobal.h"
5 5
6 6 #include <Data/DataSeriesType.h>
7 7
8 8 #include <QLoggingCategory>
9 9
10 10 #include <memory>
11 11
12 12 class IDataSeries;
13 13
14 14 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaResultParser)
15 15
16 16 struct SCIQLOP_AMDA_EXPORT AmdaResultParser {
17 17 static std::shared_ptr<IDataSeries> readTxt(const QString &filePath,
18 18 DataSeriesType valueType) noexcept;
19 static IDataSeries* readTxt(QTextStream stream,
20 DataSeriesType type)noexcept;
19 21 };
20 22
21 23 #endif // SCIQLOP_AMDARESULTPARSER_H
@@ -1,107 +1,107
1 1 #ifndef SCIQLOP_AMDARESULTPARSERHELPER_H
2 2 #define SCIQLOP_AMDARESULTPARSERHELPER_H
3 3
4 4 #include "AmdaResultParserDefs.h"
5 5
6 6 #include <QtCore/QLoggingCategory>
7 7 #include <QtCore/QString>
8 8
9 9 #include <memory>
10 10
11 11 class IDataSeries;
12 12
13 13 Q_DECLARE_LOGGING_CATEGORY(LOG_AmdaResultParserHelper)
14 14
15 15 /**
16 16 * Helper used to interpret the data of an AMDA result file and generate the corresponding data
17 17 * series.
18 18 *
19 19 * It proposes methods allowing to read line by line an AMDA file and to extract the properties
20 20 * (from the header) and the values corresponding to the data series
21 21 *
22 22 * @sa DataSeries
23 23 */
24 24 struct IAmdaResultParserHelper {
25 25 virtual ~IAmdaResultParserHelper() noexcept = default;
26 26
27 27 /// Verifies that the extracted properties are well formed and possibly applies other treatments
28 28 /// on them
29 29 /// @return true if the properties are well formed, false otherwise
30 30 virtual bool checkProperties() = 0;
31 31
32 32 /// Creates the data series from the properties and values extracted from the AMDA file.
33 33 /// @warning as the data are moved in the data series, the helper shouldn't be used after
34 34 /// calling this method
35 35 /// @return the data series created
36 virtual std::shared_ptr<IDataSeries> createSeries() = 0;
36 virtual IDataSeries* createSeries() = 0;
37 37
38 38 /// Reads a line from the AMDA file to extract a property that will be used to generate the data
39 39 /// series
40 40 /// @param line tahe line to interpret
41 41 virtual void readPropertyLine(const QString &line) = 0;
42 42
43 43 /// Reads a line from the AMDA file to extract a value that will be set in the data series
44 44 /// @param line the line to interpret
45 45 virtual void readResultLine(const QString &line) = 0;
46 46 };
47 47
48 48 /**
49 49 * Implementation of @sa IAmdaResultParserHelper for scalars
50 50 */
51 51 class ScalarParserHelper : public IAmdaResultParserHelper {
52 52 public:
53 53 bool checkProperties() override;
54 std::shared_ptr<IDataSeries> createSeries() override;
54 IDataSeries* createSeries() override;
55 55 void readPropertyLine(const QString &line) override;
56 56 void readResultLine(const QString &line) override;
57 57
58 58 private:
59 59 /// @return the reading order of the "value" columns for a result line of the AMDA file
60 60 std::vector<int> valuesIndexes() const;
61 61
62 62 Properties m_Properties{};
63 63 std::vector<double> m_XAxisData{};
64 64 std::vector<double> m_ValuesData{};
65 65 };
66 66
67 67 /**
68 68 * Implementation of @sa IAmdaResultParserHelper for spectrograms
69 69 */
70 70 class SpectrogramParserHelper : public IAmdaResultParserHelper {
71 71 public:
72 72 bool checkProperties() override;
73 std::shared_ptr<IDataSeries> createSeries() override;
73 IDataSeries* createSeries() override;
74 74 void readPropertyLine(const QString &line) override;
75 75 void readResultLine(const QString &line) override;
76 76
77 77 private:
78 78 void handleDataHoles();
79 79
80 80 Properties m_Properties{};
81 81 std::vector<double> m_XAxisData{};
82 82 std::vector<double> m_YAxisData{};
83 83 std::vector<double> m_ValuesData{};
84 84 std::vector<int> m_ValuesIndexes{};
85 85 double m_FillValue{std::numeric_limits<double>::quiet_NaN()};
86 86 };
87 87
88 88 /**
89 89 * Implementation of @sa IAmdaResultParserHelper for vectors
90 90 */
91 91 class VectorParserHelper : public IAmdaResultParserHelper {
92 92 public:
93 93 bool checkProperties() override;
94 std::shared_ptr<IDataSeries> createSeries() override;
94 IDataSeries* createSeries() override;
95 95 void readPropertyLine(const QString &line) override;
96 96 void readResultLine(const QString &line) override;
97 97
98 98 private:
99 99 /// @return the reading order of the "value" columns for a result line of the AMDA file
100 100 std::vector<int> valuesIndexes() const;
101 101
102 102 Properties m_Properties{};
103 103 std::vector<double> m_XAxisData{};
104 104 std::vector<double> m_ValuesData{};
105 105 };
106 106
107 107 #endif // SCIQLOP_AMDARESULTPARSERHELPER_H
@@ -1,274 +1,88
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 #include <QJsonDocument>
17 #include <Network/Downloader.h>
16 18
17 19 Q_LOGGING_CATEGORY(LOG_AmdaProvider, "AmdaProvider")
18 20
19 21 namespace {
20 22
21 23 /// URL format for a request on AMDA server. The parameters are as follows:
22 24 /// - %1: server URL
23 25 /// - %2: start date
24 26 /// - %3: end date
25 27 /// - %4: parameter id
26 28 /// AMDA V2: http://amdatest.irap.omp.eu/php/rest/
27 29 const auto AMDA_URL_FORMAT = QStringLiteral(
28 30 "http://%1/php/rest/"
29 31 "getParameter.php?startTime=%2&stopTime=%3&parameterID=%4&outputFormat=ASCII&"
30 32 "timeFormat=ISO8601&gzip=0");
31 33
34 const auto AMDA_URL_FORMAT_WITH_TOKEN = QStringLiteral(
35 "http://%1/php/rest/"
36 "getParameter.php?startTime=%2&stopTime=%3&parameterID=%4&outputFormat=ASCII&"
37 "timeFormat=ISO8601&gzip=0&"
38 "token=%5");
39
40 const auto AMDA_TOKEN_URL_FORMAT = QStringLiteral(
41 "http://%1/php/rest/"
42 "auth.php");
43
32 44 /// Dates format passed in the URL (e.g 2013-09-23T09:00)
33 45 const auto AMDA_TIME_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss");
34 46
35 47 /// Formats a time to a date that can be passed in URL
36 48 QString dateFormat(double sqpRange) noexcept
37 49 {
38 50 auto dateTime = DateUtils::dateTime(sqpRange);
39 51 return dateTime.toString(AMDA_TIME_FORMAT);
40 52 }
41 53
42 54
43 55 } // namespace
44 56
45 57 AmdaProvider::AmdaProvider()
46 58 {
47 qCDebug(LOG_AmdaProvider()) << tr("AmdaProvider::AmdaProvider") << QThread::currentThread();
48 if (auto app = sqpApp) {
49 auto &networkController = app->networkController();
50 connect(this, SIGNAL(requestConstructed(std::shared_ptr<QNetworkRequest>, QUuid,
51 std::function<void(QNetworkReply *, QUuid)>)),
52 &networkController,
53 SLOT(onProcessRequested(std::shared_ptr<QNetworkRequest>, QUuid,
54 std::function<void(QNetworkReply *, QUuid)>)));
55
56 59
57 connect(&sqpApp->networkController(),
58 SIGNAL(replyDownloadProgress(QUuid, std::shared_ptr<QNetworkRequest>, double)),
59 this,
60 SLOT(onReplyDownloadProgress(QUuid, std::shared_ptr<QNetworkRequest>, double)));
61 }
62 60 }
63 61
64 62 std::shared_ptr<IDataProvider> AmdaProvider::clone() const
65 63 {
66 64 // No copy is made in the clone
67 65 return std::make_shared<AmdaProvider>();
68 66 }
69 67
70 void AmdaProvider::requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters)
71 {
72 // NOTE: Try to use multithread if possible
73 const auto times = parameters.m_Times;
74 const auto data = parameters.m_Data;
75 for (const auto &dateTime : qAsConst(times)) {
76 qCDebug(LOG_AmdaProvider()) << tr("TORM AmdaProvider::requestDataLoading ") << acqIdentifier
77 << dateTime;
78 this->retrieveData(acqIdentifier, dateTime, data);
79
80
81 // TORM when AMDA will support quick asynchrone request
82 QThread::msleep(1000);
83 }
84 }
85
86 void AmdaProvider::requestDataAborting(QUuid acqIdentifier)
87 {
88 if (auto app = sqpApp) {
89 auto &networkController = app->networkController();
90 networkController.onReplyCanceled(acqIdentifier);
91 }
92 }
93
94 void AmdaProvider::onReplyDownloadProgress(QUuid acqIdentifier,
95 std::shared_ptr<QNetworkRequest> networkRequest,
96 double progress)
97 {
98 qCDebug(LOG_AmdaProvider()) << tr("onReplyDownloadProgress") << acqIdentifier
99 << networkRequest.get() << progress;
100 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
101 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
102
103 // Update the progression for the current request
104 auto requestPtr = networkRequest;
105 auto findRequest = [requestPtr](const auto &entry) { return requestPtr == entry.first; };
106
107 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
108 auto requestProgressMapEnd = requestProgressMap.end();
109 auto requestProgressMapIt
110 = std::find_if(requestProgressMap.begin(), requestProgressMapEnd, findRequest);
111
112 if (requestProgressMapIt != requestProgressMapEnd) {
113 requestProgressMapIt->second = progress;
114 }
115 else {
116 // This case can happened when a progression is send after the request has been
117 // finished.
118 // Generaly the case when aborting a request
119 qCDebug(LOG_AmdaProvider()) << tr("Can't retrieve Request in progress") << acqIdentifier
120 << networkRequest.get() << progress;
121 }
122
123 // Compute the current final progress and notify it
124 double finalProgress = 0.0;
125
126 auto fraq = requestProgressMap.size();
127
128 for (auto requestProgress : requestProgressMap) {
129 finalProgress += requestProgress.second;
130 qCDebug(LOG_AmdaProvider()) << tr("Current final progress without fraq:")
131 << finalProgress << requestProgress.second;
132 }
133
134 if (fraq > 0) {
135 finalProgress = finalProgress / fraq;
136 }
137
138 qCDebug(LOG_AmdaProvider()) << tr("Current final progress: ") << fraq << finalProgress;
139 emit dataProvidedProgress(acqIdentifier, finalProgress);
140 }
141 else {
142 // This case can happened when a progression is send after the request has been finished.
143 // Generaly the case when aborting a request
144 emit dataProvidedProgress(acqIdentifier, 100.0);
145 }
146 }
147
148 void AmdaProvider::retrieveData(QUuid token, const DateTimeRange &dateTime, const QVariantHash &data)
68 IDataSeries* AmdaProvider::getData(const DataProviderParameters &parameters)
149 69 {
150 // Retrieves product ID from data: if the value is invalid, no request is made
151 auto productId = data.value(AMDA_XML_ID_KEY).toString();
152 if (productId.isNull()) {
153 qCCritical(LOG_AmdaProvider()) << tr("Can't retrieve data: unknown product id");
154 return;
155 }
156
157 // Retrieves the data type that determines whether the expected format for the result file is
158 // scalar, vector...
70 auto range = parameters.m_Times.front();
71 auto metaData = parameters.m_Data;
72 auto productId = metaData.value(AMDA_XML_ID_KEY).toString();
159 73 auto productValueType
160 = DataSeriesTypeUtils::fromString(data.value(AMDA_DATA_TYPE_KEY).toString());
161
162 // /////////// //
163 // Creates URL //
164 // /////////// //
165
166 auto startDate = dateFormat(dateTime.m_TStart);
167 auto endDate = dateFormat(dateTime.m_TEnd);
168
169 QVariantHash urlProperties{{AMDA_SERVER_KEY, data.value(AMDA_SERVER_KEY)}};
170 auto url = QUrl{QString{AMDA_URL_FORMAT}.arg(AmdaServer::instance().url(urlProperties),
171 startDate, endDate, productId)};
172 qCInfo(LOG_AmdaProvider()) << tr("TORM AmdaProvider::retrieveData url:") << url;
173 auto tempFile = std::make_shared<QTemporaryFile>();
174
175 // LAMBDA
176 auto httpDownloadFinished = [this, dateTime, tempFile,
177 productValueType](QNetworkReply *reply, QUuid dataId) noexcept {
178
179 // Don't do anything if the reply was abort
180 if (reply->error() == QNetworkReply::NoError) {
181
182 if (tempFile) {
183 auto replyReadAll = reply->readAll();
184 if (!replyReadAll.isEmpty()) {
185 tempFile->write(replyReadAll);
186 }
187 tempFile->close();
188
189 // Parse results file
190 if (auto dataSeries
191 = AmdaResultParser::readTxt(tempFile->fileName(), productValueType)) {
192 emit dataProvided(dataId, dataSeries, dateTime);
193 }
194 else {
195 /// @todo ALX : debug
196 emit dataProvidedFailed(dataId);
197 }
198 }
199 m_AcqIdToRequestProgressMap.erase(dataId);
200 }
201 else {
202 qCCritical(LOG_AmdaProvider()) << tr("httpDownloadFinished ERROR");
203 emit dataProvidedFailed(dataId);
204 }
205
206 };
207 auto httpFinishedLambda
208 = [this, httpDownloadFinished, tempFile](QNetworkReply *reply, QUuid dataId) noexcept {
209
210 // Don't do anything if the reply was abort
211 if (reply->error() == QNetworkReply::NoError) {
212 auto downloadFileUrl = QUrl{QString{reply->readAll()}.trimmed()};
213
214 qCInfo(LOG_AmdaProvider())
215 << tr("TORM AmdaProvider::retrieveData downloadFileUrl:") << downloadFileUrl;
216 // Executes request for downloading file //
217
218 // Creates destination file
219 if (tempFile->open()) {
220 // Executes request and store the request for progression
221 auto request = std::make_shared<QNetworkRequest>(downloadFileUrl);
222 updateRequestProgress(dataId, request, 0.0);
223 emit requestConstructed(request, dataId, httpDownloadFinished);
224 }
225 else {
226 emit dataProvidedFailed(dataId);
227 }
228 }
229 else {
230 qCCritical(LOG_AmdaProvider()) << tr("httpFinishedLambda ERROR");
231 m_AcqIdToRequestProgressMap.erase(dataId);
232 emit dataProvidedFailed(dataId);
233 }
234 };
235
236 // //////////////// //
237 // Executes request //
238 // //////////////// //
239
240 auto request = std::make_shared<QNetworkRequest>(url);
241 qCDebug(LOG_AmdaProvider()) << tr("First Request creation") << request.get();
242 updateRequestProgress(token, request, 0.0);
243
244 emit requestConstructed(request, token, httpFinishedLambda);
74 = DataSeriesTypeUtils::fromString(metaData.value(AMDA_DATA_TYPE_KEY).toString());
75 auto startDate = dateFormat(range.m_TStart);
76 auto endDate = dateFormat(range.m_TEnd);
77 QVariantHash urlProperties{{AMDA_SERVER_KEY, metaData.value(AMDA_SERVER_KEY)}};
78 auto token_url = QString{AMDA_TOKEN_URL_FORMAT}.arg(AmdaServer::instance().url(urlProperties));
79 auto response = Downloader::get(token_url);
80 auto url = QString{AMDA_URL_FORMAT_WITH_TOKEN}.arg(AmdaServer::instance().url(urlProperties),
81 startDate, endDate, productId, QString(response.data()));
82 response = Downloader::get(url);
83 auto test = QJsonDocument::fromJson(response.data());
84 url = test["dataFileURLs"].toString();
85 response = Downloader::get(url);
86 return AmdaResultParser::readTxt(QTextStream{response.data()},productValueType);
245 87 }
246 88
247 void AmdaProvider::updateRequestProgress(QUuid acqIdentifier,
248 std::shared_ptr<QNetworkRequest> request, double progress)
249 {
250 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress request") << request.get();
251 auto acqIdToRequestProgressMapIt = m_AcqIdToRequestProgressMap.find(acqIdentifier);
252 if (acqIdToRequestProgressMapIt != m_AcqIdToRequestProgressMap.end()) {
253 auto &requestProgressMap = acqIdToRequestProgressMapIt->second;
254 auto requestProgressMapIt = requestProgressMap.find(request);
255 if (requestProgressMapIt != requestProgressMap.end()) {
256 requestProgressMapIt->second = progress;
257 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new progress for request")
258 << acqIdentifier << request.get() << progress;
259 }
260 else {
261 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new request") << acqIdentifier
262 << request.get() << progress;
263 acqIdToRequestProgressMapIt->second.insert(std::make_pair(request, progress));
264 }
265 }
266 else {
267 qCDebug(LOG_AmdaProvider()) << tr("updateRequestProgress new acqIdentifier")
268 << acqIdentifier << request.get() << progress;
269 auto requestProgressMap = std::map<std::shared_ptr<QNetworkRequest>, double>{};
270 requestProgressMap.insert(std::make_pair(request, progress));
271 m_AcqIdToRequestProgressMap.insert(
272 std::make_pair(acqIdentifier, std::move(requestProgressMap)));
273 }
274 }
@@ -1,133 +1,141
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 #include <Data/IDataSeries.h>
8 9
9 10 Q_LOGGING_CATEGORY(LOG_AmdaResultParser, "AmdaResultParser")
10 11
11 12 namespace {
12 13
13 14 /// Message in result file when the file was not found on server
14 15 const auto FILE_NOT_FOUND_MESSAGE = QStringLiteral("Not Found");
15 16
16 17 /// Checks if a line is a comment line
17 18 bool isCommentLine(const QString &line)
18 19 {
19 20 return line.startsWith("#");
20 21 }
21 22
22 23 /**
23 24 * Creates helper that will be used to read AMDA file, according to the type passed as parameter
24 25 * @param valueType the type of values expected in the AMDA file (scalars, vectors, spectrograms...)
25 26 * @return the helper created
26 27 */
27 28 std::unique_ptr<IAmdaResultParserHelper> createHelper(DataSeriesType valueType)
28 29 {
29 30 switch (valueType) {
30 31 case DataSeriesType::SCALAR:
31 32 return std::make_unique<ScalarParserHelper>();
32 33 case DataSeriesType::SPECTROGRAM:
33 34 return std::make_unique<SpectrogramParserHelper>();
34 35 case DataSeriesType::VECTOR:
35 36 return std::make_unique<VectorParserHelper>();
36 37 case DataSeriesType::UNKNOWN:
37 38 // Invalid case
38 39 break;
39 40 }
40 41
41 42 // Invalid cases
42 43 qCCritical(LOG_AmdaResultParser())
43 44 << QObject::tr("Can't create helper to read result file: unsupported type");
44 45 return nullptr;
45 46 }
46 47
47 48 /**
48 49 * Reads properties of the stream passed as parameter
49 50 * @param helper the helper used to read properties line by line
50 51 * @param stream the stream to read
51 52 */
52 53 void readProperties(IAmdaResultParserHelper &helper, QTextStream &stream)
53 54 {
54 55 // Searches properties in the comment lines (as long as the reading has not reached the data)
55 56 // AMDA V2: while (stream.readLineInto(&line) && !line.contains(DATA_HEADER_REGEX)) {
56 57 QString line{};
57 58 while (stream.readLineInto(&line) && isCommentLine(line)) {
58 59 helper.readPropertyLine(line);
59 60 }
60 61 }
61 62
62 63 /**
63 64 * Reads results of the stream passed as parameter
64 65 * @param helper the helper used to read results line by line
65 66 * @param stream the stream to read
66 67 */
67 68 void readResults(IAmdaResultParserHelper &helper, QTextStream &stream)
68 69 {
69 70 QString line{};
70 71
71 72 // Skip comment lines
72 73 while (stream.readLineInto(&line) && isCommentLine(line)) {
73 74 }
74 75
75 76 if (!stream.atEnd()) {
76 77 do {
77 78 helper.readResultLine(line);
78 79 } while (stream.readLineInto(&line));
79 80 }
80 81 }
81 82
82 83 } // namespace
83 84
84 85 std::shared_ptr<IDataSeries> AmdaResultParser::readTxt(const QString &filePath,
85 86 DataSeriesType type) noexcept
86 87 {
87 88 if (type == DataSeriesType::UNKNOWN) {
88 89 qCCritical(LOG_AmdaResultParser())
89 90 << QObject::tr("Can't retrieve AMDA data: the type of values to be read is unknown");
90 91 return nullptr;
91 92 }
92 93
93 94 QFile file{filePath};
94 95
95 96 if (!file.open(QFile::ReadOnly | QIODevice::Text)) {
96 97 qCCritical(LOG_AmdaResultParser())
97 98 << QObject::tr("Can't retrieve AMDA data from file %1: %2")
98 99 .arg(filePath, file.errorString());
99 100 return nullptr;
100 101 }
101 102
102 QTextStream stream{&file};
103 return std::shared_ptr<IDataSeries>{AmdaResultParser::readTxt(QTextStream{&file},type)};
104 }
105
106 IDataSeries *AmdaResultParser::readTxt(QTextStream stream,
107 DataSeriesType type) noexcept
108 {
109 if (type == DataSeriesType::UNKNOWN)
110 {
111 return nullptr;
112 }
103 113
104 114 // Checks if the file was found on the server
105 115 auto firstLine = stream.readLine();
106 if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0) {
107 qCCritical(LOG_AmdaResultParser())
108 << QObject::tr("Can't retrieve AMDA data from file %1: file was not found on server")
109 .arg(filePath);
116 if (firstLine.compare(FILE_NOT_FOUND_MESSAGE) == 0)
117 {
110 118 return nullptr;
111 119 }
112 120
113 121 auto helper = createHelper(type);
114 122 Q_ASSERT(helper != nullptr);
115 123
116 124 // Reads header file to retrieve properties
117 125 stream.seek(0); // returns to the beginning of the file
118 126 readProperties(*helper, stream);
119 127
120 128 // Checks properties
121 129 if (helper->checkProperties()) {
122 130 // Reads results
123 131 // AMDA V2: remove line
124 132 stream.seek(0); // returns to the beginning of the file
125 133 readResults(*helper, stream);
126 134
127 135 // Creates data series
128 136 return helper->createSeries();
129 137 }
130 138 else {
131 139 return nullptr;
132 140 }
133 141 }
@@ -1,432 +1,432
1 1 #include "AmdaResultParserHelper.h"
2 2
3 3 #include <Common/DateUtils.h>
4 4 #include <Common/SortUtils.h>
5 5
6 6 #include <Data/DataSeriesUtils.h>
7 7 #include <Data/ScalarSeries.h>
8 8 #include <Data/SpectrogramSeries.h>
9 9 #include <Data/Unit.h>
10 10 #include <Data/VectorSeries.h>
11 11
12 12 #include <QtCore/QDateTime>
13 13 #include <QtCore/QRegularExpression>
14 14
15 15 #include <functional>
16 16
17 17 Q_LOGGING_CATEGORY(LOG_AmdaResultParserHelper, "AmdaResultParserHelper")
18 18
19 19 namespace {
20 20
21 21 // ///////// //
22 22 // Constants //
23 23 // ///////// //
24 24
25 25 /// Separator between values in a result line
26 26 const auto RESULT_LINE_SEPARATOR = QRegularExpression{QStringLiteral("\\s+")};
27 27
28 28 /// Format for dates in result files
29 29 const auto DATE_FORMAT = QStringLiteral("yyyy-MM-ddThh:mm:ss.zzz");
30 30
31 31 // /////// //
32 32 // Methods //
33 33 // /////// //
34 34
35 35 /**
36 36 * Checks that the properties contain a specific unit and that this unit is valid
37 37 * @param properties the properties map in which to search unit
38 38 * @param key the key to search for the unit in the properties
39 39 * @param errorMessage the error message to log in case the unit is invalid
40 40 * @return true if the unit is valid, false it it's invalid or was not found in the properties
41 41 */
42 42 bool checkUnit(const Properties &properties, const QString &key, const QString &errorMessage)
43 43 {
44 44 auto unit = properties.value(key).value<Unit>();
45 45 if (unit.m_Name.isEmpty()) {
46 46 qCWarning(LOG_AmdaResultParserHelper()) << errorMessage;
47 47 return false;
48 48 }
49 49
50 50 return true;
51 51 }
52 52
53 53 QDateTime dateTimeFromString(const QString &stringDate) noexcept
54 54 {
55 55 #if QT_VERSION >= QT_VERSION_CHECK(5, 8, 0)
56 56 return QDateTime::fromString(stringDate, Qt::ISODateWithMs);
57 57 #else
58 58 return QDateTime::fromString(stringDate, DATE_FORMAT);
59 59 #endif
60 60 }
61 61
62 62 /// Converts a string date to a double date
63 63 /// @return a double that represents the date in seconds, NaN if the string date can't be converted
64 64 double doubleDate(const QString &stringDate) noexcept
65 65 {
66 66 // Format: yyyy-MM-ddThh:mm:ss.zzz
67 67 auto dateTime = dateTimeFromString(stringDate);
68 68 dateTime.setTimeSpec(Qt::UTC);
69 69 return dateTime.isValid() ? DateUtils::secondsSinceEpoch(dateTime)
70 70 : std::numeric_limits<double>::quiet_NaN();
71 71 }
72 72
73 73 /**
74 74 * Reads a line from the AMDA file and tries to extract a x-axis data and value data from it
75 75 * @param xAxisData the vector in which to store the x-axis data extracted
76 76 * @param valuesData the vector in which to store the value extracted
77 77 * @param line the line to read to extract the property
78 78 * @param valuesIndexes indexes of insertion of read values. For example, if the line contains three
79 79 * columns of values, and valuesIndexes are {2, 0, 1}, the value of the third column will be read
80 80 * and inserted first, then the value of the first column, and finally the value of the second
81 81 * column.
82 82 * @param fillValue value that tags an invalid data. For example, if fillValue is -1 and a read
83 83 * value is -1, then this value is considered as invalid and converted to NaN
84 84 */
85 85 void tryReadResult(std::vector<double> &xAxisData, std::vector<double> &valuesData,
86 86 const QString &line, const std::vector<int> &valuesIndexes,
87 87 double fillValue = std::numeric_limits<double>::quiet_NaN())
88 88 {
89 89 auto lineData = line.split(RESULT_LINE_SEPARATOR, QString::SkipEmptyParts);
90 90
91 91 // Checks that the line contains expected number of values + x-axis value
92 92 if (static_cast<size_t>(lineData.size()) == valuesIndexes.size() + 1) {
93 93 // X : the data is converted from date to double (in secs)
94 94 auto x = doubleDate(lineData.at(0));
95 95
96 96 // Adds result only if x is valid. Then, if value is invalid, it is set to NaN
97 97 if (!std::isnan(x)) {
98 98 xAxisData.push_back(x);
99 99
100 100 // Values
101 101 for (auto valueIndex : valuesIndexes) {
102 102 bool valueOk;
103 103 // we use valueIndex + 1 to skip column 0 (x-axis value)
104 104 auto value = lineData.at(valueIndex + 1).toDouble(&valueOk);
105 105
106 106 if (!valueOk) {
107 107 qCWarning(LOG_AmdaResultParserHelper())
108 108 << QObject::tr(
109 109 "Value from (line %1, column %2) is invalid and will be "
110 110 "converted to NaN")
111 111 .arg(line, valueIndex);
112 112 value = std::numeric_limits<double>::quiet_NaN();
113 113 }
114 114
115 115 // Handles fill value
116 116 if (!std::isnan(fillValue) && !std::isnan(value) && fillValue == value) {
117 117 value = std::numeric_limits<double>::quiet_NaN();
118 118 }
119 119
120 120 valuesData.push_back(value);
121 121 }
122 122 }
123 123 else {
124 124 qCWarning(LOG_AmdaResultParserHelper())
125 125 << QObject::tr("Can't retrieve results from line %1: x is invalid").arg(line);
126 126 }
127 127 }
128 128 else {
129 129 qCWarning(LOG_AmdaResultParserHelper())
130 130 << QObject::tr("Can't retrieve results from line %1: invalid line").arg(line);
131 131 }
132 132 }
133 133
134 134 /**
135 135 * Reads a line from the AMDA file and tries to extract a property from it
136 136 * @param properties the properties map in which to put the property extracted from the line
137 137 * @param key the key to which the property is added in the properties map
138 138 * @param line the line to read to extract the property
139 139 * @param regexes the expected regexes to extract the property. If the line matches one regex, the
140 140 * property is generated
141 141 * @param fun the function used to generate the property
142 142 * @return true if the property could be generated, false if the line does not match the regex, or
143 143 * if a property has already been generated for the key
144 144 */
145 145 template <typename GeneratePropertyFun>
146 146 bool tryReadProperty(Properties &properties, const QString &key, const QString &line,
147 147 const std::vector<QRegularExpression> &regexes, GeneratePropertyFun fun)
148 148 {
149 149 if (properties.contains(key)) {
150 150 return false;
151 151 }
152 152
153 153 // Searches for a match among all possible regexes
154 154 auto hasMatch = false;
155 155 for (auto regexIt = regexes.cbegin(), end = regexes.cend(); regexIt != end && !hasMatch;
156 156 ++regexIt) {
157 157 auto match = regexIt->match(line);
158 158 auto hasMatch = match.hasMatch();
159 159 if (hasMatch) {
160 160 properties.insert(key, fun(match));
161 161 }
162 162 }
163 163
164 164 return hasMatch;
165 165 }
166 166
167 167 /**
168 168 * Reads a line from the AMDA file and tries to extract a data from it. Date is converted to double
169 169 * @sa tryReadProperty()
170 170 */
171 171 bool tryReadDate(Properties &properties, const QString &key, const QString &line,
172 172 const std::vector<QRegularExpression> &regexes, bool timeUnit = false)
173 173 {
174 174 return tryReadProperty(properties, key, line, regexes, [timeUnit](const auto &match) {
175 175 return QVariant::fromValue(doubleDate(match.captured(1)));
176 176 });
177 177 }
178 178
179 179 /**
180 180 * Reads a line from the AMDA file and tries to extract a double from it
181 181 * @sa tryReadProperty()
182 182 */
183 183 bool tryReadDouble(Properties &properties, const QString &key, const QString &line,
184 184 const std::vector<QRegularExpression> &regexes)
185 185 {
186 186 return tryReadProperty(properties, key, line, regexes, [](const auto &match) {
187 187 bool ok;
188 188
189 189 // If the value can't be converted to double, it is set to NaN
190 190 auto doubleValue = match.captured(1).toDouble(&ok);
191 191 if (!ok) {
192 192 doubleValue = std::numeric_limits<double>::quiet_NaN();
193 193 }
194 194
195 195 return QVariant::fromValue(doubleValue);
196 196 });
197 197 }
198 198
199 199 /**
200 200 * Reads a line from the AMDA file and tries to extract a vector of doubles from it
201 201 * @param sep the separator of double values in the line
202 202 * @sa tryReadProperty()
203 203 */
204 204 bool tryReadDoubles(Properties &properties, const QString &key, const QString &line,
205 205 const std::vector<QRegularExpression> &regexes,
206 206 const QString &sep = QStringLiteral(","))
207 207 {
208 208 return tryReadProperty(properties, key, line, regexes, [sep](const auto &match) {
209 209 std::vector<double> doubleValues{};
210 210
211 211 // If the value can't be converted to double, it is set to NaN
212 212 auto values = match.captured(1).split(sep);
213 213 for (auto value : values) {
214 214 bool ok;
215 215
216 216 auto doubleValue = value.toDouble(&ok);
217 217 if (!ok) {
218 218 doubleValue = std::numeric_limits<double>::quiet_NaN();
219 219 }
220 220
221 221 doubleValues.push_back(doubleValue);
222 222 }
223 223
224 224 return QVariant::fromValue(doubleValues);
225 225 });
226 226 }
227 227
228 228 /**
229 229 * Reads a line from the AMDA file and tries to extract a unit from it
230 230 * @sa tryReadProperty()
231 231 */
232 232 bool tryReadUnit(Properties &properties, const QString &key, const QString &line,
233 233 const std::vector<QRegularExpression> &regexes, bool timeUnit = false)
234 234 {
235 235 return tryReadProperty(properties, key, line, regexes, [timeUnit](const auto &match) {
236 236 return QVariant::fromValue(Unit{match.captured(1), timeUnit});
237 237 });
238 238 }
239 239
240 240 } // namespace
241 241
242 242 // ////////////////// //
243 243 // ScalarParserHelper //
244 244 // ////////////////// //
245 245
246 246 bool ScalarParserHelper::checkProperties()
247 247 {
248 248 return checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY,
249 249 QObject::tr("The x-axis unit could not be found in the file"));
250 250 }
251 251
252 std::shared_ptr<IDataSeries> ScalarParserHelper::createSeries()
252 IDataSeries* ScalarParserHelper::createSeries()
253 253 {
254 return std::make_shared<ScalarSeries>(std::move(m_XAxisData), std::move(m_ValuesData),
254 return new ScalarSeries(std::move(m_XAxisData), std::move(m_ValuesData),
255 255 m_Properties.value(X_AXIS_UNIT_PROPERTY).value<Unit>(),
256 256 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>());
257 257 }
258 258
259 259 void ScalarParserHelper::readPropertyLine(const QString &line)
260 260 {
261 261 tryReadUnit(m_Properties, X_AXIS_UNIT_PROPERTY, line,
262 262 {DEFAULT_X_AXIS_UNIT_REGEX, ALTERNATIVE_X_AXIS_UNIT_REGEX}, true);
263 263 }
264 264
265 265 void ScalarParserHelper::readResultLine(const QString &line)
266 266 {
267 267 tryReadResult(m_XAxisData, m_ValuesData, line, valuesIndexes());
268 268 }
269 269
270 270 std::vector<int> ScalarParserHelper::valuesIndexes() const
271 271 {
272 272 // Only one value to read
273 273 static auto result = std::vector<int>{0};
274 274 return result;
275 275 }
276 276
277 277 // /////////////////////// //
278 278 // SpectrogramParserHelper //
279 279 // /////////////////////// //
280 280
281 281 bool SpectrogramParserHelper::checkProperties()
282 282 {
283 283 // Generates y-axis data from bands extracted (take the middle of the intervals)
284 284 auto minBands = m_Properties.value(MIN_BANDS_PROPERTY).value<std::vector<double> >();
285 285 auto maxBands = m_Properties.value(MAX_BANDS_PROPERTY).value<std::vector<double> >();
286 286
287 287 if (minBands.size() < 2 || minBands.size() != maxBands.size()) {
288 288 qCWarning(LOG_AmdaResultParserHelper()) << QObject::tr(
289 289 "Can't generate y-axis data from bands extracted: bands intervals are invalid");
290 290 return false;
291 291 }
292 292
293 293 std::transform(
294 294 minBands.begin(), minBands.end(), maxBands.begin(), std::back_inserter(m_YAxisData),
295 295 [](const auto &minValue, const auto &maxValue) { return (minValue + maxValue) / 2.; });
296 296
297 297 // Generates values indexes, i.e. the order in which each value will be retrieved (in ascending
298 298 // order of the associated bands)
299 299 m_ValuesIndexes = SortUtils::sortPermutation(m_YAxisData, std::less<double>());
300 300
301 301 // Sorts y-axis data accoding to the ascending order
302 302 m_YAxisData = SortUtils::sort(m_YAxisData, 1, m_ValuesIndexes);
303 303
304 304 // Sets fill value
305 305 m_FillValue = m_Properties.value(FILL_VALUE_PROPERTY).value<double>();
306 306
307 307 return true;
308 308 }
309 309
310 std::shared_ptr<IDataSeries> SpectrogramParserHelper::createSeries()
310 IDataSeries* SpectrogramParserHelper::createSeries()
311 311 {
312 312 // Before creating the series, we handle its data holes
313 313 handleDataHoles();
314 314
315 return std::make_shared<SpectrogramSeries>(
315 return new SpectrogramSeries(
316 316 std::move(m_XAxisData), std::move(m_YAxisData), std::move(m_ValuesData),
317 317 Unit{"t", true}, // x-axis unit is always a time unit
318 318 m_Properties.value(Y_AXIS_UNIT_PROPERTY).value<Unit>(),
319 319 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>(),
320 320 m_Properties.value(MIN_SAMPLING_PROPERTY).value<double>());
321 321 }
322 322
323 323 void SpectrogramParserHelper::readPropertyLine(const QString &line)
324 324 {
325 325 // Set of functions to test on the line to generate a property. If a function is valid (i.e. a
326 326 // property has been generated for the line), the line is treated as processed and the other
327 327 // functions are not called
328 328 std::vector<std::function<bool()> > functions{
329 329 // values unit
330 330 [&] {
331 331 return tryReadUnit(m_Properties, VALUES_UNIT_PROPERTY, line,
332 332 {SPECTROGRAM_VALUES_UNIT_REGEX});
333 333 },
334 334 // y-axis unit
335 335 [&] {
336 336 return tryReadUnit(m_Properties, Y_AXIS_UNIT_PROPERTY, line,
337 337 {SPECTROGRAM_Y_AXIS_UNIT_REGEX});
338 338 },
339 339 // min sampling
340 340 [&] {
341 341 return tryReadDouble(m_Properties, MIN_SAMPLING_PROPERTY, line,
342 342 {SPECTROGRAM_MIN_SAMPLING_REGEX});
343 343 },
344 344 // max sampling
345 345 [&] {
346 346 return tryReadDouble(m_Properties, MAX_SAMPLING_PROPERTY, line,
347 347 {SPECTROGRAM_MAX_SAMPLING_REGEX});
348 348 },
349 349 // fill value
350 350 [&] {
351 351 return tryReadDouble(m_Properties, FILL_VALUE_PROPERTY, line,
352 352 {SPECTROGRAM_FILL_VALUE_REGEX});
353 353 },
354 354 // min bounds of each band
355 355 [&] {
356 356 return tryReadDoubles(m_Properties, MIN_BANDS_PROPERTY, line,
357 357 {SPECTROGRAM_MIN_BANDS_REGEX});
358 358 },
359 359 // max bounds of each band
360 360 [&] {
361 361 return tryReadDoubles(m_Properties, MAX_BANDS_PROPERTY, line,
362 362 {SPECTROGRAM_MAX_BANDS_REGEX});
363 363 },
364 364 // start time of data
365 365 [&] {
366 366 return tryReadDate(m_Properties, START_TIME_PROPERTY, line,
367 367 {SPECTROGRAM_START_TIME_REGEX});
368 368 },
369 369 // end time of data
370 370 [&] {
371 371 return tryReadDate(m_Properties, END_TIME_PROPERTY, line, {SPECTROGRAM_END_TIME_REGEX});
372 372 }};
373 373
374 374 for (auto function : functions) {
375 375 // Stops at the first function that is valid
376 376 if (function()) {
377 377 return;
378 378 }
379 379 }
380 380 }
381 381
382 382 void SpectrogramParserHelper::readResultLine(const QString &line)
383 383 {
384 384 tryReadResult(m_XAxisData, m_ValuesData, line, m_ValuesIndexes, m_FillValue);
385 385 }
386 386
387 387 void SpectrogramParserHelper::handleDataHoles()
388 388 {
389 389 // Fills data holes according to the max resolution found in the AMDA file
390 390 auto resolution = m_Properties.value(MAX_SAMPLING_PROPERTY).value<double>();
391 391 auto fillValue = m_Properties.value(FILL_VALUE_PROPERTY).value<double>();
392 392 auto minBound = m_Properties.value(START_TIME_PROPERTY).value<double>();
393 393 auto maxBound = m_Properties.value(END_TIME_PROPERTY).value<double>();
394 394
395 395 DataSeriesUtils::fillDataHoles(m_XAxisData, m_ValuesData, resolution, fillValue, minBound,
396 396 maxBound);
397 397 }
398 398
399 399 // ////////////////// //
400 400 // VectorParserHelper //
401 401 // ////////////////// //
402 402
403 403 bool VectorParserHelper::checkProperties()
404 404 {
405 405 return checkUnit(m_Properties, X_AXIS_UNIT_PROPERTY,
406 406 QObject::tr("The x-axis unit could not be found in the file"));
407 407 }
408 408
409 std::shared_ptr<IDataSeries> VectorParserHelper::createSeries()
409 IDataSeries* VectorParserHelper::createSeries()
410 410 {
411 return std::make_shared<VectorSeries>(std::move(m_XAxisData), std::move(m_ValuesData),
411 return new VectorSeries(std::move(m_XAxisData), std::move(m_ValuesData),
412 412 m_Properties.value(X_AXIS_UNIT_PROPERTY).value<Unit>(),
413 413 m_Properties.value(VALUES_UNIT_PROPERTY).value<Unit>());
414 414 }
415 415
416 416 void VectorParserHelper::readPropertyLine(const QString &line)
417 417 {
418 418 tryReadUnit(m_Properties, X_AXIS_UNIT_PROPERTY, line,
419 419 {DEFAULT_X_AXIS_UNIT_REGEX, ALTERNATIVE_X_AXIS_UNIT_REGEX}, true);
420 420 }
421 421
422 422 void VectorParserHelper::readResultLine(const QString &line)
423 423 {
424 424 tryReadResult(m_XAxisData, m_ValuesData, line, valuesIndexes());
425 425 }
426 426
427 427 std::vector<int> VectorParserHelper::valuesIndexes() const
428 428 {
429 429 // 3 values to read, in order in the file (x, y, z)
430 430 static auto result = std::vector<int>{0, 1, 2};
431 431 return result;
432 432 }
@@ -1,126 +1,126
1 1 /*------------------------------------------------------------------------------
2 2 -- This file is a part of the SciQLOP Software
3 3 -- Copyright (C) 2018, 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 <string>
23 23 #include <sstream>
24 24 #include <memory>
25 25
26 26 #include <pybind11/pybind11.h>
27 27 #include <pybind11/operators.h>
28 28 #include <pybind11/embed.h>
29 29 #include <pybind11/numpy.h>
30 30 #include <pybind11/chrono.h>
31 31
32 32 #include <SqpApplication.h>
33 33 #include <Variable/VariableController2.h>
34 34 #include <Time/TimeController.h>
35 35 #include <Data/DateTimeRange.h>
36 36 #include <Data/DataSeriesType.h>
37 37 #include <Common/DateUtils.h>
38 38 #include <Variable/Variable.h>
39 39 #include <Data/ScalarSeries.h>
40 40 #include <Data/VectorSeries.h>
41 41
42 42 #include <AmdaProvider.h>
43 43 #include <AmdaResultParser.h>
44 44
45 45 //#include <QDate>
46 46 //#include <QTime>
47 47 //#include <QUuid>
48 48 //#include <QString>
49 49 #include <QFile>
50 50
51 51 #include <pywrappers_common.h>
52 52 #include <CoreWrappers.h>
53 53
54 54 #include "PyTestAmdaWrapper.h"
55 55
56 56
57 57 using namespace std::chrono;
58 58
59 59
60 60
61 61
62 62 PYBIND11_MODULE(pytestamda, m){
63 63
64 64 int argc = 0;
65 65 char ** argv=nullptr;
66 66 SqpApplication::setOrganizationName("LPP");
67 67 SqpApplication::setOrganizationDomain("lpp.fr");
68 68 SqpApplication::setApplicationName("SciQLop");
69 69 static SqpApplication app(argc, argv);
70 70
71 71 auto qtmod = py::module::import("sciqlopqt");
72 72 auto sciqlopmod = py::module::import("pysciqlopcore");
73 73
74 74 m.doc() = "hello";
75 75
76 76 // py::class_<VariableController>(m, "VariableController")
77 77 // .def_static("createVariable",[](const QString &name,
78 78 // std::shared_ptr<IDataProvider> provider, const DateTimeRange& range){
79 79 // return sqpApp->variableController().createVariable(name, {{"dataType", "vector"}, {"xml:id", "c1_b"}}, provider, range);
80 80 // })
81 81 // .def_static("hasPendingDownloads",
82 82 // [](){return sqpApp->variableController().hasPendingDownloads();}
83 83 // )
84 84 // .def_static("addSynchronizationGroup",
85 85 // [](QUuid uuid){sqpApp->variableController().onAddSynchronizationGroupId(uuid);}
86 86 // )
87 87 // .def_static("removeSynchronizationGroup",
88 88 // [](QUuid uuid){sqpApp->variableController().onRemoveSynchronizationGroupId(uuid);}
89 89 // )
90 90 // .def_static("synchronizeVar",
91 91 // [](std::shared_ptr<Variable> variable, QUuid uuid){sqpApp->variableController().onAddSynchronized(variable, uuid);}
92 92 // )
93 93 // .def_static("deSynchronizeVar",
94 94 // [](std::shared_ptr<Variable> variable, QUuid uuid){sqpApp->variableController().desynchronize(variable, uuid);}
95 95 // )
96 96 // .def_static("deleteVariable",
97 97 // [](std::shared_ptr<Variable> variable){
98 98 // sqpApp->variableController().deleteVariable(variable);}
99 99 // )
100 100 // .def_static("update_range",[](std::shared_ptr<Variable> variable, const DateTimeRange &range, bool synchronise){
101 101 // sqpApp->variableController().onRequestDataLoading({variable}, range, synchronise);
102 102 // })
103 103 // .def_static("wait_for_downloads",[](){
104 104 // while (sqpApp->variableController().hasPendingDownloads()) {
105 105 // usleep(100);
106 106 // }
107 107 // });
108 108
109 109 py::class_<TimeController>(m,"TimeController")
110 110 .def_static("setTime", [](DateTimeRange range){sqpApp->timeController().setDateTimeRange(range);});
111 111
112 112
113 113 auto amda_provider = std::make_shared<AmdaProvider>();
114 114 m.def("amda_provider",[amda_provider](){return amda_provider;}, py::return_value_policy::copy);
115 115
116 116 py::class_<AmdaProvider, std::shared_ptr<AmdaProvider>, IDataProvider>(m, "AmdaProvider");
117 117
118 py::class_<AmdaResultParser>(m, "AmdaResultParser")
119 .def_static("readTxt", AmdaResultParser::readTxt)
120 .def("readScalarTxt", [](const QString& path){
121 return std::dynamic_pointer_cast<ScalarSeries>(AmdaResultParser::readTxt(path, DataSeriesType::SCALAR));
122 }, py::return_value_policy::copy);
118 // py::class_<AmdaResultParser>(m, "AmdaResultParser")
119 // .def_static("readTxt", AmdaResultParser::readTxt)
120 // .def("readScalarTxt", [](const QString& path){
121 // return std::dynamic_pointer_cast<ScalarSeries>(AmdaResultParser::readTxt(path, DataSeriesType::SCALAR));
122 // }, py::return_value_policy::copy);
123 123
124 124
125 125 }
126 126
@@ -1,29 +1,29
1 1 include_directories(include)
2 2 FILE (GLOB_RECURSE mockplugin_SRCS
3 3 include/*.h
4 4 src/*.cpp
5 5 resources/*.qrc
6 6 )
7 7
8 8 add_definitions(-DQT_PLUGIN)
9 9 add_definitions(-DPLUGIN_JSON_FILE_PATH="${CMAKE_CURRENT_SOURCE_DIR}/resources/mockplugin.json")
10 10 if(NOT BUILD_SHARED_LIBS)
11 11 add_definitions(-DQT_STATICPLUGIN)
12 12 endif()
13 13
14 14 add_library(mockplugin ${mockplugin_SRCS})
15 15 SET_TARGET_PROPERTIES(mockplugin PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS TRUE)
16 16
17 17 target_link_libraries(mockplugin sciqlopgui)
18 18
19 19 install(TARGETS mockplugin
20 20 ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/SciQlop
21 21 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/SciQlop
22 22 RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
23 23
24 24 include(sciqlop_tests)
25 25
26 26 add_definitions(-DMOCKPLUGIN_TESTS_RESOURCES_DIR="${CMAKE_CURRENT_LIST_DIR}/tests-resources")
27 27
28 declare_test(TestCosinusAcquisition TestCosinusAcquisition tests/TestCosinusAcquisition.cpp "mockplugin;Qt5::Test")
28 #declare_test(TestCosinusAcquisition TestCosinusAcquisition tests/TestCosinusAcquisition.cpp "mockplugin;Qt5::Test")
29 29
@@ -1,45 +1,31
1 1 #ifndef SCIQLOP_COSINUSPROVIDER_H
2 2 #define SCIQLOP_COSINUSPROVIDER_H
3 3
4 4 #include "MockPluginGlobal.h"
5 5
6 6 #include <Data/IDataProvider.h>
7 7
8 8 #include <QLoggingCategory>
9 9 #include <QUuid>
10 10
11 11 #include <QHash>
12 Q_DECLARE_LOGGING_CATEGORY(LOG_CosinusProvider)
13 12
14 13 /**
15 14 * @brief The CosinusProvider class is an example of how a data provider can generate data
16 15 */
17 16 class SCIQLOP_MOCKPLUGIN_EXPORT CosinusProvider : public IDataProvider {
18 17 public:
19 18 std::shared_ptr<IDataProvider> clone() const override;
20 19
21 /// @sa IDataProvider::requestDataLoading(). The current impl isn't thread safe.
22 void requestDataLoading(QUuid acqIdentifier, const DataProviderParameters &parameters) override;
23
24
25 20 virtual IDataSeries* getData(const DataProviderParameters &parameters) override;
26 21
27 /// @sa IDataProvider::requestDataAborting(). The current impl isn't thread safe.
28 void requestDataAborting(QUuid acqIdentifier) override;
29
30
31 /// Provide data
32 std::shared_ptr<IDataSeries> provideDataSeries(const DateTimeRange &dataRangeRequested,
33 const QVariantHash &data);
34
35
36 22 private:
37 23 std::shared_ptr<IDataSeries>
38 24 retrieveData(QUuid acqIdentifier, const DateTimeRange &dataRangeRequested, const QVariantHash &data);
39 25
40 26 IDataSeries* _generate(const DateTimeRange &range, const QVariantHash &metaData);
41 27
42 28 QHash<QUuid, bool> m_VariableToEnableProvider;
43 29 };
44 30
45 31 #endif // SCIQLOP_COSINUSPROVIDER_H
@@ -1,332 +1,199
1 1 #include "CosinusProvider.h"
2 2 #include "MockDefs.h"
3 3
4 4 #include <Data/DataProviderParameters.h>
5 5 #include <Data/ScalarSeries.h>
6 6 #include <Data/SpectrogramSeries.h>
7 7 #include <Data/VectorSeries.h>
8 8
9 9 #include <cmath>
10 10 #include <set>
11 11
12 12 #include <QFuture>
13 13 #include <QThread>
14 14 #include <QtConcurrent/QtConcurrent>
15 15
16 Q_LOGGING_CATEGORY(LOG_CosinusProvider, "CosinusProvider")
17 16
18 17 namespace {
19 18
20 19 /// Number of bands generated for a spectrogram
21 20 const auto SPECTROGRAM_NUMBER_BANDS = 30;
22 21
23 22 /// Bands for which to generate NaN values for a spectrogram
24 23 const auto SPECTROGRAM_NAN_BANDS = std::set<int>{1, 3, 10, 20};
25 24
26 25 /// Bands for which to generate zeros for a spectrogram
27 26 const auto SPECTROGRAM_ZERO_BANDS = std::set<int>{2, 15, 19, 29};
28 27
29 28 /// Abstract cosinus type
30 29 struct ICosinusType {
31 30 virtual ~ICosinusType() = default;
32 31 /// @return the number of components generated for the type
33 32 virtual std::size_t componentCount() const = 0;
34 33 /// @return the data series created for the type
35 34 virtual IDataSeries* createDataSeries(std::vector<double> xAxisData,
36 35 std::vector<double> valuesData) const = 0;
37 36 /// Generates values (one value per component)
38 37 /// @param x the x-axis data used to generate values
39 38 /// @param values the vector in which to insert the generated values
40 39 /// @param dataIndex the index of insertion of the generated values
41 40 ///
42 41 virtual void generateValues(double x, std::vector<double> &values, int dataIndex) const = 0;
43 42 };
44 43
45 44 struct ScalarCosinus : public ICosinusType {
46 45 std::size_t componentCount() const override { return 1; }
47 46
48 47 IDataSeries* createDataSeries(std::vector<double> xAxisData,
49 48 std::vector<double> valuesData) const override
50 49 {
51 50 return new ScalarSeries(std::move(xAxisData), std::move(valuesData),
52 51 Unit{QStringLiteral("t"), true}, Unit{});
53 52 }
54 53
55 54 void generateValues(double x, std::vector<double> &values, int dataIndex) const override
56 55 {
57 56 values[dataIndex] = std::cos(x);
58 57 }
59 58 };
60 59
61 60 struct SpectrogramCosinus : public ICosinusType {
62 61 /// Ctor with y-axis
63 62 explicit SpectrogramCosinus(std::vector<double> yAxisData, Unit yAxisUnit, Unit valuesUnit)
64 63 : m_YAxisData{std::move(yAxisData)},
65 64 m_YAxisUnit{std::move(yAxisUnit)},
66 65 m_ValuesUnit{std::move(valuesUnit)}
67 66 {
68 67 }
69 68
70 69 std::size_t componentCount() const override { return m_YAxisData.size(); }
71 70
72 71 IDataSeries* createDataSeries(std::vector<double> xAxisData,
73 72 std::vector<double> valuesData) const override
74 73 {
75 74 return new SpectrogramSeries(
76 75 std::move(xAxisData), m_YAxisData, std::move(valuesData),
77 76 Unit{QStringLiteral("t"), true}, m_YAxisUnit, m_ValuesUnit);
78 77 }
79 78
80 79 void generateValues(double x, std::vector<double> &values, int dataIndex) const override
81 80 {
82 81 auto componentCount = this->componentCount();
83 82 for (int i = 0; i < componentCount; ++i) {
84 83 auto y = m_YAxisData[i];
85 84
86 85 double value;
87 86
88 87 if (SPECTROGRAM_ZERO_BANDS.find(y) != SPECTROGRAM_ZERO_BANDS.end()) {
89 88 value = 0.;
90 89 }
91 90 else if (SPECTROGRAM_NAN_BANDS.find(y) != SPECTROGRAM_NAN_BANDS.end()) {
92 91 value = std::numeric_limits<double>::quiet_NaN();
93 92 }
94 93 else {
95 94 // Generates value for non NaN/zero bands
96 95 auto r = 3 * std::sqrt(x * x + y * y) + 1e-2;
97 96 value = 2 * x * (std::cos(r + 2) / r - std::sin(r + 2) / r);
98 97 }
99 98
100 99 values[componentCount * dataIndex + i] = value;
101 100 }
102 101 }
103 102
104 103 std::vector<double> m_YAxisData;
105 104 Unit m_YAxisUnit;
106 105 Unit m_ValuesUnit;
107 106 };
108 107
109 108 struct VectorCosinus : public ICosinusType {
110 109 std::size_t componentCount() const override { return 3; }
111 110
112 111 IDataSeries* createDataSeries(std::vector<double> xAxisData,
113 112 std::vector<double> valuesData) const override
114 113 {
115 114 return new VectorSeries(std::move(xAxisData), std::move(valuesData),
116 115 Unit{QStringLiteral("t"), true}, Unit{});
117 116 }
118 117
119 118 void generateValues(double x, std::vector<double> &values, int dataIndex) const override
120 119 {
121 120 // Generates value for each component: cos(x), cos(x)/2, cos(x)/3
122 121 auto xValue = std::cos(x);
123 122 auto componentCount = this->componentCount();
124 123 for (auto i = 0; i < componentCount; ++i) {
125 124 values[componentCount * dataIndex + i] = xValue / (i + 1);
126 125 }
127 126 }
128 127 };
129 128
130 129 /// Converts string to cosinus type
131 130 /// @return the cosinus type if the string could be converted, nullptr otherwise
132 131 std::unique_ptr<ICosinusType> cosinusType(const QString &type) noexcept
133 132 {
134 133 if (type.compare(QStringLiteral("scalar"), Qt::CaseInsensitive) == 0) {
135 134 return std::make_unique<ScalarCosinus>();
136 135 }
137 136 else if (type.compare(QStringLiteral("spectrogram"), Qt::CaseInsensitive) == 0) {
138 137 // Generates default y-axis data for spectrogram [0., 1., 2., ...]
139 138 std::vector<double> yAxisData(SPECTROGRAM_NUMBER_BANDS);
140 139 std::iota(yAxisData.begin(), yAxisData.end(), 0.);
141 140
142 141 return std::make_unique<SpectrogramCosinus>(std::move(yAxisData), Unit{"eV"},
143 142 Unit{"eV/(cm^2-s-sr-eV)"});
144 143 }
145 144 else if (type.compare(QStringLiteral("vector"), Qt::CaseInsensitive) == 0) {
146 145 return std::make_unique<VectorCosinus>();
147 146 }
148 147 else {
149 148 return nullptr;
150 149 }
151 150 }
152 151
153 152 } // namespace
154 153
155 154 std::shared_ptr<IDataProvider> CosinusProvider::clone() const
156 155 {
157 156 // No copy is made in clone
158 157 return std::make_shared<CosinusProvider>();
159 158 }
160 159
161 std::shared_ptr<IDataSeries> CosinusProvider::retrieveData(QUuid acqIdentifier,
162 const DateTimeRange &dataRangeRequested,
163 const QVariantHash &data)
164 {
165 // TODO: Add Mutex
166 auto dataIndex = 0;
167
168 // Retrieves cosinus type
169 auto typeVariant = data.value(COSINUS_TYPE_KEY, COSINUS_TYPE_DEFAULT_VALUE);
170 if (!typeVariant.canConvert<QString>()) {
171 qCCritical(LOG_CosinusProvider()) << tr("Can't retrieve data: invalid type");
172 return nullptr;
173 }
174
175 auto type = cosinusType(typeVariant.toString());
176 if (!type) {
177 qCCritical(LOG_CosinusProvider()) << tr("Can't retrieve data: unknown type");
178 return nullptr;
179 }
180
181 // Retrieves frequency
182 auto freqVariant = data.value(COSINUS_FREQUENCY_KEY, COSINUS_FREQUENCY_DEFAULT_VALUE);
183 if (!freqVariant.canConvert<double>()) {
184 qCCritical(LOG_CosinusProvider()) << tr("Can't retrieve data: invalid frequency");
185 return nullptr;
186 }
187
188 // Gets the timerange from the parameters
189 double freq = freqVariant.toDouble();
190 double start = std::ceil(dataRangeRequested.m_TStart * freq);
191 double end = std::floor(dataRangeRequested.m_TEnd * freq);
192
193 // We assure that timerange is valid
194 if (end < start) {
195 std::swap(start, end);
196 }
197
198 // Generates scalar series containing cosinus values (one value per second, end value is
199 // included)
200 auto dataCount = end - start + 1;
201
202 // Number of components (depending on the cosinus type)
203 auto componentCount = type->componentCount();
204
205 auto xAxisData = std::vector<double>{};
206 xAxisData.resize(dataCount);
207
208 auto valuesData = std::vector<double>{};
209 valuesData.resize(dataCount * componentCount);
210
211 int progress = 0;
212 auto progressEnd = dataCount;
213 for (auto time = start; time <= end; ++time, ++dataIndex) {
214 auto it = m_VariableToEnableProvider.find(acqIdentifier);
215 if (it != m_VariableToEnableProvider.end() && it.value()) {
216 const auto x = time / freq;
217
218 xAxisData[dataIndex] = x;
219
220 // Generates values (depending on the type)
221 type->generateValues(x, valuesData, dataIndex);
222
223 // progression
224 int currentProgress = (time - start) * 100.0 / progressEnd;
225 if (currentProgress != progress) {
226 progress = currentProgress;
227
228 emit dataProvidedProgress(acqIdentifier, progress);
229 qCDebug(LOG_CosinusProvider()) << "TORM: CosinusProvider::retrieveData"
230 << QThread::currentThread()->objectName()
231 << progress;
232 // NOTE: Try to use multithread if possible
233 }
234 }
235 else {
236 if (!it.value()) {
237 qCDebug(LOG_CosinusProvider())
238 << "CosinusProvider::retrieveData: ARRET De l'acquisition detecté"
239 << end - time;
240 }
241 }
242 }
243 if (progress != 100) {
244 // We can close progression beacause all data has been retrieved
245 emit dataProvidedProgress(acqIdentifier, 100);
246 }
247 return std::shared_ptr<IDataSeries>(type->createDataSeries(std::move(xAxisData), std::move(valuesData)));
248 }
249
250 160 IDataSeries *CosinusProvider::_generate(const DateTimeRange &range, const QVariantHash &metaData)
251 161 {
252 162 auto dataIndex = 0;
253 163
254 164 // Retrieves cosinus type
255 165 auto typeVariant = metaData.value(COSINUS_TYPE_KEY, COSINUS_TYPE_DEFAULT_VALUE);
256 166 auto type = cosinusType(typeVariant.toString());
257 167 auto freqVariant = metaData.value(COSINUS_FREQUENCY_KEY, COSINUS_FREQUENCY_DEFAULT_VALUE);
258 168 double freq = freqVariant.toDouble();
259 169 double start = std::ceil(range.m_TStart * freq);
260 170 double end = std::floor(range.m_TEnd * freq);
261 171 if (end < start) {
262 172 std::swap(start, end);
263 173 }
264 174 std::size_t dataCount = static_cast<std::size_t>(end - start + 1);
265 175 std::size_t componentCount = type->componentCount();
266 176
267 177 auto xAxisData = std::vector<double>{};
268 178 xAxisData.resize(dataCount);
269 179
270 180 auto valuesData = std::vector<double>{};
271 181 valuesData.resize(dataCount * componentCount);
272 182
273 183 int progress = 0;
274 184 auto progressEnd = dataCount;
275 185 for (auto time = start; time <= end; ++time, ++dataIndex)
276 186 {
277 187 const auto x = time / freq;
278 188 xAxisData[dataIndex] = x;
279 189 // Generates values (depending on the type)
280 190 type->generateValues(x, valuesData, dataIndex);
281 191 }
282 192 return type->createDataSeries(std::move(xAxisData), std::move(valuesData));
283 193 }
284 194
285 void CosinusProvider::requestDataLoading(QUuid acqIdentifier,
286 const DataProviderParameters &parameters)
287 {
288 // TODO: Add Mutex
289 m_VariableToEnableProvider[acqIdentifier] = true;
290 qCDebug(LOG_CosinusProvider()) << "TORM: CosinusProvider::requestDataLoading"
291 << QThread::currentThread()->objectName();
292 // NOTE: Try to use multithread if possible
293 const auto times = parameters.m_Times;
294
295 for (const auto &dateTime : qAsConst(times)) {
296 if (m_VariableToEnableProvider[acqIdentifier]) {
297 auto scalarSeries = this->retrieveData(acqIdentifier, dateTime, parameters.m_Data);
298 emit dataProvided(acqIdentifier, scalarSeries, dateTime);
299 }
300 }
301 }
302
303 195 IDataSeries* CosinusProvider::getData(const DataProviderParameters &parameters)
304 196 {
305 197 return _generate(parameters.m_Times.front(),parameters.m_Data);
306 198 }
307 199
308
309 void CosinusProvider::requestDataAborting(QUuid acqIdentifier)
310 {
311 qCDebug(LOG_CosinusProvider()) << "CosinusProvider::requestDataAborting" << acqIdentifier
312 << QThread::currentThread()->objectName();
313 auto it = m_VariableToEnableProvider.find(acqIdentifier);
314 if (it != m_VariableToEnableProvider.end()) {
315 it.value() = false;
316 }
317 else {
318 qCDebug(LOG_CosinusProvider())
319 << tr("Aborting progression of inexistant identifier detected !!!");
320 }
321 }
322
323 std::shared_ptr<IDataSeries> CosinusProvider::provideDataSeries(const DateTimeRange &dataRangeRequested,
324 const QVariantHash &data)
325 {
326 auto uid = QUuid::createUuid();
327 m_VariableToEnableProvider[uid] = true;
328 auto dataSeries = this->retrieveData(uid, dataRangeRequested, data);
329
330 m_VariableToEnableProvider.remove(uid);
331 return dataSeries;
332 }
1 NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now