##// END OF EJS Templates
Merge branch 'feature/GraphCleaning' into develop
Alexandre Leroux -
r731:1cb481e11c97 merge
parent child
Show More
@@ -0,0 +1,24
1 #ifndef SCIQLOP_IGRAPHSYNCHRONIZER_H
2 #define SCIQLOP_IGRAPHSYNCHRONIZER_H
3
4 class VisualizationGraphWidget;
5
6 /**
7 * @brief The IVisualizationSynchronizer interface represents a delegate used to manage the
8 * synchronization between graphs, applying them processes or properties to ensure their
9 * synchronization
10 */
11 class IGraphSynchronizer {
12
13 public:
14 virtual ~IGraphSynchronizer() = default;
15
16 /**
17 * Adds a graph as a new synchronized element, and sets its properties so that its
18 * synchronization is maintained with all other graphs managed by the synchonizer
19 * @param graph the graph to add in the synchronization
20 */
21 virtual void addGraph(VisualizationGraphWidget &graph) const = 0;
22 };
23
24 #endif // SCIQLOP_IGRAPHSYNCHRONIZER_H
@@ -0,0 +1,26
1 #ifndef SCIQLOP_QCUSTOMPLOTSYNCHRONIZER_H
2 #define SCIQLOP_QCUSTOMPLOTSYNCHRONIZER_H
3
4 #include "Visualization/IGraphSynchronizer.h"
5
6 #include <Common/spimpl.h>
7
8 /**
9 * @brief The QCustomPlotSynchronizer class is an implementation of IGraphSynchronizer that handles
10 * graphs using QCustomPlot elements
11 * @sa IGraphSynchronizer
12 * @sa QCustomPlot
13 */
14 class QCustomPlotSynchronizer : public IGraphSynchronizer {
15 public:
16 explicit QCustomPlotSynchronizer();
17
18 /// @sa IGraphSynchronizer::addGraph()
19 virtual void addGraph(VisualizationGraphWidget &graph) const override;
20
21 private:
22 class QCustomPlotSynchronizerPrivate;
23 spimpl::unique_impl_ptr<QCustomPlotSynchronizerPrivate> impl;
24 };
25
26 #endif // SCIQLOP_QCUSTOMPLOTSYNCHRONIZER_H
1 NO CONTENT: new file 100644, binary diff hidden
1 NO CONTENT: new file 100644, binary diff hidden
@@ -0,0 +1,30
1 #include "Visualization/QCustomPlotSynchronizer.h"
2
3 #include "Visualization/VisualizationGraphWidget.h"
4 #include "Visualization/qcustomplot.h"
5
6 struct QCustomPlotSynchronizer::QCustomPlotSynchronizerPrivate {
7 explicit QCustomPlotSynchronizerPrivate()
8 : m_MarginGroup{std::make_unique<QCPMarginGroup>(nullptr)}
9 {
10 }
11
12 /// Sets the same margin sides for all added plot elements
13 std::unique_ptr<QCPMarginGroup> m_MarginGroup;
14 };
15
16 QCustomPlotSynchronizer::QCustomPlotSynchronizer()
17 : impl{spimpl::make_unique_impl<QCustomPlotSynchronizerPrivate>()}
18 {
19 }
20
21 void QCustomPlotSynchronizer::addGraph(VisualizationGraphWidget &graph) const
22 {
23 // Adds all plot elements of the graph to the margin group: all these elements will then have
24 // the same margin sides
25 auto &plot = graph.plot();
26 for (auto axisRect : plot.axisRects()) {
27 // Sames margin sides at left and right
28 axisRect->setMarginGroup(QCP::msLeft | QCP::msRight, impl->m_MarginGroup.get());
29 }
30 }
@@ -1,20 +1,31
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
3 3
4 4 #include <Common/spimpl.h>
5 5
6 6 class QCustomPlot;
7 7 class QMouseEvent;
8 class Unit;
9 class VisualizationGraphWidget;
8 10
9 11 class VisualizationGraphRenderingDelegate {
10 12 public:
11 explicit VisualizationGraphRenderingDelegate(QCustomPlot &plot);
13 /// Ctor
14 /// @param graphWidget the graph widget to which the delegate is associated
15 /// @remarks the graph widget must exist throughout the life cycle of the delegate
16 explicit VisualizationGraphRenderingDelegate(VisualizationGraphWidget &graphWidget);
12 17
13 18 void onMouseMove(QMouseEvent *event) noexcept;
14 19
20 /// Sets properties of the plot's axes
21 void setAxesProperties(const Unit &xAxisUnit, const Unit &valuesUnit) noexcept;
22
23 /// Shows or hides graph overlay (name, close button, etc.)
24 void showGraphOverlay(bool show) noexcept;
25
15 26 private:
16 27 class VisualizationGraphRenderingDelegatePrivate;
17 28 spimpl::unique_impl_ptr<VisualizationGraphRenderingDelegatePrivate> impl;
18 29 };
19 30
20 31 #endif // SCIQLOP_VISUALIZATIONGRAPHRENDERINGDELEGATE_H
@@ -1,86 +1,94
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
3 3
4 4 #include "Visualization/IVisualizationWidget.h"
5 5
6 6 #include <QLoggingCategory>
7 7 #include <QWidget>
8 8
9 9 #include <memory>
10 10
11 11 #include <Common/spimpl.h>
12 12
13 13 Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationGraphWidget)
14 14
15 15 class QCPRange;
16 class QCustomPlot;
16 17 class SqpRange;
17 18 class Variable;
18 19
19 20 namespace Ui {
20 21 class VisualizationGraphWidget;
21 22 } // namespace Ui
22 23
23 24 class VisualizationGraphWidget : public QWidget, public IVisualizationWidget {
24 25 Q_OBJECT
25 26
27 friend class QCustomPlotSynchronizer;
28 friend class VisualizationGraphRenderingDelegate;
29
26 30 public:
27 31 explicit VisualizationGraphWidget(const QString &name = {}, QWidget *parent = 0);
28 32 virtual ~VisualizationGraphWidget();
29 33
30 34 /// If acquisition isn't enable, requestDataLoading signal cannot be emit
31 35 void enableAcquisition(bool enable);
32 36
33 37 void addVariable(std::shared_ptr<Variable> variable, SqpRange range);
34 38
35 39 /// Removes a variable from the graph
36 40 void removeVariable(std::shared_ptr<Variable> variable) noexcept;
37 41
38 42 void setRange(std::shared_ptr<Variable> variable, const SqpRange &range);
39 43 void setYRange(const SqpRange &range);
40 44 SqpRange graphRange() const noexcept;
41 45 void setGraphRange(const SqpRange &range);
42 46
43 47 // IVisualizationWidget interface
44 48 void accept(IVisualizationWidgetVisitor *visitor) override;
45 49 bool canDrop(const Variable &variable) const override;
46 50 bool contains(const Variable &variable) const override;
47 51 QString name() const override;
48 52
49 53
50 54 signals:
51 55 void synchronize(const SqpRange &range, const SqpRange &oldRange);
52 56 void requestDataLoading(QVector<std::shared_ptr<Variable> > variable, const SqpRange &range,
53 57 const SqpRange &oldRange, bool synchronise);
54 58
55
56 59 void variableAdded(std::shared_ptr<Variable> var);
57 60
61 protected:
62 void enterEvent(QEvent *event) override;
63 void leaveEvent(QEvent *event) override;
64
65 QCustomPlot &plot() noexcept;
58 66
59 67 private:
60 68 Ui::VisualizationGraphWidget *ui;
61 69
62 70 class VisualizationGraphWidgetPrivate;
63 71 spimpl::unique_impl_ptr<VisualizationGraphWidgetPrivate> impl;
64 72
65 73 private slots:
66 74 /// Slot called when right clicking on the graph (displays a menu)
67 75 void onGraphMenuRequested(const QPoint &pos) noexcept;
68 76
69 77 /// Rescale the X axe to range parameter
70 78 void onRangeChanged(const QCPRange &t1, const QCPRange &t2);
71 79
72 80 /// Slot called when a mouse move was made
73 81 void onMouseMove(QMouseEvent *event) noexcept;
74 82 /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done
75 83 void onMouseWheel(QWheelEvent *event) noexcept;
76 84 /// Slot called when a mouse press was made, to activate the calibration of a graph
77 85 void onMousePress(QMouseEvent *event) noexcept;
78 86 /// Slot called when a mouse release was made, to deactivate the calibration of a graph
79 87 void onMouseRelease(QMouseEvent *event) noexcept;
80 88
81 89 void onDataCacheVariableUpdated();
82 90
83 91 void onUpdateVarDisplaying(std::shared_ptr<Variable> variable, const SqpRange &range);
84 92 };
85 93
86 94 #endif // SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
@@ -1,75 +1,76
1 1
2 2 gui_moc_headers = [
3 3 'include/DataSource/DataSourceWidget.h',
4 4 'include/Settings/SqpSettingsDialog.h',
5 5 'include/Settings/SqpSettingsGeneralWidget.h',
6 6 'include/SidePane/SqpSidePane.h',
7 7 'include/SqpApplication.h',
8 8 'include/TimeWidget/TimeWidget.h',
9 9 'include/Variable/VariableInspectorWidget.h',
10 10 'include/Visualization/qcustomplot.h',
11 11 'include/Visualization/VisualizationGraphWidget.h',
12 12 'include/Visualization/VisualizationTabWidget.h',
13 13 'include/Visualization/VisualizationWidget.h',
14 14 'include/Visualization/VisualizationZoneWidget.h'
15 15 ]
16 16
17 17 gui_ui_files = [
18 18 'ui/DataSource/DataSourceWidget.ui',
19 19 'ui/Settings/SqpSettingsDialog.ui',
20 20 'ui/Settings/SqpSettingsGeneralWidget.ui',
21 21 'ui/SidePane/SqpSidePane.ui',
22 22 'ui/TimeWidget/TimeWidget.ui',
23 23 'ui/Variable/VariableInspectorWidget.ui',
24 24 'ui/Variable/VariableMenuHeaderWidget.ui',
25 25 'ui/Visualization/VisualizationGraphWidget.ui',
26 26 'ui/Visualization/VisualizationTabWidget.ui',
27 27 'ui/Visualization/VisualizationWidget.ui',
28 28 'ui/Visualization/VisualizationZoneWidget.ui'
29 29 ]
30 30
31 31 gui_qresources = ['resources/sqpguiresources.qrc']
32 32
33 33 gui_moc_files = qt5.preprocess(moc_headers : gui_moc_headers,
34 34 ui_files : gui_ui_files,
35 35 qresources : gui_qresources)
36 36
37 37 gui_sources = [
38 38 'src/SqpApplication.cpp',
39 39 'src/Common/ColorUtils.cpp',
40 40 'src/DataSource/DataSourceTreeWidgetItem.cpp',
41 41 'src/DataSource/DataSourceTreeWidgetHelper.cpp',
42 42 'src/DataSource/DataSourceWidget.cpp',
43 43 'src/Settings/SqpSettingsDialog.cpp',
44 44 'src/Settings/SqpSettingsGeneralWidget.cpp',
45 45 'src/SidePane/SqpSidePane.cpp',
46 46 'src/TimeWidget/TimeWidget.cpp',
47 47 'src/Variable/VariableInspectorWidget.cpp',
48 48 'src/Variable/VariableMenuHeaderWidget.cpp',
49 49 'src/Visualization/VisualizationGraphHelper.cpp',
50 50 'src/Visualization/VisualizationGraphRenderingDelegate.cpp',
51 51 'src/Visualization/VisualizationGraphWidget.cpp',
52 52 'src/Visualization/VisualizationTabWidget.cpp',
53 53 'src/Visualization/VisualizationWidget.cpp',
54 54 'src/Visualization/VisualizationZoneWidget.cpp',
55 55 'src/Visualization/qcustomplot.cpp',
56 'src/Visualization/QCustomPlotSynchronizer.cpp',
56 57 'src/Visualization/operations/GenerateVariableMenuOperation.cpp',
57 58 'src/Visualization/operations/MenuBuilder.cpp',
58 59 'src/Visualization/operations/RemoveVariableOperation.cpp',
59 60 'src/Visualization/operations/RescaleAxeOperation.cpp'
60 61 ]
61 62
62 63 gui_inc = include_directories(['include'])
63 64
64 65 sciqlop_gui_lib = library('sciqlopgui',
65 66 gui_sources,
66 67 gui_moc_files,
67 68 include_directories : [gui_inc],
68 69 dependencies : [ qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core],
69 70 install : true
70 71 )
71 72
72 73 sciqlop_gui = declare_dependency(link_with : sciqlop_gui_lib,
73 74 include_directories : gui_inc,
74 75 dependencies : [qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core])
75 76
@@ -1,14 +1,16
1 1 <RCC>
2 2 <qresource prefix="/">
3 3 <file>icones/dataSourceComponent.png</file>
4 4 <file>icones/dataSourceNode.png</file>
5 5 <file>icones/dataSourceProduct.png</file>
6 6 <file>icones/dataSourceRoot.png</file>
7 7 <file>icones/delete.png</file>
8 <file>icones/down.png</file>
8 9 <file>icones/openInspector.png</file>
9 10 <file>icones/next.png</file>
10 11 <file>icones/plot.png</file>
11 12 <file>icones/previous.png</file>
12 13 <file>icones/unplot.png</file>
14 <file>icones/up.png</file>
13 15 </qresource>
14 16 </RCC>
@@ -1,247 +1,204
1 1 #include "Visualization/VisualizationGraphHelper.h"
2 2 #include "Visualization/qcustomplot.h"
3 3
4 4 #include <Common/ColorUtils.h>
5 5
6 6 #include <Data/ScalarSeries.h>
7 7 #include <Data/VectorSeries.h>
8 8
9 9 #include <Variable/Variable.h>
10 10
11 11 Q_LOGGING_CATEGORY(LOG_VisualizationGraphHelper, "VisualizationGraphHelper")
12 12
13 13 namespace {
14 14
15 15 class SqpDataContainer : public QCPGraphDataContainer {
16 16 public:
17 17 void appendGraphData(const QCPGraphData &data) { mData.append(data); }
18 18 };
19 19
20
21 /// Format for datetimes on a axis
22 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
23
24 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
25 /// non-time data
26 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis)
27 {
28 if (isTimeAxis) {
29 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
30 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
31 dateTicker->setDateTimeSpec(Qt::UTC);
32
33 return dateTicker;
34 }
35 else {
36 // default ticker
37 return QSharedPointer<QCPAxisTicker>::create();
38 }
39 }
40
41 /// Sets axes properties according to the properties of a data series. Not thread safe
42 template <int Dim>
43 void setAxesProperties(const DataSeries<Dim> &dataSeries, QCustomPlot &plot) noexcept
44 {
45 /// @todo : for the moment, no control is performed on the axes: the units and the tickers
46 /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph
47 auto setAxisProperties = [](auto axis, const auto &unit) {
48 // label (unit name)
49 axis->setLabel(unit.m_Name);
50
51 // ticker (depending on the type of unit)
52 axis->setTicker(axisTicker(unit.m_TimeUnit));
53 };
54 setAxisProperties(plot.xAxis, dataSeries.xAxisUnit());
55 setAxisProperties(plot.yAxis, dataSeries.valuesUnit());
56 }
57
58 20 /**
59 21 * Struct used to create plottables, depending on the type of the data series from which to create
60 22 * them
61 23 * @tparam T the data series' type
62 24 * @remarks Default implementation can't create plottables
63 25 */
64 26 template <typename T, typename Enabled = void>
65 27 struct PlottablesCreator {
66 28 static PlottablesMap createPlottables(T &, QCustomPlot &)
67 29 {
68 30 qCCritical(LOG_DataSeries())
69 31 << QObject::tr("Can't create plottables: unmanaged data series type");
70 32 return {};
71 33 }
72 34 };
73 35
74 36 /**
75 37 * Specialization of PlottablesCreator for scalars and vectors
76 38 * @sa ScalarSeries
77 39 * @sa VectorSeries
78 40 */
79 41 template <typename T>
80 42 struct PlottablesCreator<T,
81 43 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
82 44 or std::is_base_of<VectorSeries, T>::value> > {
83 45 static PlottablesMap createPlottables(T &dataSeries, QCustomPlot &plot)
84 46 {
85 47 PlottablesMap result{};
86 48
87 49 // Gets the number of components of the data series
88 50 dataSeries.lockRead();
89 51 auto componentCount = dataSeries.valuesData()->componentCount();
90 52 dataSeries.unlock();
91 53
92 54 auto colors = ColorUtils::colors(Qt::blue, Qt::red, componentCount);
93 55
94 56 // For each component of the data series, creates a QCPGraph to add to the plot
95 57 for (auto i = 0; i < componentCount; ++i) {
96 58 auto graph = plot.addGraph();
97 59 graph->setPen(QPen{colors.at(i)});
98 60
99 61 result.insert({i, graph});
100 62 }
101 63
102 // Axes properties
103 dataSeries.lockRead();
104 setAxesProperties(dataSeries, plot);
105 dataSeries.unlock();
106
107 64 plot.replot();
108 65
109 66 return result;
110 67 }
111 68 };
112 69
113 70 /**
114 71 * Struct used to update plottables, depending on the type of the data series from which to update
115 72 * them
116 73 * @tparam T the data series' type
117 74 * @remarks Default implementation can't update plottables
118 75 */
119 76 template <typename T, typename Enabled = void>
120 77 struct PlottablesUpdater {
121 78 static void updatePlottables(T &, PlottablesMap &, const SqpRange &, bool)
122 79 {
123 80 qCCritical(LOG_DataSeries())
124 81 << QObject::tr("Can't update plottables: unmanaged data series type");
125 82 }
126 83 };
127 84
128 85 /**
129 86 * Specialization of PlottablesUpdater for scalars and vectors
130 87 * @sa ScalarSeries
131 88 * @sa VectorSeries
132 89 */
133 90 template <typename T>
134 91 struct PlottablesUpdater<T,
135 92 typename std::enable_if_t<std::is_base_of<ScalarSeries, T>::value
136 93 or std::is_base_of<VectorSeries, T>::value> > {
137 94 static void updatePlottables(T &dataSeries, PlottablesMap &plottables, const SqpRange &range,
138 95 bool rescaleAxes)
139 96 {
140 97
141 98 // For each plottable to update, resets its data
142 99 std::map<int, QSharedPointer<SqpDataContainer> > dataContainers{};
143 100 for (const auto &plottable : plottables) {
144 101 if (auto graph = dynamic_cast<QCPGraph *>(plottable.second)) {
145 102 auto dataContainer = QSharedPointer<SqpDataContainer>::create();
146 103 graph->setData(dataContainer);
147 104
148 105 dataContainers.insert({plottable.first, dataContainer});
149 106 }
150 107 }
151 108 dataSeries.lockRead();
152 109
153 110 // - Gets the data of the series included in the current range
154 111 // - Updates each plottable by adding, for each data item, a point that takes x-axis data
155 112 // and value data. The correct value is retrieved according to the index of the component
156 113 auto subDataIts = dataSeries.xAxisRange(range.m_TStart, range.m_TEnd);
157 114 for (auto it = subDataIts.first; it != subDataIts.second; ++it) {
158 115 for (const auto &dataContainer : dataContainers) {
159 116 auto componentIndex = dataContainer.first;
160 117 dataContainer.second->appendGraphData(
161 118 QCPGraphData(it->x(), it->value(componentIndex)));
162 119 }
163 120 }
164 121
165 122 dataSeries.unlock();
166 123
167 124 if (!plottables.empty()) {
168 125 auto plot = plottables.begin()->second->parentPlot();
169 126
170 127 if (rescaleAxes) {
171 128 plot->rescaleAxes();
172 129 }
173 130
174 131 plot->replot();
175 132 }
176 133 }
177 134 };
178 135
179 136 /**
180 137 * Helper used to create/update plottables
181 138 */
182 139 struct IPlottablesHelper {
183 140 virtual ~IPlottablesHelper() noexcept = default;
184 141 virtual PlottablesMap create(QCustomPlot &plot) const = 0;
185 142 virtual void update(PlottablesMap &plottables, const SqpRange &range,
186 143 bool rescaleAxes = false) const = 0;
187 144 };
188 145
189 146 /**
190 147 * Default implementation of IPlottablesHelper, which takes data series to create/update plottables
191 148 * @tparam T the data series' type
192 149 */
193 150 template <typename T>
194 151 struct PlottablesHelper : public IPlottablesHelper {
195 152 explicit PlottablesHelper(T &dataSeries) : m_DataSeries{dataSeries} {}
196 153
197 154 PlottablesMap create(QCustomPlot &plot) const override
198 155 {
199 156 return PlottablesCreator<T>::createPlottables(m_DataSeries, plot);
200 157 }
201 158
202 159 void update(PlottablesMap &plottables, const SqpRange &range, bool rescaleAxes) const override
203 160 {
204 161 PlottablesUpdater<T>::updatePlottables(m_DataSeries, plottables, range, rescaleAxes);
205 162 }
206 163
207 164 T &m_DataSeries;
208 165 };
209 166
210 167 /// Creates IPlottablesHelper according to a data series
211 168 std::unique_ptr<IPlottablesHelper> createHelper(std::shared_ptr<IDataSeries> dataSeries) noexcept
212 169 {
213 170 if (auto scalarSeries = std::dynamic_pointer_cast<ScalarSeries>(dataSeries)) {
214 171 return std::make_unique<PlottablesHelper<ScalarSeries> >(*scalarSeries);
215 172 }
216 173 else if (auto vectorSeries = std::dynamic_pointer_cast<VectorSeries>(dataSeries)) {
217 174 return std::make_unique<PlottablesHelper<VectorSeries> >(*vectorSeries);
218 175 }
219 176 else {
220 177 return std::make_unique<PlottablesHelper<IDataSeries> >(*dataSeries);
221 178 }
222 179 }
223 180
224 181 } // namespace
225 182
226 183 PlottablesMap VisualizationGraphHelper::create(std::shared_ptr<Variable> variable,
227 184 QCustomPlot &plot) noexcept
228 185 {
229 186 if (variable) {
230 187 auto helper = createHelper(variable->dataSeries());
231 188 auto plottables = helper->create(plot);
232 189 return plottables;
233 190 }
234 191 else {
235 192 qCDebug(LOG_VisualizationGraphHelper())
236 193 << QObject::tr("Can't create graph plottables : the variable is null");
237 194 return PlottablesMap{};
238 195 }
239 196 }
240 197
241 198 void VisualizationGraphHelper::updateData(PlottablesMap &plottables,
242 199 std::shared_ptr<IDataSeries> dataSeries,
243 200 const SqpRange &dateTime)
244 201 {
245 202 auto helper = createHelper(dataSeries);
246 203 helper->update(plottables, dateTime);
247 204 }
@@ -1,105 +1,275
1 1 #include "Visualization/VisualizationGraphRenderingDelegate.h"
2 #include "Visualization/VisualizationGraphWidget.h"
2 3 #include "Visualization/qcustomplot.h"
3 4
4 5 #include <Common/DateUtils.h>
5 6
7 #include <Data/IDataSeries.h>
8
9 #include <SqpApplication.h>
10
6 11 namespace {
7 12
13 /// Name of the axes layer in QCustomPlot
14 const auto AXES_LAYER = QStringLiteral("axes");
15
8 16 const auto DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd hh:mm:ss:zzz");
9 17
18 /// Format for datetimes on a axis
19 const auto DATETIME_TICKER_FORMAT = QStringLiteral("yyyy/MM/dd \nhh:mm:ss");
20
21 /// Icon used to show x-axis properties
22 const auto HIDE_AXIS_ICON_PATH = QStringLiteral(":/icones/down.png");
23
24 /// Name of the overlay layer in QCustomPlot
25 const auto OVERLAY_LAYER = QStringLiteral("overlay");
26
27 /// Pixmap used to show x-axis properties
28 const auto SHOW_AXIS_ICON_PATH = QStringLiteral(":/icones/up.png");
29
10 30 const auto TOOLTIP_FORMAT = QStringLiteral("key: %1\nvalue: %2");
11 31
12 32 /// Offset used to shift the tooltip of the mouse
13 33 const auto TOOLTIP_OFFSET = QPoint{20, 20};
14 34
15 35 /// Tooltip display rectangle (the tooltip is hidden when the mouse leaves this rectangle)
16 36 const auto TOOLTIP_RECT = QRect{10, 10, 10, 10};
17 37
18 38 /// Timeout after which the tooltip is displayed
19 39 const auto TOOLTIP_TIMEOUT = 500;
20 40
41 /// Generates the appropriate ticker for an axis, depending on whether the axis displays time or
42 /// non-time data
43 QSharedPointer<QCPAxisTicker> axisTicker(bool isTimeAxis)
44 {
45 if (isTimeAxis) {
46 auto dateTicker = QSharedPointer<QCPAxisTickerDateTime>::create();
47 dateTicker->setDateTimeFormat(DATETIME_TICKER_FORMAT);
48 dateTicker->setDateTimeSpec(Qt::UTC);
49
50 return dateTicker;
51 }
52 else {
53 // default ticker
54 return QSharedPointer<QCPAxisTicker>::create();
55 }
56 }
57
21 58 /// Formats a data value according to the axis on which it is present
22 59 QString formatValue(double value, const QCPAxis &axis)
23 60 {
24 61 // If the axis is a time axis, formats the value as a date
25 62 if (auto axisTicker = qSharedPointerDynamicCast<QCPAxisTickerDateTime>(axis.ticker())) {
26 63 return DateUtils::dateTime(value, axisTicker->dateTimeSpec()).toString(DATETIME_FORMAT);
27 64 }
28 65 else {
29 66 return QString::number(value);
30 67 }
31 68 }
32 69
33 70 void initPointTracerStyle(QCPItemTracer &tracer) noexcept
34 71 {
35 72 tracer.setInterpolating(false);
36 73 tracer.setStyle(QCPItemTracer::tsCircle);
37 74 tracer.setSize(3);
38 75 tracer.setPen(QPen(Qt::black));
39 76 tracer.setBrush(Qt::black);
40 77 }
41 78
79 QPixmap pixmap(const QString &iconPath) noexcept
80 {
81 return QIcon{iconPath}.pixmap(QSize{16, 16});
82 }
83
84 void initClosePixmapStyle(QCPItemPixmap &pixmap) noexcept
85 {
86 // Icon
87 pixmap.setPixmap(
88 sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton).pixmap(QSize{16, 16}));
89
90 // Position
91 pixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
92 pixmap.topLeft->setCoords(1, 0);
93 pixmap.setClipToAxisRect(false);
94
95 // Can be selected
96 pixmap.setSelectable(true);
97 }
98
99 void initXAxisPixmapStyle(QCPItemPixmap &itemPixmap) noexcept
100 {
101 // Icon
102 itemPixmap.setPixmap(pixmap(HIDE_AXIS_ICON_PATH));
103
104 // Position
105 itemPixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
106 itemPixmap.topLeft->setCoords(0, 1);
107 itemPixmap.setClipToAxisRect(false);
108
109 // Can be selected
110 itemPixmap.setSelectable(true);
111 }
112
113 void initTitleTextStyle(QCPItemText &text) noexcept
114 {
115 // Font and background styles
116 text.setColor(Qt::gray);
117 text.setBrush(Qt::white);
118
119 // Position
120 text.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
121 text.position->setType(QCPItemPosition::ptAxisRectRatio);
122 text.position->setCoords(0.5, 0);
123 }
124
42 125 } // namespace
43 126
44 127 struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate {
45 explicit VisualizationGraphRenderingDelegatePrivate(QCustomPlot &plot)
46 : m_Plot{plot}, m_PointTracer{new QCPItemTracer{&plot}}, m_TracerTimer{}
128 explicit VisualizationGraphRenderingDelegatePrivate(VisualizationGraphWidget &graphWidget)
129 : m_Plot{graphWidget.plot()},
130 m_PointTracer{new QCPItemTracer{&m_Plot}},
131 m_TracerTimer{},
132 m_ClosePixmap{new QCPItemPixmap{&m_Plot}},
133 m_TitleText{new QCPItemText{&m_Plot}},
134 m_XAxisPixmap{new QCPItemPixmap{&m_Plot}},
135 m_ShowXAxis{true},
136 m_XAxisLabel{}
47 137 {
48 138 initPointTracerStyle(*m_PointTracer);
49 139
50 140 m_TracerTimer.setInterval(TOOLTIP_TIMEOUT);
51 141 m_TracerTimer.setSingleShot(true);
142
143 // Inits "close button" in plot overlay
144 m_ClosePixmap->setLayer(OVERLAY_LAYER);
145 initClosePixmapStyle(*m_ClosePixmap);
146
147 // Connects pixmap selection to graph widget closing
148 QObject::connect(m_ClosePixmap, &QCPItemPixmap::selectionChanged,
149 [&graphWidget](bool selected) {
150 if (selected) {
151 graphWidget.close();
152 }
153 });
154
155 // Inits graph name in plot overlay
156 m_TitleText->setLayer(OVERLAY_LAYER);
157 m_TitleText->setText(graphWidget.name());
158 initTitleTextStyle(*m_TitleText);
159
160 // Inits "show x-axis button" in plot overlay
161 m_XAxisPixmap->setLayer(OVERLAY_LAYER);
162 initXAxisPixmapStyle(*m_XAxisPixmap);
163
164 // Connects pixmap selection to graph x-axis showing/hiding
165 QObject::connect(m_XAxisPixmap, &QCPItemPixmap::selectionChanged, [this]() {
166 if (m_XAxisPixmap->selected()) {
167 // Changes the selection state and refreshes the x-axis
168 m_ShowXAxis = !m_ShowXAxis;
169 updateXAxisState();
170 m_Plot.layer(AXES_LAYER)->replot();
171
172 // Deselects the x-axis pixmap and updates icon
173 m_XAxisPixmap->setSelected(false);
174 m_XAxisPixmap->setPixmap(
175 pixmap(m_ShowXAxis ? HIDE_AXIS_ICON_PATH : SHOW_AXIS_ICON_PATH));
176 m_Plot.layer(OVERLAY_LAYER)->replot();
177 }
178 });
179 }
180
181 /// Updates state of x-axis according to the current selection of x-axis pixmap
182 /// @remarks the method doesn't call plot refresh
183 void updateXAxisState() noexcept
184 {
185 m_Plot.xAxis->setTickLabels(m_ShowXAxis);
186 m_Plot.xAxis->setLabel(m_ShowXAxis ? m_XAxisLabel : QString{});
52 187 }
53 188
54 189 QCustomPlot &m_Plot;
55 190 QCPItemTracer *m_PointTracer;
56 191 QTimer m_TracerTimer;
192 QCPItemPixmap *m_ClosePixmap; /// Graph's close button
193 QCPItemText *m_TitleText; /// Graph's title
194 QCPItemPixmap *m_XAxisPixmap;
195 bool m_ShowXAxis; /// X-axis properties are shown or hidden
196 QString m_XAxisLabel;
57 197 };
58 198
59 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(QCustomPlot &plot)
60 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(plot)}
199 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(
200 VisualizationGraphWidget &graphWidget)
201 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(graphWidget)}
61 202 {
62 203 }
63 204
64 205 void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept
65 206 {
66 207 // Cancels pending refresh
67 208 impl->m_TracerTimer.disconnect();
68 209
69 210 // Reinits tracers
70 211 impl->m_PointTracer->setGraph(nullptr);
71 212 impl->m_PointTracer->setVisible(false);
72 213 impl->m_Plot.replot();
73 214
74 215 // Gets the graph under the mouse position
75 216 auto eventPos = event->pos();
76 217 if (auto graph = qobject_cast<QCPGraph *>(impl->m_Plot.plottableAt(eventPos))) {
77 218 auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x());
78 219 auto graphData = graph->data();
79 220
80 221 // Gets the closest data point to the mouse
81 222 auto graphDataIt = graphData->findBegin(mouseKey);
82 223 if (graphDataIt != graphData->constEnd()) {
83 224 auto key = formatValue(graphDataIt->key, *graph->keyAxis());
84 225 auto value = formatValue(graphDataIt->value, *graph->valueAxis());
85 226
86 227 // Displays point tracer
87 228 impl->m_PointTracer->setGraph(graph);
88 229 impl->m_PointTracer->setGraphKey(graphDataIt->key);
89 230 impl->m_PointTracer->setLayer(
90 231 impl->m_Plot.layer("main")); // Tracer is set on top of the plot's main layer
91 232 impl->m_PointTracer->setVisible(true);
92 233 impl->m_Plot.replot();
93 234
94 235 // Starts timer to show tooltip after timeout
95 236 auto showTooltip = [ tooltip = TOOLTIP_FORMAT.arg(key, value), eventPos, this ]()
96 237 {
97 238 QToolTip::showText(impl->m_Plot.mapToGlobal(eventPos) + TOOLTIP_OFFSET, tooltip,
98 239 &impl->m_Plot, TOOLTIP_RECT);
99 240 };
100 241
101 242 QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTooltip);
102 243 impl->m_TracerTimer.start();
103 244 }
104 245 }
105 246 }
247
248 void VisualizationGraphRenderingDelegate::setAxesProperties(const Unit &xAxisUnit,
249 const Unit &valuesUnit) noexcept
250 {
251 // Stores x-axis label to be able to retrieve it when x-axis pixmap is unselected
252 impl->m_XAxisLabel = xAxisUnit.m_Name;
253
254 auto setAxisProperties = [](auto axis, const auto &unit) {
255 // label (unit name)
256 axis->setLabel(unit.m_Name);
257
258 // ticker (depending on the type of unit)
259 axis->setTicker(axisTicker(unit.m_TimeUnit));
260 };
261 setAxisProperties(impl->m_Plot.xAxis, xAxisUnit);
262 setAxisProperties(impl->m_Plot.yAxis, valuesUnit);
263
264 // Updates x-axis state
265 impl->updateXAxisState();
266
267 impl->m_Plot.layer(AXES_LAYER)->replot();
268 }
269
270 void VisualizationGraphRenderingDelegate::showGraphOverlay(bool show) noexcept
271 {
272 auto overlay = impl->m_Plot.layer(OVERLAY_LAYER);
273 overlay->setVisible(show);
274 overlay->replot();
275 }
@@ -1,313 +1,344
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 3 #include "Visualization/VisualizationDefs.h"
4 4 #include "Visualization/VisualizationGraphHelper.h"
5 5 #include "Visualization/VisualizationGraphRenderingDelegate.h"
6 6 #include "ui_VisualizationGraphWidget.h"
7 7
8 8 #include <Data/ArrayData.h>
9 9 #include <Data/IDataSeries.h>
10 10 #include <Settings/SqpSettingsDefs.h>
11 11 #include <SqpApplication.h>
12 12 #include <Variable/Variable.h>
13 13 #include <Variable/VariableController.h>
14 14
15 15 #include <unordered_map>
16 16
17 17 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
18 18
19 19 namespace {
20 20
21 21 /// Key pressed to enable zoom on horizontal axis
22 22 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::NoModifier;
23 23
24 24 /// Key pressed to enable zoom on vertical axis
25 25 const auto VERTICAL_ZOOM_MODIFIER = Qt::ControlModifier;
26 26
27 27 } // namespace
28 28
29 29 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
30 30
31 explicit VisualizationGraphWidgetPrivate()
32 : m_DoAcquisition{true}, m_IsCalibration{false}, m_RenderingDelegate{nullptr}
31 explicit VisualizationGraphWidgetPrivate(const QString &name)
32 : m_Name{name},
33 m_DoAcquisition{true},
34 m_IsCalibration{false},
35 m_RenderingDelegate{nullptr}
33 36 {
34 37 }
35 38
39 QString m_Name;
36 40 // 1 variable -> n qcpplot
37 41 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
38 42 bool m_DoAcquisition;
39 43 bool m_IsCalibration;
40 44 QCPItemTracer *m_TextTracer;
41 45 /// Delegate used to attach rendering features to the plot
42 46 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
43 47 };
44 48
45 49 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
46 50 : QWidget{parent},
47 51 ui{new Ui::VisualizationGraphWidget},
48 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>()}
52 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
49 53 {
50 54 ui->setupUi(this);
51 55
52 // The delegate must be initialized after the ui as it uses the plot
53 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*ui->widget);
54
55 ui->graphNameLabel->setText(name);
56
57 56 // 'Close' options : widget is deleted when closed
58 57 setAttribute(Qt::WA_DeleteOnClose);
59 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationGraphWidget::close);
60 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
61 58
62 59 // Set qcpplot properties :
63 60 // - Drag (on x-axis) and zoom are enabled
64 61 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
65 ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom);
62 ui->widget->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectItems);
66 63 ui->widget->axisRect()->setRangeDrag(Qt::Horizontal);
67 64
65 // The delegate must be initialized after the ui as it uses the plot
66 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
67
68 68 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
69 69 connect(ui->widget, &QCustomPlot::mouseRelease, this,
70 70 &VisualizationGraphWidget::onMouseRelease);
71 71 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
72 72 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
73 73 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
74 74 &QCPAxis::rangeChanged),
75 75 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
76 76
77 77 // Activates menu when right clicking on the graph
78 78 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
79 79 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
80 80 &VisualizationGraphWidget::onGraphMenuRequested);
81 81
82 82 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
83 83 &VariableController::onRequestDataLoading);
84 84
85 85 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
86 86 &VisualizationGraphWidget::onUpdateVarDisplaying);
87 87 }
88 88
89 89
90 90 VisualizationGraphWidget::~VisualizationGraphWidget()
91 91 {
92 92 delete ui;
93 93 }
94 94
95 95 void VisualizationGraphWidget::enableAcquisition(bool enable)
96 96 {
97 97 impl->m_DoAcquisition = enable;
98 98 }
99 99
100 100 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
101 101 {
102 102 // Uses delegate to create the qcpplot components according to the variable
103 103 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
104 104 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
105 105
106 // Set axes properties according to the units of the data series
107 /// @todo : for the moment, no control is performed on the axes: the units and the tickers
108 /// are fixed for the default x-axis and y-axis of the plot, and according to the new graph
109 auto xAxisUnit = Unit{};
110 auto valuesUnit = Unit{};
111
112 if (auto dataSeries = variable->dataSeries()) {
113 dataSeries->lockRead();
114 xAxisUnit = dataSeries->xAxisUnit();
115 valuesUnit = dataSeries->valuesUnit();
116 dataSeries->unlock();
117 }
118 impl->m_RenderingDelegate->setAxesProperties(xAxisUnit, valuesUnit);
119
106 120 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
107 121
108 122 auto varRange = variable->range();
109 123
110 124 this->enableAcquisition(false);
111 125 this->setGraphRange(range);
112 126 this->enableAcquisition(true);
113 127
114 128 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, varRange,
115 129 false);
116 130
117 131 emit variableAdded(variable);
118 132 }
119 133
120 134 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
121 135 {
122 136 // Each component associated to the variable :
123 137 // - is removed from qcpplot (which deletes it)
124 138 // - is no longer referenced in the map
125 139 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
126 140 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
127 141 auto &plottablesMap = variableIt->second;
128 142
129 143 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
130 144 plottableIt != plottableEnd;) {
131 145 ui->widget->removePlottable(plottableIt->second);
132 146 plottableIt = plottablesMap.erase(plottableIt);
133 147 }
134 148
135 149 impl->m_VariableToPlotMultiMap.erase(variableIt);
136 150 }
137 151
138 152 // Updates graph
139 153 ui->widget->replot();
140 154 }
141 155
142 156 void VisualizationGraphWidget::setRange(std::shared_ptr<Variable> variable, const SqpRange &range)
143 157 {
144 158 // Note: in case of different axes that depends on variable, we could start with a code like
145 159 // that:
146 160 // auto componentsIt = impl->m_VariableToPlotMultiMap.equal_range(variable);
147 161 // for (auto it = componentsIt.first; it != componentsIt.second;) {
148 162 // }
149 163 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
150 164 ui->widget->replot();
151 165 }
152 166
153 167 void VisualizationGraphWidget::setYRange(const SqpRange &range)
154 168 {
155 169 ui->widget->yAxis->setRange(range.m_TStart, range.m_TEnd);
156 170 }
157 171
158 172 SqpRange VisualizationGraphWidget::graphRange() const noexcept
159 173 {
160 174 auto graphRange = ui->widget->xAxis->range();
161 175 return SqpRange{graphRange.lower, graphRange.upper};
162 176 }
163 177
164 178 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
165 179 {
166 180 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
167 181 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
168 182 ui->widget->replot();
169 183 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
170 184 }
171 185
172 186 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
173 187 {
174 188 if (visitor) {
175 189 visitor->visit(this);
176 190 }
177 191 else {
178 192 qCCritical(LOG_VisualizationGraphWidget())
179 193 << tr("Can't visit widget : the visitor is null");
180 194 }
181 195 }
182 196
183 197 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
184 198 {
185 199 /// @todo : for the moment, a graph can always accomodate a variable
186 200 Q_UNUSED(variable);
187 201 return true;
188 202 }
189 203
190 204 bool VisualizationGraphWidget::contains(const Variable &variable) const
191 205 {
192 206 // Finds the variable among the keys of the map
193 207 auto variablePtr = &variable;
194 208 auto findVariable
195 209 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
196 210
197 211 auto end = impl->m_VariableToPlotMultiMap.cend();
198 212 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
199 213 return it != end;
200 214 }
201 215
202 216 QString VisualizationGraphWidget::name() const
203 217 {
204 return ui->graphNameLabel->text();
218 return impl->m_Name;
219 }
220
221 void VisualizationGraphWidget::enterEvent(QEvent *event)
222 {
223 Q_UNUSED(event);
224 impl->m_RenderingDelegate->showGraphOverlay(true);
225 }
226
227 void VisualizationGraphWidget::leaveEvent(QEvent *event)
228 {
229 Q_UNUSED(event);
230 impl->m_RenderingDelegate->showGraphOverlay(false);
231 }
232
233 QCustomPlot &VisualizationGraphWidget::plot() noexcept
234 {
235 return *ui->widget;
205 236 }
206 237
207 238 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
208 239 {
209 240 QMenu graphMenu{};
210 241
211 242 // Iterates on variables (unique keys)
212 243 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
213 244 end = impl->m_VariableToPlotMultiMap.cend();
214 245 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
215 246 // 'Remove variable' action
216 247 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
217 248 [ this, var = it->first ]() { removeVariable(var); });
218 249 }
219 250
220 251 if (!graphMenu.isEmpty()) {
221 252 graphMenu.exec(QCursor::pos());
222 253 }
223 254 }
224 255
225 256 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
226 257 {
227 258 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
228 259 << QThread::currentThread()->objectName() << "DoAcqui"
229 260 << impl->m_DoAcquisition;
230 261
231 262 auto graphRange = SqpRange{t1.lower, t1.upper};
232 263 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
233 264
234 265 if (impl->m_DoAcquisition) {
235 266 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
236 267
237 268 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
238 269 end = impl->m_VariableToPlotMultiMap.end();
239 270 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
240 271 variableUnderGraphVector.push_back(it->first);
241 272 }
242 273 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange, oldGraphRange,
243 274 !impl->m_IsCalibration);
244 275
245 276 if (!impl->m_IsCalibration) {
246 277 qCDebug(LOG_VisualizationGraphWidget())
247 278 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
248 279 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
249 280 emit synchronize(graphRange, oldGraphRange);
250 281 }
251 282 }
252 283 }
253 284
254 285 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
255 286 {
256 287 // Handles plot rendering when mouse is moving
257 288 impl->m_RenderingDelegate->onMouseMove(event);
258 289 }
259 290
260 291 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
261 292 {
262 293 auto zoomOrientations = QFlags<Qt::Orientation>{};
263 294
264 295 // Lambda that enables a zoom orientation if the key modifier related to this orientation
265 296 // has
266 297 // been pressed
267 298 auto enableOrientation
268 299 = [&zoomOrientations, event](const auto &orientation, const auto &modifier) {
269 300 auto orientationEnabled = event->modifiers().testFlag(modifier);
270 301 zoomOrientations.setFlag(orientation, orientationEnabled);
271 302 };
272 303 enableOrientation(Qt::Vertical, VERTICAL_ZOOM_MODIFIER);
273 304 enableOrientation(Qt::Horizontal, HORIZONTAL_ZOOM_MODIFIER);
274 305
275 306 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
276 307 }
277 308
278 309 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
279 310 {
280 311 impl->m_IsCalibration = event->modifiers().testFlag(Qt::ControlModifier);
281 312 }
282 313
283 314 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
284 315 {
285 316 impl->m_IsCalibration = false;
286 317 }
287 318
288 319 void VisualizationGraphWidget::onDataCacheVariableUpdated()
289 320 {
290 321 auto graphRange = ui->widget->xAxis->range();
291 322 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
292 323
293 324 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
294 325 auto variable = variableEntry.first;
295 326 qCDebug(LOG_VisualizationGraphWidget())
296 327 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
297 328 qCDebug(LOG_VisualizationGraphWidget())
298 329 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
299 330 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
300 331 VisualizationGraphHelper::updateData(variableEntry.second, variable->dataSeries(),
301 332 variable->range());
302 333 }
303 334 }
304 335 }
305 336
306 337 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
307 338 const SqpRange &range)
308 339 {
309 340 auto it = impl->m_VariableToPlotMultiMap.find(variable);
310 341 if (it != impl->m_VariableToPlotMultiMap.end()) {
311 342 VisualizationGraphHelper::updateData(it->second, variable->dataSeries(), range);
312 343 }
313 344 }
@@ -1,260 +1,268
1 1 #include "Visualization/VisualizationZoneWidget.h"
2 2
3
4 3 #include "Visualization/IVisualizationWidgetVisitor.h"
4 #include "Visualization/QCustomPlotSynchronizer.h"
5 5 #include "Visualization/VisualizationGraphWidget.h"
6 6 #include "ui_VisualizationZoneWidget.h"
7 7
8 8 #include <Data/SqpRange.h>
9 9 #include <Variable/Variable.h>
10 10 #include <Variable/VariableController.h>
11 11
12 12 #include <QUuid>
13 13 #include <SqpApplication.h>
14 14 #include <cmath>
15 15
16 16 Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget")
17 17
18 18 namespace {
19 19
20 20 /// Minimum height for graph added in zones (in pixels)
21 21 const auto GRAPH_MINIMUM_HEIGHT = 300;
22 22
23 23 /// Generates a default name for a new graph, according to the number of graphs already displayed in
24 24 /// the zone
25 25 QString defaultGraphName(const QLayout &layout)
26 26 {
27 27 auto count = 0;
28 28 for (auto i = 0; i < layout.count(); ++i) {
29 29 if (dynamic_cast<VisualizationGraphWidget *>(layout.itemAt(i)->widget())) {
30 30 count++;
31 31 }
32 32 }
33 33
34 34 return QObject::tr("Graph %1").arg(count + 1);
35 35 }
36 36
37 37 } // namespace
38 38
39 39 struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate {
40 40
41 explicit VisualizationZoneWidgetPrivate() : m_SynchronisationGroupId{QUuid::createUuid()} {}
41 explicit VisualizationZoneWidgetPrivate()
42 : m_SynchronisationGroupId{QUuid::createUuid()},
43 m_Synchronizer{std::make_unique<QCustomPlotSynchronizer>()}
44 {
45 }
42 46 QUuid m_SynchronisationGroupId;
47 std::unique_ptr<IGraphSynchronizer> m_Synchronizer;
43 48 };
44 49
45 50 VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent)
46 51 : QWidget{parent},
47 52 ui{new Ui::VisualizationZoneWidget},
48 53 impl{spimpl::make_unique_impl<VisualizationZoneWidgetPrivate>()}
49 54 {
50 55 ui->setupUi(this);
51 56
52 57 ui->zoneNameLabel->setText(name);
53 58
54 59 // 'Close' options : widget is deleted when closed
55 60 setAttribute(Qt::WA_DeleteOnClose);
56 61 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationZoneWidget::close);
57 62 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
58 63
59 64 // Synchronisation id
60 65 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronizationGroupId",
61 66 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
62 67 }
63 68
64 69 VisualizationZoneWidget::~VisualizationZoneWidget()
65 70 {
66 71 delete ui;
67 72 }
68 73
69 74 void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget)
70 75 {
76 // Synchronize new graph with others in the zone
77 impl->m_Synchronizer->addGraph(*graphWidget);
78
71 79 ui->visualizationZoneFrame->layout()->addWidget(graphWidget);
72 80 }
73 81
74 82 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable)
75 83 {
76 84 auto graphWidget = new VisualizationGraphWidget{
77 85 defaultGraphName(*ui->visualizationZoneFrame->layout()), this};
78 86
79 87
80 88 // Set graph properties
81 89 graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
82 90 graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT);
83 91
84 92
85 93 // Lambda to synchronize zone widget
86 94 auto synchronizeZoneWidget = [this, graphWidget](const SqpRange &graphRange,
87 95 const SqpRange &oldGraphRange) {
88 96
89 97 auto zoomType = VariableController::getZoomType(graphRange, oldGraphRange);
90 98 auto frameLayout = ui->visualizationZoneFrame->layout();
91 99 for (auto i = 0; i < frameLayout->count(); ++i) {
92 100 auto graphChild
93 101 = dynamic_cast<VisualizationGraphWidget *>(frameLayout->itemAt(i)->widget());
94 102 if (graphChild && (graphChild != graphWidget)) {
95 103
96 104 auto graphChildRange = graphChild->graphRange();
97 105 switch (zoomType) {
98 106 case AcquisitionZoomType::ZoomIn: {
99 107 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
100 108 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
101 109 graphChildRange.m_TStart += deltaLeft;
102 110 graphChildRange.m_TEnd -= deltaRight;
103 111 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomIn");
104 112 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
105 113 << deltaLeft;
106 114 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
107 115 << deltaRight;
108 116 qCDebug(LOG_VisualizationZoneWidget())
109 117 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
110 118
111 119 break;
112 120 }
113 121
114 122 case AcquisitionZoomType::ZoomOut: {
115 123 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomOut");
116 124 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
117 125 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
118 126 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
119 127 << deltaLeft;
120 128 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
121 129 << deltaRight;
122 130 qCDebug(LOG_VisualizationZoneWidget())
123 131 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
124 132 graphChildRange.m_TStart -= deltaLeft;
125 133 graphChildRange.m_TEnd += deltaRight;
126 134 break;
127 135 }
128 136 case AcquisitionZoomType::PanRight: {
129 137 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanRight");
130 138 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
131 139 graphChildRange.m_TStart += deltaRight;
132 140 graphChildRange.m_TEnd += deltaRight;
133 141 qCDebug(LOG_VisualizationZoneWidget())
134 142 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
135 143 break;
136 144 }
137 145 case AcquisitionZoomType::PanLeft: {
138 146 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanLeft");
139 147 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
140 148 graphChildRange.m_TStart -= deltaLeft;
141 149 graphChildRange.m_TEnd -= deltaLeft;
142 150 break;
143 151 }
144 152 case AcquisitionZoomType::Unknown: {
145 153 qCDebug(LOG_VisualizationZoneWidget())
146 154 << tr("Impossible to synchronize: zoom type unknown");
147 155 break;
148 156 }
149 157 default:
150 158 qCCritical(LOG_VisualizationZoneWidget())
151 159 << tr("Impossible to synchronize: zoom type not take into account");
152 160 // No action
153 161 break;
154 162 }
155 163 graphChild->enableAcquisition(false);
156 164 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: Range before: ")
157 165 << graphChild->graphRange();
158 166 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: Range after : ")
159 167 << graphChildRange;
160 168 qCDebug(LOG_VisualizationZoneWidget())
161 169 << tr("TORM: child dt") << graphChildRange.m_TEnd - graphChildRange.m_TStart;
162 170 graphChild->setGraphRange(graphChildRange);
163 171 graphChild->enableAcquisition(true);
164 172 }
165 173 }
166 174 };
167 175
168 176 // connection for synchronization
169 177 connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget);
170 178 connect(graphWidget, &VisualizationGraphWidget::variableAdded, this,
171 179 &VisualizationZoneWidget::onVariableAdded);
172 180
173 181 auto range = SqpRange{};
174 182
175 183 // Apply visitor to graph children
176 184 auto layout = ui->visualizationZoneFrame->layout();
177 185 if (layout->count() > 0) {
178 186 // Case of a new graph in a existant zone
179 187 if (auto visualizationGraphWidget
180 188 = dynamic_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
181 189 range = visualizationGraphWidget->graphRange();
182 190 }
183 191 }
184 192 else {
185 193 // Case of a new graph as the first of the zone
186 194 range = variable->range();
187 195 }
188 196
189 197 this->addGraph(graphWidget);
190 198
191 199 graphWidget->addVariable(variable, range);
192 200
193 201 // get y using variable range
194 202 if (auto dataSeries = variable->dataSeries()) {
195 203 dataSeries->lockRead();
196 204 auto valuesBounds
197 205 = dataSeries->valuesBounds(variable->range().m_TStart, variable->range().m_TEnd);
198 206 auto end = dataSeries->cend();
199 207 if (valuesBounds.first != end && valuesBounds.second != end) {
200 208 auto rangeValue = [](const auto &value) { return std::isnan(value) ? 0. : value; };
201 209
202 210 auto minValue = rangeValue(valuesBounds.first->minValue());
203 211 auto maxValue = rangeValue(valuesBounds.second->maxValue());
204 212
205 213 graphWidget->setYRange(SqpRange{minValue, maxValue});
206 214 }
207 215 dataSeries->unlock();
208 216 }
209 217
210 218 return graphWidget;
211 219 }
212 220
213 221 void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor)
214 222 {
215 223 if (visitor) {
216 224 visitor->visitEnter(this);
217 225
218 226 // Apply visitor to graph children
219 227 auto layout = ui->visualizationZoneFrame->layout();
220 228 for (auto i = 0; i < layout->count(); ++i) {
221 229 if (auto item = layout->itemAt(i)) {
222 230 // Widgets different from graphs are not visited (no action)
223 231 if (auto visualizationGraphWidget
224 232 = dynamic_cast<VisualizationGraphWidget *>(item->widget())) {
225 233 visualizationGraphWidget->accept(visitor);
226 234 }
227 235 }
228 236 }
229 237
230 238 visitor->visitLeave(this);
231 239 }
232 240 else {
233 241 qCCritical(LOG_VisualizationZoneWidget()) << tr("Can't visit widget : the visitor is null");
234 242 }
235 243 }
236 244
237 245 bool VisualizationZoneWidget::canDrop(const Variable &variable) const
238 246 {
239 247 // A tab can always accomodate a variable
240 248 Q_UNUSED(variable);
241 249 return true;
242 250 }
243 251
244 252 bool VisualizationZoneWidget::contains(const Variable &variable) const
245 253 {
246 254 Q_UNUSED(variable);
247 255 return false;
248 256 }
249 257
250 258 QString VisualizationZoneWidget::name() const
251 259 {
252 260 return ui->zoneNameLabel->text();
253 261 }
254 262
255 263 void VisualizationZoneWidget::onVariableAdded(std::shared_ptr<Variable> variable)
256 264 {
257 265 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronized",
258 266 Qt::QueuedConnection, Q_ARG(std::shared_ptr<Variable>, variable),
259 267 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
260 268 }
@@ -1,83 +1,51
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <ui version="4.0">
3 3 <class>VisualizationGraphWidget</class>
4 4 <widget class="QWidget" name="VisualizationGraphWidget">
5 5 <property name="geometry">
6 6 <rect>
7 7 <x>0</x>
8 8 <y>0</y>
9 9 <width>400</width>
10 10 <height>300</height>
11 11 </rect>
12 12 </property>
13 13 <property name="windowTitle">
14 14 <string>Form</string>
15 15 </property>
16 16 <layout class="QVBoxLayout" name="verticalLayout">
17 <item>
18 <widget class="QWidget" name="infobar" native="true">
19 <layout class="QHBoxLayout" name="horizontalLayout_2">
20 <property name="leftMargin">
21 <number>0</number>
22 </property>
23 <property name="topMargin">
24 <number>0</number>
25 </property>
26 <property name="rightMargin">
27 <number>0</number>
28 </property>
29 <property name="bottomMargin">
30 <number>0</number>
31 </property>
32 <item>
33 <widget class="QLabel" name="graphNameLabel">
34 <property name="styleSheet">
35 <string notr="true">font: 75 9pt &quot;MS Shell Dlg 2&quot;;</string>
36 </property>
37 <property name="text">
38 <string>TextLabel</string>
39 </property>
40 <property name="textFormat">
41 <enum>Qt::AutoText</enum>
42 </property>
43 <property name="alignment">
44 <set>Qt::AlignCenter</set>
45 </property>
46 </widget>
47 </item>
48 <item>
49 <widget class="QToolButton" name="closeButton">
50 <property name="styleSheet">
51 <string notr="true">background-color: transparent;</string>
52 </property>
53 <property name="text">
54 <string>Close</string>
55 </property>
56 </widget>
57 </item>
58 </layout>
59 </widget>
60 </item>
17 <property name="leftMargin">
18 <number>0</number>
19 </property>
20 <property name="topMargin">
21 <number>0</number>
22 </property>
23 <property name="rightMargin">
24 <number>0</number>
25 </property>
26 <property name="bottomMargin">
27 <number>0</number>
28 </property>
61 29 <item>
62 30 <widget class="QCustomPlot" name="widget" native="true">
63 31 <property name="sizePolicy">
64 32 <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
65 33 <horstretch>0</horstretch>
66 34 <verstretch>0</verstretch>
67 35 </sizepolicy>
68 36 </property>
69 37 </widget>
70 38 </item>
71 39 </layout>
72 40 </widget>
73 41 <customwidgets>
74 42 <customwidget>
75 43 <class>QCustomPlot</class>
76 44 <extends>QWidget</extends>
77 45 <header>Visualization/qcustomplot.h</header>
78 46 <container>1</container>
79 47 </customwidget>
80 48 </customwidgets>
81 49 <resources/>
82 50 <connections/>
83 51 </ui>
@@ -1,57 +1,73
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <ui version="4.0">
3 3 <class>VisualizationTabWidget</class>
4 4 <widget class="QWidget" name="VisualizationTabWidget">
5 5 <property name="geometry">
6 6 <rect>
7 7 <x>0</x>
8 8 <y>0</y>
9 9 <width>400</width>
10 10 <height>300</height>
11 11 </rect>
12 12 </property>
13 13 <property name="windowTitle">
14 14 <string>Form</string>
15 15 </property>
16 16 <layout class="QVBoxLayout" name="verticalLayout">
17 17 <property name="leftMargin">
18 18 <number>0</number>
19 19 </property>
20 20 <property name="topMargin">
21 21 <number>0</number>
22 22 </property>
23 23 <property name="rightMargin">
24 24 <number>0</number>
25 25 </property>
26 26 <property name="bottomMargin">
27 27 <number>0</number>
28 28 </property>
29 29 <item>
30 30 <widget class="QScrollArea" name="scrollArea">
31 31 <property name="frameShape">
32 32 <enum>QFrame::NoFrame</enum>
33 33 </property>
34 34 <property name="frameShadow">
35 35 <enum>QFrame::Sunken</enum>
36 36 </property>
37 37 <property name="widgetResizable">
38 38 <bool>true</bool>
39 39 </property>
40 40 <widget class="QWidget" name="scrollAreaWidgetContents">
41 41 <property name="geometry">
42 42 <rect>
43 43 <x>0</x>
44 44 <y>0</y>
45 45 <width>400</width>
46 46 <height>300</height>
47 47 </rect>
48 48 </property>
49 <layout class="QVBoxLayout" name="verticalLayout_3"/>
49 <layout class="QVBoxLayout" name="verticalLayout_3">
50 <property name="spacing">
51 <number>3</number>
52 </property>
53 <property name="leftMargin">
54 <number>0</number>
55 </property>
56 <property name="topMargin">
57 <number>0</number>
58 </property>
59 <property name="rightMargin">
60 <number>0</number>
61 </property>
62 <property name="bottomMargin">
63 <number>0</number>
64 </property>
65 </layout>
50 66 </widget>
51 67 </widget>
52 68 </item>
53 69 </layout>
54 70 </widget>
55 71 <resources/>
56 72 <connections/>
57 73 </ui>
@@ -1,28 +1,40
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <ui version="4.0">
3 3 <class>VisualizationWidget</class>
4 4 <widget class="QWidget" name="VisualizationWidget">
5 5 <property name="geometry">
6 6 <rect>
7 7 <x>0</x>
8 8 <y>0</y>
9 9 <width>400</width>
10 10 <height>300</height>
11 11 </rect>
12 12 </property>
13 13 <property name="windowTitle">
14 14 <string>Form</string>
15 15 </property>
16 16 <layout class="QVBoxLayout" name="verticalLayout">
17 <property name="leftMargin">
18 <number>0</number>
19 </property>
20 <property name="topMargin">
21 <number>0</number>
22 </property>
23 <property name="rightMargin">
24 <number>0</number>
25 </property>
26 <property name="bottomMargin">
27 <number>0</number>
28 </property>
17 29 <item>
18 30 <widget class="QTabWidget" name="tabWidget">
19 31 <property name="currentIndex">
20 32 <number>-1</number>
21 33 </property>
22 34 </widget>
23 35 </item>
24 36 </layout>
25 37 </widget>
26 38 <resources/>
27 39 <connections/>
28 40 </ui>
@@ -1,86 +1,117
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <ui version="4.0">
3 3 <class>VisualizationZoneWidget</class>
4 4 <widget class="QWidget" name="VisualizationZoneWidget">
5 5 <property name="geometry">
6 6 <rect>
7 7 <x>0</x>
8 8 <y>0</y>
9 9 <width>400</width>
10 10 <height>300</height>
11 11 </rect>
12 12 </property>
13 13 <property name="windowTitle">
14 14 <string>Form</string>
15 15 </property>
16 16 <layout class="QVBoxLayout" name="verticalLayout_2">
17 <property name="spacing">
18 <number>0</number>
19 </property>
20 <property name="leftMargin">
21 <number>0</number>
22 </property>
23 <property name="topMargin">
24 <number>0</number>
25 </property>
26 <property name="rightMargin">
27 <number>0</number>
28 </property>
29 <property name="bottomMargin">
30 <number>0</number>
31 </property>
17 32 <item>
18 33 <widget class="QWidget" name="infobar" native="true">
19 34 <property name="sizePolicy">
20 35 <sizepolicy hsizetype="Preferred" vsizetype="Minimum">
21 36 <horstretch>0</horstretch>
22 37 <verstretch>0</verstretch>
23 38 </sizepolicy>
24 39 </property>
25 40 <layout class="QHBoxLayout" name="horizontalLayout">
41 <property name="spacing">
42 <number>0</number>
43 </property>
26 44 <property name="leftMargin">
27 45 <number>0</number>
28 46 </property>
29 47 <property name="topMargin">
30 48 <number>0</number>
31 49 </property>
32 50 <property name="rightMargin">
33 51 <number>0</number>
34 52 </property>
35 53 <property name="bottomMargin">
36 54 <number>0</number>
37 55 </property>
38 56 <item>
39 57 <widget class="QLabel" name="zoneNameLabel">
40 58 <property name="styleSheet">
41 59 <string notr="true">color: rgb(127, 127, 127);
42 60 </string>
43 61 </property>
44 62 <property name="text">
45 63 <string>TextLabel</string>
46 64 </property>
47 65 </widget>
48 66 </item>
49 67 <item>
50 68 <widget class="QToolButton" name="closeButton">
51 69 <property name="styleSheet">
52 70 <string notr="true">background-color: transparent;</string>
53 71 </property>
54 72 <property name="text">
55 73 <string>Close</string>
56 74 </property>
57 75 </widget>
58 76 </item>
59 77 </layout>
60 78 </widget>
61 79 </item>
62 80 <item>
63 81 <widget class="QFrame" name="visualizationZoneFrame">
64 82 <property name="sizePolicy">
65 83 <sizepolicy hsizetype="Preferred" vsizetype="Expanding">
66 84 <horstretch>0</horstretch>
67 85 <verstretch>0</verstretch>
68 86 </sizepolicy>
69 87 </property>
70 88 <property name="frameShape">
71 89 <enum>QFrame::Box</enum>
72 90 </property>
73 91 <property name="frameShadow">
74 92 <enum>QFrame::Raised</enum>
75 93 </property>
76 94 <property name="lineWidth">
77 95 <number>1</number>
78 96 </property>
79 <layout class="QVBoxLayout" name="verticalLayout"/>
97 <layout class="QVBoxLayout" name="verticalLayout">
98 <property name="leftMargin">
99 <number>0</number>
100 </property>
101 <property name="topMargin">
102 <number>0</number>
103 </property>
104 <property name="rightMargin">
105 <number>0</number>
106 </property>
107 <property name="bottomMargin">
108 <number>0</number>
109 </property>
110 </layout>
80 111 </widget>
81 112 </item>
82 113 </layout>
83 114 </widget>
84 115 <resources/>
85 116 <connections/>
86 117 </ui>
General Comments 0
You need to be logged in to leave comments. Login now