##// END OF EJS Templates
Implements cursor mode
trabillard -
r1003:569b12637de1
parent child
Show More
@@ -0,0 +1,26
1 #ifndef VISUALIZATIONCURSORITEM_H
2 #define VISUALIZATIONCURSORITEM_H
3
4 #include <Common/spimpl.h>
5 #include <SqpApplication.h>
6
7 class QCustomPlot;
8
9 class VisualizationCursorItem {
10 public:
11 VisualizationCursorItem(QCustomPlot *plot);
12
13 void setVisible(bool value);
14 bool isVisible() const;
15
16 void setPosition(double value);
17 void setAbsolutePosition(double value);
18 void setOrientation(Qt::Orientation orientation);
19 void setLabelText(const QString &text);
20
21 private:
22 class VisualizationCursorItemPrivate;
23 spimpl::unique_impl_ptr<VisualizationCursorItemPrivate> impl;
24 };
25
26 #endif // VISUALIZATIONCURSORITEM_H
@@ -0,0 +1,163
1 #include <Common/DateUtils.h>
2 #include <Visualization/VisualizationCursorItem.h>
3 #include <Visualization/qcustomplot.h>
4
5 /// Width of the cursor in pixel
6 const auto CURSOR_WIDTH = 3;
7
8 /// Color of the cursor in the graph
9 const auto CURSOR_COLOR = QColor{68, 114, 196};
10
11 /// Line style of the cursor in the graph
12 auto CURSOR_PEN_STYLE = Qt::DotLine;
13
14 struct VisualizationCursorItem::VisualizationCursorItemPrivate {
15
16 QCustomPlot *m_Plot = nullptr;
17
18 QCPItemStraightLine *m_LineItem = nullptr;
19 QCPItemText *m_LabelItem = nullptr;
20
21 Qt::Orientation m_Orientation;
22 double m_Position = 0.0;
23 bool m_IsAbsolutePosition = false;
24 QString m_LabelText;
25
26 explicit VisualizationCursorItemPrivate(QCustomPlot *plot)
27 : m_Plot(plot), m_Orientation(Qt::Vertical)
28 {
29 }
30
31 void updateOrientation()
32 {
33 if (m_LineItem) {
34 switch (m_Orientation) {
35 case Qt::Vertical:
36 m_LineItem->point1->setTypeX(m_IsAbsolutePosition
37 ? QCPItemPosition::ptAbsolute
38 : QCPItemPosition::ptPlotCoords);
39 m_LineItem->point1->setTypeY(QCPItemPosition::ptAxisRectRatio);
40 m_LineItem->point2->setTypeX(m_IsAbsolutePosition
41 ? QCPItemPosition::ptAbsolute
42 : QCPItemPosition::ptPlotCoords);
43 m_LineItem->point2->setTypeY(QCPItemPosition::ptAxisRectRatio);
44 m_LabelItem->setPositionAlignment(Qt::AlignLeft | Qt::AlignTop);
45 break;
46 case Qt::Horizontal:
47 m_LineItem->point1->setTypeX(QCPItemPosition::ptAxisRectRatio);
48 m_LineItem->point1->setTypeY(m_IsAbsolutePosition
49 ? QCPItemPosition::ptAbsolute
50 : QCPItemPosition::ptPlotCoords);
51 m_LineItem->point2->setTypeX(QCPItemPosition::ptAxisRectRatio);
52 m_LineItem->point2->setTypeY(m_IsAbsolutePosition
53 ? QCPItemPosition::ptAbsolute
54 : QCPItemPosition::ptPlotCoords);
55 m_LabelItem->setPositionAlignment(Qt::AlignRight | Qt::AlignBottom);
56 }
57 }
58 }
59
60 void updateCursorPosition()
61 {
62 if (m_LineItem) {
63 switch (m_Orientation) {
64 case Qt::Vertical:
65 m_LineItem->point1->setCoords(m_Position, 0);
66 m_LineItem->point2->setCoords(m_Position, 1);
67 m_LabelItem->position->setCoords(5, 5);
68 break;
69 case Qt::Horizontal:
70 m_LineItem->point1->setCoords(1, m_Position);
71 m_LineItem->point2->setCoords(0, m_Position);
72 m_LabelItem->position->setCoords(-5, -5);
73 }
74 }
75 }
76
77 void updateLabelText()
78 {
79 if (m_LabelItem) {
80 m_LabelItem->setText(m_LabelText);
81 }
82 }
83 };
84
85 VisualizationCursorItem::VisualizationCursorItem(QCustomPlot *plot)
86 : impl{spimpl::make_unique_impl<VisualizationCursorItemPrivate>(plot)}
87 {
88 }
89
90 void VisualizationCursorItem::setVisible(bool value)
91 {
92 if (value != isVisible()) {
93
94 if (value) {
95 Q_ASSERT(!impl->m_LineItem && !impl->m_Plot);
96
97 impl->m_LineItem = new QCPItemStraightLine{impl->m_Plot};
98 auto pen = QPen{CURSOR_PEN_STYLE};
99 pen.setColor(CURSOR_COLOR);
100 pen.setWidth(CURSOR_WIDTH);
101 impl->m_LineItem->setPen(pen);
102 impl->m_LineItem->setSelectable(false);
103
104 impl->m_LabelItem = new QCPItemText{impl->m_Plot};
105 impl->m_LabelItem->setColor(CURSOR_COLOR);
106 impl->m_LabelItem->setSelectable(false);
107 impl->m_LabelItem->position->setParentAnchor(impl->m_LineItem->point1);
108 impl->m_LabelItem->position->setTypeX(QCPItemPosition::ptAbsolute);
109 impl->m_LabelItem->position->setTypeY(QCPItemPosition::ptAbsolute);
110
111 auto font = impl->m_LabelItem->font();
112 font.setPointSize(10);
113 font.setBold(true);
114 impl->m_LabelItem->setFont(font);
115
116 impl->updateOrientation();
117 impl->updateLabelText();
118 impl->updateCursorPosition();
119 }
120 else {
121 Q_ASSERT(impl->m_LineItem && impl->m_Plot);
122
123 // Note: the items are destroyed by QCustomPlot in removeItem
124 impl->m_Plot->removeItem(impl->m_LineItem);
125 impl->m_LineItem = nullptr;
126 impl->m_Plot->removeItem(impl->m_LabelItem);
127 impl->m_LabelItem = nullptr;
128 }
129 }
130 }
131
132 bool VisualizationCursorItem::isVisible() const
133 {
134 return impl->m_LineItem != nullptr;
135 }
136
137 void VisualizationCursorItem::setPosition(double value)
138 {
139 impl->m_Position = value;
140 impl->m_IsAbsolutePosition = false;
141 impl->updateLabelText();
142 impl->updateCursorPosition();
143 }
144
145 void VisualizationCursorItem::setAbsolutePosition(double value)
146 {
147 setPosition(value);
148 impl->m_IsAbsolutePosition = true;
149 }
150
151 void VisualizationCursorItem::setOrientation(Qt::Orientation orientation)
152 {
153 impl->m_Orientation = orientation;
154 impl->updateLabelText();
155 impl->updateOrientation();
156 impl->updateCursorPosition();
157 }
158
159 void VisualizationCursorItem::setLabelText(const QString &text)
160 {
161 impl->m_LabelText = text;
162 impl->updateLabelText();
163 }
@@ -1,7 +1,8
1 1 #ifndef SCIQLOP_VISUALIZATIONDEF_H
2 2 #define SCIQLOP_VISUALIZATIONDEF_H
3 3
4 4 /// Minimum height for graph added in zones (in pixels)
5 5 extern const int GRAPH_MINIMUM_HEIGHT;
6 6
7
7 8 #endif // SCIQLOP_VISUALIZATIONDEF_H
@@ -1,109 +1,121
1 1 #ifndef SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
2 2 #define SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
3 3
4 4 #include "Visualization/IVisualizationWidget.h"
5 5 #include "Visualization/VisualizationDragWidget.h"
6 6
7 7 #include <QLoggingCategory>
8 8 #include <QWidget>
9 9
10 10 #include <memory>
11 11
12 12 #include <Common/spimpl.h>
13 13
14 14 Q_DECLARE_LOGGING_CATEGORY(LOG_VisualizationGraphWidget)
15 15
16 16 class QCPRange;
17 17 class QCustomPlot;
18 18 class SqpRange;
19 19 class Variable;
20 20 class VisualizationZoneWidget;
21 21
22 22 namespace Ui {
23 23 class VisualizationGraphWidget;
24 24 } // namespace Ui
25 25
26 26 class VisualizationGraphWidget : public VisualizationDragWidget, public IVisualizationWidget {
27 27 Q_OBJECT
28 28
29 29 friend class QCustomPlotSynchronizer;
30 30 friend class VisualizationGraphRenderingDelegate;
31 31
32 32 public:
33 33 explicit VisualizationGraphWidget(const QString &name = {}, QWidget *parent = 0);
34 34 virtual ~VisualizationGraphWidget();
35 35
36 36 VisualizationZoneWidget *parentZoneWidget() const noexcept;
37 37
38 38 /// If acquisition isn't enable, requestDataLoading signal cannot be emit
39 39 void enableAcquisition(bool enable);
40 40
41 41 void addVariable(std::shared_ptr<Variable> variable, SqpRange range);
42 42
43 43 /// Removes a variable from the graph
44 44 void removeVariable(std::shared_ptr<Variable> variable) noexcept;
45 45
46 46 /// Returns the list of all variables used in the graph
47 47 QList<std::shared_ptr<Variable> > variables() const;
48 48
49 49 /// Sets the y-axis range based on the data of a variable
50 50 void setYRange(std::shared_ptr<Variable> variable);
51 51 SqpRange graphRange() const noexcept;
52 52 void setGraphRange(const SqpRange &range);
53 53
54 54 // IVisualizationWidget interface
55 55 void accept(IVisualizationWidgetVisitor *visitor) override;
56 56 bool canDrop(const Variable &variable) const override;
57 57 bool contains(const Variable &variable) const override;
58 58 QString name() const override;
59 59
60 60 // VisualisationDragWidget
61 61 QMimeData *mimeData() const override;
62 62 bool isDragAllowed() const override;
63 63 void highlightForMerge(bool highlighted) override;
64 64
65 // Cursors
66 /// Adds or moves the vertical cursor at the specified value on the x-axis
67 void addVerticalCursor(double time);
68 /// Adds or moves the vertical cursor at the specified value on the x-axis
69 void addVerticalCursorAtViewportPosition(double position);
70 void removeVerticalCursor();
71 /// Adds or moves the vertical cursor at the specified value on the y-axis
72 void addHorizontalCursor(double value);
73 /// Adds or moves the vertical cursor at the specified value on the y-axis
74 void addHorizontalCursorAtViewportPosition(double position);
75 void removeHorizontalCursor();
76
65 77 signals:
66 78 void synchronize(const SqpRange &range, const SqpRange &oldRange);
67 79 void requestDataLoading(QVector<std::shared_ptr<Variable> > variable, const SqpRange &range,
68 80 bool synchronise);
69 81
70 82 /// Signal emitted when the variable is about to be removed from the graph
71 83 void variableAboutToBeRemoved(std::shared_ptr<Variable> var);
72 84 /// Signal emitted when the variable has been added to the graph
73 85 void variableAdded(std::shared_ptr<Variable> var);
74 86
75 87 protected:
76 88 void closeEvent(QCloseEvent *event) override;
77 89 void enterEvent(QEvent *event) override;
78 90 void leaveEvent(QEvent *event) override;
79 91
80 92 QCustomPlot &plot() noexcept;
81 93
82 94 private:
83 95 Ui::VisualizationGraphWidget *ui;
84 96
85 97 class VisualizationGraphWidgetPrivate;
86 98 spimpl::unique_impl_ptr<VisualizationGraphWidgetPrivate> impl;
87 99
88 100 private slots:
89 101 /// Slot called when right clicking on the graph (displays a menu)
90 102 void onGraphMenuRequested(const QPoint &pos) noexcept;
91 103
92 104 /// Rescale the X axe to range parameter
93 105 void onRangeChanged(const QCPRange &t1, const QCPRange &t2);
94 106
95 107 /// Slot called when a mouse move was made
96 108 void onMouseMove(QMouseEvent *event) noexcept;
97 109 /// Slot called when a mouse wheel was made, to perform some processing before the zoom is done
98 110 void onMouseWheel(QWheelEvent *event) noexcept;
99 111 /// Slot called when a mouse press was made, to activate the calibration of a graph
100 112 void onMousePress(QMouseEvent *event) noexcept;
101 113 /// Slot called when a mouse release was made, to deactivate the calibration of a graph
102 114 void onMouseRelease(QMouseEvent *event) noexcept;
103 115
104 116 void onDataCacheVariableUpdated();
105 117
106 118 void onUpdateVarDisplaying(std::shared_ptr<Variable> variable, const SqpRange &range);
107 119 };
108 120
109 121 #endif // SCIQLOP_VISUALIZATIONGRAPHWIDGET_H
@@ -1,91 +1,95
1 1 #ifndef SCIQLOP_VISUALIZATIONZONEWIDGET_H
2 2 #define SCIQLOP_VISUALIZATIONZONEWIDGET_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_VisualizationZoneWidget)
15 15
16 16 namespace Ui {
17 17 class VisualizationZoneWidget;
18 18 } // namespace Ui
19 19
20 20 class Variable;
21 21 class VisualizationGraphWidget;
22 22
23 23 class VisualizationZoneWidget : public VisualizationDragWidget, public IVisualizationWidget {
24 24 Q_OBJECT
25 25
26 26 public:
27 27 explicit VisualizationZoneWidget(const QString &name = {}, QWidget *parent = 0);
28 28 virtual ~VisualizationZoneWidget();
29 29
30 30 /// Adds a graph widget
31 31 void addGraph(VisualizationGraphWidget *graphWidget);
32 32
33 33 /// Inserts a graph widget
34 34 void insertGraph(int index, VisualizationGraphWidget *graphWidget);
35 35
36 36 /**
37 37 * Creates a graph using a variable. The variable will be displayed in the new graph.
38 38 * The graph is added at the end.
39 39 * @param variable the variable for which to create the graph
40 40 * @return the pointer to the created graph
41 41 */
42 42 VisualizationGraphWidget *createGraph(std::shared_ptr<Variable> variable);
43 43
44 44 /**
45 45 * Creates a graph using a variable. The variable will be displayed in the new graph.
46 46 * The graph is inserted at the specified index.
47 47 * @param variable the variable for which to create the graph
48 48 * @param index The index where the graph should be inserted in the layout
49 49 * @return the pointer to the created graph
50 50 */
51 51 VisualizationGraphWidget *createGraph(std::shared_ptr<Variable> variable, int index);
52 52
53 53 /**
54 54 * Creates a graph using a list of variables. The variables will be displayed in the new graph.
55 55 * The graph is inserted at the specified index.
56 56 * @param variables List of variables to be added to the graph
57 57 * @param index The index where the graph should be inserted in the layout
58 58 * @return the pointer to the created graph
59 59 */
60 60 VisualizationGraphWidget *createGraph(const QList<std::shared_ptr<Variable> > variables,
61 61 int index);
62 62
63 63 // IVisualizationWidget interface
64 64 void accept(IVisualizationWidgetVisitor *visitor) override;
65 65 bool canDrop(const Variable &variable) const override;
66 66 bool contains(const Variable &variable) const override;
67 67 QString name() const override;
68 68
69 69 // VisualisationDragWidget
70 70 QMimeData *mimeData() const override;
71 71 bool isDragAllowed() const override;
72 72
73 void notifyMouseMoveInGraph(const QPointF &graphPosition, const QPointF &plotPosition,
74 VisualizationGraphWidget *graphWidget);
75 void notifyMouseLeaveGraph(VisualizationGraphWidget *graphWidget);
76
73 77 protected:
74 78 void closeEvent(QCloseEvent *event) override;
75 79
76 80 private:
77 81 Ui::VisualizationZoneWidget *ui;
78 82
79 83 class VisualizationZoneWidgetPrivate;
80 84 spimpl::unique_impl_ptr<VisualizationZoneWidgetPrivate> impl;
81 85
82 86 private slots:
83 87 void onVariableAdded(std::shared_ptr<Variable> variable);
84 88 /// Slot called when a variable is about to be removed from a graph contained in the zone
85 89 void onVariableAboutToBeRemoved(std::shared_ptr<Variable> variable);
86 90
87 91 void dropMimeData(int index, const QMimeData *mimeData);
88 92 void dropMimeDataOnGraph(VisualizationDragWidget *dragWidget, const QMimeData *mimeData);
89 93 };
90 94
91 95 #endif // SCIQLOP_VISUALIZATIONZONEWIDGET_H
@@ -1,98 +1,99
1 1
2 2 gui_moc_headers = [
3 3 'include/DataSource/DataSourceWidget.h',
4 4 'include/DataSource/DataSourceTreeWidget.h',
5 5 'include/Settings/SqpSettingsDialog.h',
6 6 'include/Settings/SqpSettingsGeneralWidget.h',
7 7 'include/SidePane/SqpSidePane.h',
8 8 'include/SqpApplication.h',
9 9 'include/DragAndDrop/DragDropHelper.h',
10 10 'include/DragAndDrop/DragDropScroller.h',
11 11 'include/DragAndDrop/DragDropTabSwitcher.h',
12 12 'include/TimeWidget/TimeWidget.h',
13 13 'include/Variable/VariableInspectorWidget.h',
14 14 'include/Variable/VariableInspectorTableView.h',
15 15 'include/Variable/RenameVariableDialog.h',
16 16 'include/Visualization/qcustomplot.h',
17 17 'include/Visualization/VisualizationGraphWidget.h',
18 18 'include/Visualization/VisualizationTabWidget.h',
19 19 'include/Visualization/VisualizationWidget.h',
20 20 'include/Visualization/VisualizationZoneWidget.h',
21 21 'include/Visualization/VisualizationDragDropContainer.h',
22 22 'include/Visualization/VisualizationDragWidget.h'
23 23 ]
24 24
25 25 gui_ui_files = [
26 26 'ui/DataSource/DataSourceWidget.ui',
27 27 'ui/Settings/SqpSettingsDialog.ui',
28 28 'ui/Settings/SqpSettingsGeneralWidget.ui',
29 29 'ui/SidePane/SqpSidePane.ui',
30 30 'ui/TimeWidget/TimeWidget.ui',
31 31 'ui/Variable/VariableInspectorWidget.ui',
32 32 'ui/Variable/RenameVariableDialog.ui',
33 33 'ui/Variable/VariableMenuHeaderWidget.ui',
34 34 'ui/Visualization/VisualizationGraphWidget.ui',
35 35 'ui/Visualization/VisualizationTabWidget.ui',
36 36 'ui/Visualization/VisualizationWidget.ui',
37 37 'ui/Visualization/VisualizationZoneWidget.ui'
38 38 ]
39 39
40 40 gui_qresources = ['resources/sqpguiresources.qrc']
41 41
42 42 gui_moc_files = qt5.preprocess(moc_headers : gui_moc_headers,
43 43 ui_files : gui_ui_files,
44 44 qresources : gui_qresources)
45 45
46 46 gui_sources = [
47 47 'src/SqpApplication.cpp',
48 48 'src/DragAndDrop/DragDropHelper.cpp',
49 49 'src/DragAndDrop/DragDropScroller.cpp',
50 50 'src/DragAndDrop/DragDropTabSwitcher.cpp',
51 51 'src/Common/ColorUtils.cpp',
52 52 'src/Common/VisualizationDef.cpp',
53 53 'src/DataSource/DataSourceTreeWidgetItem.cpp',
54 54 'src/DataSource/DataSourceTreeWidgetHelper.cpp',
55 55 'src/DataSource/DataSourceWidget.cpp',
56 56 'src/DataSource/DataSourceTreeWidget.cpp',
57 57 'src/Settings/SqpSettingsDialog.cpp',
58 58 'src/Settings/SqpSettingsGeneralWidget.cpp',
59 59 'src/SidePane/SqpSidePane.cpp',
60 60 'src/TimeWidget/TimeWidget.cpp',
61 61 'src/Variable/VariableInspectorWidget.cpp',
62 62 'src/Variable/VariableInspectorTableView.cpp',
63 63 'src/Variable/VariableMenuHeaderWidget.cpp',
64 64 'src/Variable/RenameVariableDialog.cpp',
65 65 'src/Visualization/VisualizationGraphHelper.cpp',
66 66 'src/Visualization/VisualizationGraphRenderingDelegate.cpp',
67 67 'src/Visualization/VisualizationGraphWidget.cpp',
68 68 'src/Visualization/VisualizationTabWidget.cpp',
69 69 'src/Visualization/VisualizationWidget.cpp',
70 70 'src/Visualization/VisualizationZoneWidget.cpp',
71 71 'src/Visualization/qcustomplot.cpp',
72 72 'src/Visualization/QCustomPlotSynchronizer.cpp',
73 73 'src/Visualization/operations/FindVariableOperation.cpp',
74 74 'src/Visualization/operations/GenerateVariableMenuOperation.cpp',
75 75 'src/Visualization/operations/MenuBuilder.cpp',
76 76 'src/Visualization/operations/RemoveVariableOperation.cpp',
77 77 'src/Visualization/operations/RescaleAxeOperation.cpp',
78 78 'src/Visualization/VisualizationDragDropContainer.cpp',
79 79 'src/Visualization/VisualizationDragWidget.cpp',
80 80 'src/Visualization/AxisRenderingUtils.cpp',
81 81 'src/Visualization/PlottablesRenderingUtils.cpp',
82 'src/Visualization/MacScrollBarStyle.cpp'
82 'src/Visualization/MacScrollBarStyle.cpp',
83 'src/Visualization/VisualizationCursorItem.cpp'
83 84 ]
84 85
85 86 gui_inc = include_directories(['include'])
86 87
87 88 sciqlop_gui_lib = library('sciqlopgui',
88 89 gui_sources,
89 90 gui_moc_files,
90 91 include_directories : [gui_inc],
91 92 dependencies : [ qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core],
92 93 install : true
93 94 )
94 95
95 96 sciqlop_gui = declare_dependency(link_with : sciqlop_gui_lib,
96 97 include_directories : gui_inc,
97 98 dependencies : [qt5printsupport, qt5gui, qt5widgets, qt5svg, sciqlop_core])
98 99
@@ -1,472 +1,473
1 1 #include "Visualization/VisualizationDragDropContainer.h"
2 2 #include "DragAndDrop/DragDropHelper.h"
3 3 #include "SqpApplication.h"
4 4 #include "Visualization/VisualizationDragWidget.h"
5 5
6 6 #include "Common/VisualizationDef.h"
7 7
8 8 #include <QDrag>
9 9 #include <QDragEnterEvent>
10 10 #include <QVBoxLayout>
11 11
12 12 #include <cmath>
13 13 #include <memory>
14 14
15 15 Q_LOGGING_CATEGORY(LOG_VisualizationDragDropContainer, "VisualizationDragDropContainer")
16 16
17 17 auto DRAGGED_MINIATURE_WIDTH = 200; // in pixels
18 18
19 19 struct VisualizationDragDropContainer::VisualizationDragDropContainerPrivate {
20 20
21 21 QVBoxLayout *m_Layout;
22 22 QHash<QString, VisualizationDragDropContainer::DropBehavior> m_AcceptedMimeTypes;
23 23 QString m_PlaceHolderText;
24 DragDropHelper::PlaceHolderType m_PlaceHolderType = DragDropHelper::PlaceHolderType::Graph;
24 DragDropHelper::PlaceHolderType m_PlaceHolderType;
25 25
26 26 VisualizationDragDropContainer::AcceptMimeDataFunction m_AcceptMimeDataFun
27 27 = [](auto mimeData) { return true; };
28 28
29 29 int m_MinContainerHeight = 0;
30 30
31 31 explicit VisualizationDragDropContainerPrivate(QWidget *widget)
32 : m_PlaceHolderType(DragDropHelper::PlaceHolderType::Graph)
32 33 {
33 34 m_Layout = new QVBoxLayout(widget);
34 35 m_Layout->setContentsMargins(0, 0, 0, 0);
35 36 }
36 37
37 38 bool acceptMimeData(const QMimeData *data) const
38 39 {
39 40 auto accepted = false;
40 41 for (auto it = m_AcceptedMimeTypes.constBegin(); it != m_AcceptedMimeTypes.constEnd();
41 42 ++it) {
42 43 const auto &type = it.key();
43 44 const auto &behavior = it.value();
44 45
45 46 if (data->hasFormat(type)) {
46 47 if (behavior != DropBehavior::Forbidden) {
47 48 accepted = true;
48 49 }
49 50 else {
50 51 accepted = false;
51 52 break;
52 53 }
53 54 }
54 55 }
55 56
56 57 if (accepted) {
57 58 accepted = m_AcceptMimeDataFun(data);
58 59 }
59 60
60 61 return accepted;
61 62 }
62 63
63 64 bool allowMergeForMimeData(const QMimeData *data) const
64 65 {
65 66 auto result = false;
66 67 for (auto it = m_AcceptedMimeTypes.constBegin(); it != m_AcceptedMimeTypes.constEnd();
67 68 ++it) {
68 69
69 70 if (data->hasFormat(it.key())
70 71 && (it.value() == VisualizationDragDropContainer::DropBehavior::Merged
71 72 || it.value()
72 73 == VisualizationDragDropContainer::DropBehavior::InsertedAndMerged)) {
73 74 result = true;
74 75 }
75 76 else if (data->hasFormat(it.key())
76 77 && it.value() == VisualizationDragDropContainer::DropBehavior::Inserted) {
77 78 // Merge is forbidden if the mime data contain an acceptable type which cannot be
78 79 // merged
79 80 result = false;
80 81 break;
81 82 }
82 83 }
83 84
84 85 return result;
85 86 }
86 87
87 88 bool allowInsertForMimeData(const QMimeData *data) const
88 89 {
89 90 for (auto it = m_AcceptedMimeTypes.constBegin(); it != m_AcceptedMimeTypes.constEnd();
90 91 ++it) {
91 92 if (data->hasFormat(it.key())
92 93 && (it.value() == VisualizationDragDropContainer::DropBehavior::Inserted
93 94 || it.value()
94 95 == VisualizationDragDropContainer::DropBehavior::InsertedAndMerged)) {
95 96 return true;
96 97 }
97 98 }
98 99
99 100 return false;
100 101 }
101 102
102 103 bool hasPlaceHolder() const
103 104 {
104 105 return sqpApp->dragDropHelper().placeHolder().parentWidget() == m_Layout->parentWidget();
105 106 }
106 107
107 108 VisualizationDragWidget *getChildDragWidgetAt(const QWidget *parent, const QPoint &pos) const
108 109 {
109 110 VisualizationDragWidget *dragWidget = nullptr;
110 111
111 112 for (auto child : parent->children()) {
112 113 auto widget = qobject_cast<VisualizationDragWidget *>(child);
113 114 if (widget && widget->isVisible()) {
114 115 if (widget->frameGeometry().contains(pos)) {
115 116 dragWidget = widget;
116 117 break;
117 118 }
118 119 }
119 120 }
120 121
121 122 return dragWidget;
122 123 }
123 124
124 125 bool cursorIsInContainer(QWidget *container) const
125 126 {
126 127 auto widgetUnderMouse = sqpApp->widgetAt(QCursor::pos());
127 128 return container->isAncestorOf(widgetUnderMouse) && widgetUnderMouse != container
128 129 && sqpApp->dragDropHelper().placeHolder().isAncestorOf(widgetUnderMouse);
129 130 }
130 131
131 132 int countDragWidget(const QWidget *parent, bool onlyVisible = false) const
132 133 {
133 134 auto nbGraph = 0;
134 135 for (auto child : parent->children()) {
135 136 if (qobject_cast<VisualizationDragWidget *>(child)) {
136 137 if (!onlyVisible || qobject_cast<VisualizationDragWidget *>(child)->isVisible()) {
137 138 nbGraph += 1;
138 139 }
139 140 }
140 141 }
141 142
142 143 return nbGraph;
143 144 }
144 145
145 146 void findPlaceHolderPosition(const QPoint &pos, bool canInsert, bool canMerge,
146 147 const VisualizationDragDropContainer *container);
147 148 };
148 149
149 150 VisualizationDragDropContainer::VisualizationDragDropContainer(QWidget *parent)
150 151 : QFrame{parent},
151 152 impl{spimpl::make_unique_impl<VisualizationDragDropContainerPrivate>(this)}
152 153 {
153 154 setAcceptDrops(true);
154 155 }
155 156
156 157 void VisualizationDragDropContainer::addDragWidget(VisualizationDragWidget *dragWidget)
157 158 {
158 159 impl->m_Layout->addWidget(dragWidget);
159 160 disconnect(dragWidget, &VisualizationDragWidget::dragDetected, nullptr, nullptr);
160 161 connect(dragWidget, &VisualizationDragWidget::dragDetected, this,
161 162 &VisualizationDragDropContainer::startDrag);
162 163 }
163 164
164 165 void VisualizationDragDropContainer::insertDragWidget(int index,
165 166 VisualizationDragWidget *dragWidget)
166 167 {
167 168 impl->m_Layout->insertWidget(index, dragWidget);
168 169 disconnect(dragWidget, &VisualizationDragWidget::dragDetected, nullptr, nullptr);
169 170 connect(dragWidget, &VisualizationDragWidget::dragDetected, this,
170 171 &VisualizationDragDropContainer::startDrag);
171 172 }
172 173
173 174 void VisualizationDragDropContainer::setMimeType(
174 175 const QString &mimeType, VisualizationDragDropContainer::DropBehavior behavior)
175 176 {
176 177 impl->m_AcceptedMimeTypes[mimeType] = behavior;
177 178 }
178 179
179 180 int VisualizationDragDropContainer::countDragWidget() const
180 181 {
181 182 return impl->countDragWidget(this);
182 183 }
183 184
184 185 void VisualizationDragDropContainer::setAcceptMimeDataFunction(
185 186 VisualizationDragDropContainer::AcceptMimeDataFunction fun)
186 187 {
187 188 impl->m_AcceptMimeDataFun = fun;
188 189 }
189 190
190 191 void VisualizationDragDropContainer::setPlaceHolderType(DragDropHelper::PlaceHolderType type,
191 192 const QString &placeHolderText)
192 193 {
193 194 impl->m_PlaceHolderType = type;
194 195 impl->m_PlaceHolderText = placeHolderText;
195 196 }
196 197
197 198 void VisualizationDragDropContainer::startDrag(VisualizationDragWidget *dragWidget,
198 199 const QPoint &dragPosition)
199 200 {
200 201 auto &helper = sqpApp->dragDropHelper();
201 202 helper.resetDragAndDrop();
202 203
203 204 // Note: The management of the drag object is done by Qt
204 205 auto drag = new QDrag{dragWidget};
205 206
206 207 auto mimeData = dragWidget->mimeData();
207 208 drag->setMimeData(mimeData);
208 209
209 210 auto pixmap = QPixmap(dragWidget->size());
210 211 dragWidget->render(&pixmap);
211 212 drag->setPixmap(pixmap.scaled(DRAGGED_MINIATURE_WIDTH, DRAGGED_MINIATURE_WIDTH,
212 213 Qt::KeepAspectRatio, Qt::SmoothTransformation));
213 214
214 215 auto image = pixmap.toImage();
215 216 mimeData->setImageData(image);
216 217 mimeData->setUrls({helper.imageTemporaryUrl(image)});
217 218
218 219 if (impl->m_Layout->indexOf(dragWidget) >= 0) {
219 220 helper.setCurrentDragWidget(dragWidget);
220 221
221 222 if (impl->cursorIsInContainer(this)) {
222 223 auto dragWidgetIndex = impl->m_Layout->indexOf(dragWidget);
223 224 helper.insertPlaceHolder(impl->m_Layout, dragWidgetIndex, impl->m_PlaceHolderType,
224 225 impl->m_PlaceHolderText);
225 226 dragWidget->setVisible(false);
226 227 }
227 228 else {
228 229 // The drag starts directly outside the drop zone
229 230 // do not add the placeHolder
230 231 }
231 232
232 233 drag->exec(Qt::MoveAction | Qt::CopyAction, Qt::MoveAction);
233 234
234 235 helper.doCloseWidgets();
235 236 }
236 237 else {
237 238 qCWarning(LOG_VisualizationDragDropContainer())
238 239 << tr("VisualizationDragDropContainer::startDrag, drag aborted, the specified "
239 240 "VisualizationDragWidget is not found in this container.");
240 241 }
241 242 }
242 243
243 244 void VisualizationDragDropContainer::dragEnterEvent(QDragEnterEvent *event)
244 245 {
245 246 if (impl->acceptMimeData(event->mimeData())) {
246 247 event->acceptProposedAction();
247 248
248 249 auto &helper = sqpApp->dragDropHelper();
249 250
250 251 if (!impl->hasPlaceHolder()) {
251 252 auto dragWidget = helper.getCurrentDragWidget();
252 253
253 254 if (dragWidget) {
254 255 // If the drag&drop is internal to the visualization, entering the container hide
255 256 // the dragWidget which was made visible by the dragLeaveEvent
256 257 auto parentWidget
257 258 = qobject_cast<VisualizationDragDropContainer *>(dragWidget->parentWidget());
258 259 if (parentWidget) {
259 260 dragWidget->setVisible(false);
260 261 }
261 262 }
262 263
263 264 auto canMerge = impl->allowMergeForMimeData(event->mimeData());
264 265 auto canInsert = impl->allowInsertForMimeData(event->mimeData());
265 266 impl->findPlaceHolderPosition(event->pos(), canInsert, canMerge, this);
266 267 }
267 268 else {
268 269 // do nothing
269 270 }
270 271 }
271 272 else {
272 273 event->ignore();
273 274 }
274 275
275 276 QWidget::dragEnterEvent(event);
276 277 }
277 278
278 279 void VisualizationDragDropContainer::dragLeaveEvent(QDragLeaveEvent *event)
279 280 {
280 281 Q_UNUSED(event);
281 282
282 283 auto &helper = sqpApp->dragDropHelper();
283 284
284 285 if (!impl->cursorIsInContainer(this)) {
285 286 helper.removePlaceHolder();
286 287 helper.setHightlightedDragWidget(nullptr);
287 288 impl->m_MinContainerHeight = 0;
288 289
289 290 auto dragWidget = helper.getCurrentDragWidget();
290 291 if (dragWidget) {
291 292 // dragWidget has a value only if the drag is started from the visualization
292 293 // In that case, shows the drag widget at its original place
293 294 // So the drag widget doesn't stay hidden if the drop occurs outside the visualization
294 295 // drop zone (It is not possible to catch a drop event outside of the application)
295 296
296 297 if (dragWidget) {
297 298 dragWidget->setVisible(true);
298 299 }
299 300 }
300 301 }
301 302 else {
302 303 // Leave event probably received for a child widget.
303 304 // Do nothing.
304 305 // Note: The DragLeave event, doesn't have any mean to determine who sent it.
305 306 }
306 307
307 308 QWidget::dragLeaveEvent(event);
308 309 }
309 310
310 311 void VisualizationDragDropContainer::dragMoveEvent(QDragMoveEvent *event)
311 312 {
312 313 if (impl->acceptMimeData(event->mimeData())) {
313 314 auto canMerge = impl->allowMergeForMimeData(event->mimeData());
314 315 auto canInsert = impl->allowInsertForMimeData(event->mimeData());
315 316 impl->findPlaceHolderPosition(event->pos(), canInsert, canMerge, this);
316 317 }
317 318 else {
318 319 event->ignore();
319 320 }
320 321
321 322 QWidget::dragMoveEvent(event);
322 323 }
323 324
324 325 void VisualizationDragDropContainer::dropEvent(QDropEvent *event)
325 326 {
326 327 auto &helper = sqpApp->dragDropHelper();
327 328
328 329 if (impl->acceptMimeData(event->mimeData())) {
329 330 auto dragWidget = helper.getCurrentDragWidget();
330 331 if (impl->hasPlaceHolder()) {
331 332 // drop where the placeHolder is located
332 333
333 334 auto canInsert = impl->allowInsertForMimeData(event->mimeData());
334 335 if (canInsert) {
335 336 auto droppedIndex = impl->m_Layout->indexOf(&helper.placeHolder());
336 337
337 338 if (dragWidget) {
338 339 auto dragWidgetIndex = impl->m_Layout->indexOf(dragWidget);
339 340 if (dragWidgetIndex >= 0 && dragWidgetIndex < droppedIndex) {
340 341 // Correction of the index if the drop occurs in the same container
341 342 // and if the drag is started from the visualization (in that case, the
342 343 // dragWidget is hidden)
343 344 droppedIndex -= 1;
344 345 }
345 346
346 347 dragWidget->setVisible(true);
347 348 }
348 349
349 350 event->acceptProposedAction();
350 351
351 352 helper.removePlaceHolder();
352 353
353 354 emit dropOccuredInContainer(droppedIndex, event->mimeData());
354 355 }
355 356 else {
356 357 qCWarning(LOG_VisualizationDragDropContainer()) << tr(
357 358 "VisualizationDragDropContainer::dropEvent, dropping on the placeHolder, but "
358 359 "the insertion is forbidden.");
359 360 Q_ASSERT(false);
360 361 }
361 362 }
362 363 else if (helper.getHightlightedDragWidget()) {
363 364 // drop on the highlighted widget
364 365
365 366 auto canMerge = impl->allowMergeForMimeData(event->mimeData());
366 367 if (canMerge) {
367 368 event->acceptProposedAction();
368 369 emit dropOccuredOnWidget(helper.getHightlightedDragWidget(), event->mimeData());
369 370 }
370 371 else {
371 372 qCWarning(LOG_VisualizationDragDropContainer())
372 373 << tr("VisualizationDragDropContainer::dropEvent, dropping on a widget, but "
373 374 "the merge is forbidden.");
374 375 Q_ASSERT(false);
375 376 }
376 377 }
377 378 }
378 379 else {
379 380 event->ignore();
380 381 }
381 382
382 383 sqpApp->dragDropHelper().setHightlightedDragWidget(nullptr);
383 384 impl->m_MinContainerHeight = 0;
384 385
385 386 QWidget::dropEvent(event);
386 387 }
387 388
388 389
389 390 void VisualizationDragDropContainer::VisualizationDragDropContainerPrivate::findPlaceHolderPosition(
390 391 const QPoint &pos, bool canInsert, bool canMerge,
391 392 const VisualizationDragDropContainer *container)
392 393 {
393 394 auto &helper = sqpApp->dragDropHelper();
394 395
395 396 auto absPos = container->mapToGlobal(pos);
396 397 auto isOnPlaceHolder = helper.placeHolder().isAncestorOf(sqpApp->widgetAt(absPos));
397 398
398 399 if (countDragWidget(container, true) == 0) {
399 400 // Drop on an empty container, just add the placeHolder at the top
400 401 helper.insertPlaceHolder(m_Layout, 0, m_PlaceHolderType, m_PlaceHolderText);
401 402 }
402 403 else if (!isOnPlaceHolder) {
403 404 auto nbDragWidget = countDragWidget(container);
404 405 if (nbDragWidget > 0) {
405 406
406 407 if (m_MinContainerHeight == 0) {
407 408 m_MinContainerHeight = container->size().height();
408 409 }
409 410
410 411 m_MinContainerHeight = qMin(m_MinContainerHeight, container->size().height());
411 412 auto graphHeight = qMax(m_MinContainerHeight / nbDragWidget, GRAPH_MINIMUM_HEIGHT);
412 413
413 414 auto posY = pos.y();
414 415 auto dropIndex = floor(posY / graphHeight);
415 416 auto zoneSize = qMin(graphHeight / 4.0, 75.0);
416 417
417 418
418 419 auto isOnTop = posY < dropIndex * graphHeight + zoneSize;
419 420 auto isOnBottom = posY > (dropIndex + 1) * graphHeight - zoneSize;
420 421
421 422 auto placeHolderIndex = m_Layout->indexOf(&(helper.placeHolder()));
422 423
423 424 auto dragWidgetHovered = getChildDragWidgetAt(container, pos);
424 425
425 426 if (canInsert && (isOnTop || isOnBottom || !canMerge)) {
426 427 if (isOnBottom) {
427 428 dropIndex += 1;
428 429 }
429 430
430 431 if (helper.getCurrentDragWidget()) {
431 432 auto dragWidgetIndex = m_Layout->indexOf(helper.getCurrentDragWidget());
432 433 if (dragWidgetIndex >= 0 && dragWidgetIndex <= dropIndex) {
433 434 // Correction of the index if the drop occurs in the same container
434 435 // and if the drag is started from the visualization (in that case, the
435 436 // dragWidget is hidden)
436 437 dropIndex += 1;
437 438 }
438 439 }
439 440
440 441 if (dropIndex != placeHolderIndex) {
441 442 helper.insertPlaceHolder(m_Layout, dropIndex, m_PlaceHolderType,
442 443 m_PlaceHolderText);
443 444 }
444 445
445 446 helper.setHightlightedDragWidget(nullptr);
446 447 }
447 448 else if (canMerge && dragWidgetHovered) {
448 449 // drop on the middle -> merge
449 450 if (hasPlaceHolder()) {
450 451 helper.removePlaceHolder();
451 452 }
452 453
453 454 helper.setHightlightedDragWidget(dragWidgetHovered);
454 455 }
455 456 else {
456 457 qCWarning(LOG_VisualizationDragDropContainer())
457 458 << tr("VisualizationDragDropContainer::findPlaceHolderPosition, no valid drop "
458 459 "action.");
459 460 }
460 461 }
461 462 else {
462 463 qCWarning(LOG_VisualizationDragDropContainer())
463 464 << tr("VisualizationDragDropContainer::findPlaceHolderPosition, no widget "
464 465 "found in the "
465 466 "container");
466 467 }
467 468 }
468 469 else {
469 470 // the mouse is hover the placeHolder
470 471 // Do nothing
471 472 }
472 473 }
@@ -1,490 +1,593
1 1 #include "Visualization/VisualizationGraphWidget.h"
2 2 #include "Visualization/IVisualizationWidgetVisitor.h"
3 #include "Visualization/VisualizationCursorItem.h"
3 4 #include "Visualization/VisualizationDefs.h"
4 5 #include "Visualization/VisualizationGraphHelper.h"
5 6 #include "Visualization/VisualizationGraphRenderingDelegate.h"
6 7 #include "Visualization/VisualizationZoneWidget.h"
7 8 #include "ui_VisualizationGraphWidget.h"
8 9
9 10 #include <Common/MimeTypesDef.h>
10 11 #include <Data/ArrayData.h>
11 12 #include <Data/IDataSeries.h>
12 13 #include <DragAndDrop/DragDropHelper.h>
13 14 #include <Settings/SqpSettingsDefs.h>
14 15 #include <SqpApplication.h>
15 16 #include <Time/TimeController.h>
16 17 #include <Variable/Variable.h>
17 18 #include <Variable/VariableController.h>
18 19
19 20 #include <unordered_map>
20 21
21 22 Q_LOGGING_CATEGORY(LOG_VisualizationGraphWidget, "VisualizationGraphWidget")
22 23
23 24 namespace {
24 25
25 26 /// Key pressed to enable zoom on horizontal axis
26 27 const auto HORIZONTAL_ZOOM_MODIFIER = Qt::ControlModifier;
27 28
28 29 /// Key pressed to enable zoom on vertical axis
29 30 const auto VERTICAL_ZOOM_MODIFIER = Qt::ShiftModifier;
30 31
31 32 /// Speed of a step of a wheel event for a pan, in percentage of the axis range
32 33 const auto PAN_SPEED = 5;
33 34
34 35 /// Key pressed to enable a calibration pan
35 36 const auto VERTICAL_PAN_MODIFIER = Qt::AltModifier;
36 37
37 38 /// Minimum size for the zoom box, in percentage of the axis range
38 39 const auto ZOOM_BOX_MIN_SIZE = 0.8;
39 40
41 /// Format of the dates appearing in the label of a cursor
42 const auto CURSOR_LABELS_DATETIME_FORMAT = QStringLiteral("yyyy/MM/dd\nhh:mm:ss:zzz");
43
40 44 } // namespace
41 45
42 46 struct VisualizationGraphWidget::VisualizationGraphWidgetPrivate {
43 47
44 48 explicit VisualizationGraphWidgetPrivate(const QString &name)
45 49 : m_Name{name},
46 50 m_DoAcquisition{true},
47 51 m_IsCalibration{false},
48 52 m_RenderingDelegate{nullptr}
49 53 {
50 54 }
51 55
52 56 QString m_Name;
53 57 // 1 variable -> n qcpplot
54 58 std::map<std::shared_ptr<Variable>, PlottablesMap> m_VariableToPlotMultiMap;
55 59 bool m_DoAcquisition;
56 60 bool m_IsCalibration;
57 61 /// Delegate used to attach rendering features to the plot
58 62 std::unique_ptr<VisualizationGraphRenderingDelegate> m_RenderingDelegate;
59 63
60 64 QCPItemRect *m_DrawingRect = nullptr;
65 std::unique_ptr<VisualizationCursorItem> m_HorizontalCursor = nullptr;
66 std::unique_ptr<VisualizationCursorItem> m_VerticalCursor = nullptr;
61 67
62 68 void configureDrawingRect()
63 69 {
64 70 if (m_DrawingRect) {
65 71 QPen p;
66 72 p.setWidth(2);
67 73 m_DrawingRect->setPen(p);
68 74 }
69 75 }
70 76
71 77 void startDrawingRect(const QPoint &pos, QCustomPlot &plot)
72 78 {
73 79 removeDrawingRect(plot);
74 80
75 81 auto axisPos = posToAxisPos(pos, plot);
76 82
77 83 m_DrawingRect = new QCPItemRect{&plot};
78 84 configureDrawingRect();
79 85
80 86 m_DrawingRect->topLeft->setCoords(axisPos);
81 87 m_DrawingRect->bottomRight->setCoords(axisPos);
82 88 }
83 89
84 90 void removeDrawingRect(QCustomPlot &plot)
85 91 {
86 92 if (m_DrawingRect) {
87 93 plot.removeItem(m_DrawingRect); // the item is deleted by QCustomPlot
88 94 m_DrawingRect = nullptr;
89 95 plot.replot(QCustomPlot::rpQueuedReplot);
90 96 }
91 97 }
92 98
93 99 QPointF posToAxisPos(const QPoint &pos, QCustomPlot &plot) const
94 100 {
95 101 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
96 102 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
97 103 return QPointF{axisX->pixelToCoord(pos.x()), axisY->pixelToCoord(pos.y())};
98 104 }
105
106 bool pointIsInAxisRect(const QPointF &axisPoint, QCustomPlot &plot) const
107 {
108 auto axisX = plot.axisRect()->axis(QCPAxis::atBottom);
109 auto axisY = plot.axisRect()->axis(QCPAxis::atLeft);
110
111 return axisX->range().contains(axisPoint.x()) && axisY->range().contains(axisPoint.y());
112 }
99 113 };
100 114
101 115 VisualizationGraphWidget::VisualizationGraphWidget(const QString &name, QWidget *parent)
102 116 : VisualizationDragWidget{parent},
103 117 ui{new Ui::VisualizationGraphWidget},
104 118 impl{spimpl::make_unique_impl<VisualizationGraphWidgetPrivate>(name)}
105 119 {
106 120 ui->setupUi(this);
107 121
108 122 // 'Close' options : widget is deleted when closed
109 123 setAttribute(Qt::WA_DeleteOnClose);
110 124
111 125 // Set qcpplot properties :
112 126 // - Drag (on x-axis) and zoom are enabled
113 127 // - Mouse wheel on qcpplot is intercepted to determine the zoom orientation
114 128 ui->widget->setInteractions(QCP::iRangeZoom | QCP::iSelectItems);
115 129
116 130 // The delegate must be initialized after the ui as it uses the plot
117 131 impl->m_RenderingDelegate = std::make_unique<VisualizationGraphRenderingDelegate>(*this);
118 132
133 // Init the cursors
134 impl->m_HorizontalCursor = std::make_unique<VisualizationCursorItem>(&plot());
135 impl->m_HorizontalCursor->setOrientation(Qt::Horizontal);
136 impl->m_VerticalCursor = std::make_unique<VisualizationCursorItem>(&plot());
137 impl->m_VerticalCursor->setOrientation(Qt::Vertical);
138
119 139 connect(ui->widget, &QCustomPlot::mousePress, this, &VisualizationGraphWidget::onMousePress);
120 140 connect(ui->widget, &QCustomPlot::mouseRelease, this,
121 141 &VisualizationGraphWidget::onMouseRelease);
122 142 connect(ui->widget, &QCustomPlot::mouseMove, this, &VisualizationGraphWidget::onMouseMove);
123 143 connect(ui->widget, &QCustomPlot::mouseWheel, this, &VisualizationGraphWidget::onMouseWheel);
124 144 connect(ui->widget->xAxis, static_cast<void (QCPAxis::*)(const QCPRange &, const QCPRange &)>(
125 145 &QCPAxis::rangeChanged),
126 146 this, &VisualizationGraphWidget::onRangeChanged, Qt::DirectConnection);
127 147
128 148 // Activates menu when right clicking on the graph
129 149 ui->widget->setContextMenuPolicy(Qt::CustomContextMenu);
130 150 connect(ui->widget, &QCustomPlot::customContextMenuRequested, this,
131 151 &VisualizationGraphWidget::onGraphMenuRequested);
132 152
133 153 connect(this, &VisualizationGraphWidget::requestDataLoading, &sqpApp->variableController(),
134 154 &VariableController::onRequestDataLoading);
135 155
136 156 connect(&sqpApp->variableController(), &VariableController::updateVarDisplaying, this,
137 157 &VisualizationGraphWidget::onUpdateVarDisplaying);
138 158 }
139 159
140 160
141 161 VisualizationGraphWidget::~VisualizationGraphWidget()
142 162 {
143 163 delete ui;
144 164 }
145 165
146 166 VisualizationZoneWidget *VisualizationGraphWidget::parentZoneWidget() const noexcept
147 167 {
148 168 auto parent = parentWidget();
149 169 while (parent != nullptr && !qobject_cast<VisualizationZoneWidget *>(parent)) {
150 170 parent = parent->parentWidget();
151 171 }
152 172
153 173 return qobject_cast<VisualizationZoneWidget *>(parent);
154 174 }
155 175
156 176 void VisualizationGraphWidget::enableAcquisition(bool enable)
157 177 {
158 178 impl->m_DoAcquisition = enable;
159 179 }
160 180
161 181 void VisualizationGraphWidget::addVariable(std::shared_ptr<Variable> variable, SqpRange range)
162 182 {
163 183 // Uses delegate to create the qcpplot components according to the variable
164 184 auto createdPlottables = VisualizationGraphHelper::create(variable, *ui->widget);
165 185
166 186 if (auto dataSeries = variable->dataSeries()) {
167 187 // Set axes properties according to the units of the data series
168 188 impl->m_RenderingDelegate->setAxesProperties(dataSeries);
169 189
170 190 // Sets rendering properties for the new plottables
171 191 // Warning: this method must be called after setAxesProperties(), as it can access to some
172 192 // axes properties that have to be initialized
173 193 impl->m_RenderingDelegate->setPlottablesProperties(dataSeries, createdPlottables);
174 194 }
175 195
176 196 impl->m_VariableToPlotMultiMap.insert({variable, std::move(createdPlottables)});
177 197
178 198 connect(variable.get(), SIGNAL(updated()), this, SLOT(onDataCacheVariableUpdated()));
179 199
180 200 this->enableAcquisition(false);
181 201 this->setGraphRange(range);
182 202 this->enableAcquisition(true);
183 203
184 204 emit requestDataLoading(QVector<std::shared_ptr<Variable> >() << variable, range, false);
185 205
186 206 emit variableAdded(variable);
187 207 }
188 208
189 209 void VisualizationGraphWidget::removeVariable(std::shared_ptr<Variable> variable) noexcept
190 210 {
191 211 // Each component associated to the variable :
192 212 // - is removed from qcpplot (which deletes it)
193 213 // - is no longer referenced in the map
194 214 auto variableIt = impl->m_VariableToPlotMultiMap.find(variable);
195 215 if (variableIt != impl->m_VariableToPlotMultiMap.cend()) {
196 216 emit variableAboutToBeRemoved(variable);
197 217
198 218 auto &plottablesMap = variableIt->second;
199 219
200 220 for (auto plottableIt = plottablesMap.cbegin(), plottableEnd = plottablesMap.cend();
201 221 plottableIt != plottableEnd;) {
202 222 ui->widget->removePlottable(plottableIt->second);
203 223 plottableIt = plottablesMap.erase(plottableIt);
204 224 }
205 225
206 226 impl->m_VariableToPlotMultiMap.erase(variableIt);
207 227 }
208 228
209 229 // Updates graph
210 230 ui->widget->replot();
211 231 }
212 232
213 233 QList<std::shared_ptr<Variable> > VisualizationGraphWidget::variables() const
214 234 {
215 235 auto variables = QList<std::shared_ptr<Variable> >{};
216 236 for (auto it = std::cbegin(impl->m_VariableToPlotMultiMap);
217 237 it != std::cend(impl->m_VariableToPlotMultiMap); ++it) {
218 238 variables << it->first;
219 239 }
220 240
221 241 return variables;
222 242 }
223 243
224 244 void VisualizationGraphWidget::setYRange(std::shared_ptr<Variable> variable)
225 245 {
226 246 if (!variable) {
227 247 qCCritical(LOG_VisualizationGraphWidget()) << "Can't set y-axis range: variable is null";
228 248 return;
229 249 }
230 250
231 251 VisualizationGraphHelper::setYAxisRange(variable, *ui->widget);
232 252 }
233 253
234 254 SqpRange VisualizationGraphWidget::graphRange() const noexcept
235 255 {
236 256 auto graphRange = ui->widget->xAxis->range();
237 257 return SqpRange{graphRange.lower, graphRange.upper};
238 258 }
239 259
240 260 void VisualizationGraphWidget::setGraphRange(const SqpRange &range)
241 261 {
242 262 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange START");
243 263 ui->widget->xAxis->setRange(range.m_TStart, range.m_TEnd);
244 264 ui->widget->replot();
245 265 qCDebug(LOG_VisualizationGraphWidget()) << tr("VisualizationGraphWidget::setGraphRange END");
246 266 }
247 267
248 268 void VisualizationGraphWidget::accept(IVisualizationWidgetVisitor *visitor)
249 269 {
250 270 if (visitor) {
251 271 visitor->visit(this);
252 272 }
253 273 else {
254 274 qCCritical(LOG_VisualizationGraphWidget())
255 275 << tr("Can't visit widget : the visitor is null");
256 276 }
257 277 }
258 278
259 279 bool VisualizationGraphWidget::canDrop(const Variable &variable) const
260 280 {
261 281 /// @todo : for the moment, a graph can always accomodate a variable
262 282 Q_UNUSED(variable);
263 283 return true;
264 284 }
265 285
266 286 bool VisualizationGraphWidget::contains(const Variable &variable) const
267 287 {
268 288 // Finds the variable among the keys of the map
269 289 auto variablePtr = &variable;
270 290 auto findVariable
271 291 = [variablePtr](const auto &entry) { return variablePtr == entry.first.get(); };
272 292
273 293 auto end = impl->m_VariableToPlotMultiMap.cend();
274 294 auto it = std::find_if(impl->m_VariableToPlotMultiMap.cbegin(), end, findVariable);
275 295 return it != end;
276 296 }
277 297
278 298 QString VisualizationGraphWidget::name() const
279 299 {
280 300 return impl->m_Name;
281 301 }
282 302
283 303 QMimeData *VisualizationGraphWidget::mimeData() const
284 304 {
285 305 auto mimeData = new QMimeData;
286 306 mimeData->setData(MIME_TYPE_GRAPH, QByteArray{});
287 307
288 308 auto timeRangeData = TimeController::mimeDataForTimeRange(graphRange());
289 309 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
290 310
291 311 return mimeData;
292 312 }
293 313
294 314 bool VisualizationGraphWidget::isDragAllowed() const
295 315 {
296 316 return true;
297 317 }
298 318
299 319 void VisualizationGraphWidget::highlightForMerge(bool highlighted)
300 320 {
301 321 if (highlighted) {
302 322 plot().setBackground(QBrush(QColor("#BBD5EE")));
303 323 }
304 324 else {
305 325 plot().setBackground(QBrush(Qt::white));
306 326 }
307 327
308 328 plot().update();
309 329 }
310 330
331 void VisualizationGraphWidget::addVerticalCursor(double time)
332 {
333 impl->m_VerticalCursor->setPosition(time);
334 impl->m_VerticalCursor->setVisible(true);
335
336 auto text
337 = DateUtils::dateTime(time).toString(CURSOR_LABELS_DATETIME_FORMAT).replace(' ', '\n');
338 impl->m_VerticalCursor->setLabelText(text);
339 }
340
341 void VisualizationGraphWidget::addVerticalCursorAtViewportPosition(double position)
342 {
343 impl->m_VerticalCursor->setAbsolutePosition(position);
344 impl->m_VerticalCursor->setVisible(true);
345
346 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
347 auto text
348 = DateUtils::dateTime(axis->pixelToCoord(position)).toString(CURSOR_LABELS_DATETIME_FORMAT);
349 impl->m_VerticalCursor->setLabelText(text);
350 }
351
352 void VisualizationGraphWidget::removeVerticalCursor()
353 {
354 impl->m_VerticalCursor->setVisible(false);
355 plot().replot(QCustomPlot::rpQueuedReplot);
356 }
357
358 void VisualizationGraphWidget::addHorizontalCursor(double value)
359 {
360 impl->m_HorizontalCursor->setPosition(value);
361 impl->m_HorizontalCursor->setVisible(true);
362 impl->m_HorizontalCursor->setLabelText(QString::number(value));
363 }
364
365 void VisualizationGraphWidget::addHorizontalCursorAtViewportPosition(double position)
366 {
367 impl->m_HorizontalCursor->setAbsolutePosition(position);
368 impl->m_HorizontalCursor->setVisible(true);
369
370 auto axis = plot().axisRect()->axis(QCPAxis::atLeft);
371 impl->m_HorizontalCursor->setLabelText(QString::number(axis->pixelToCoord(position)));
372 }
373
374 void VisualizationGraphWidget::removeHorizontalCursor()
375 {
376 impl->m_HorizontalCursor->setVisible(false);
377 plot().replot(QCustomPlot::rpQueuedReplot);
378 }
379
311 380 void VisualizationGraphWidget::closeEvent(QCloseEvent *event)
312 381 {
313 382 Q_UNUSED(event);
314 383
315 384 // Prevents that all variables will be removed from graph when it will be closed
316 385 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
317 386 emit variableAboutToBeRemoved(variableEntry.first);
318 387 }
319 388 }
320 389
321 390 void VisualizationGraphWidget::enterEvent(QEvent *event)
322 391 {
323 392 Q_UNUSED(event);
324 393 impl->m_RenderingDelegate->showGraphOverlay(true);
325 394 }
326 395
327 396 void VisualizationGraphWidget::leaveEvent(QEvent *event)
328 397 {
329 398 Q_UNUSED(event);
330 399 impl->m_RenderingDelegate->showGraphOverlay(false);
400
401 if (auto parentZone = parentZoneWidget()) {
402 parentZone->notifyMouseLeaveGraph(this);
403 }
404 else {
405 qCWarning(LOG_VisualizationGraphWidget()) << "leaveEvent: No parent zone widget";
406 }
331 407 }
332 408
333 409 QCustomPlot &VisualizationGraphWidget::plot() noexcept
334 410 {
335 411 return *ui->widget;
336 412 }
337 413
338 414 void VisualizationGraphWidget::onGraphMenuRequested(const QPoint &pos) noexcept
339 415 {
340 416 QMenu graphMenu{};
341 417
342 418 // Iterates on variables (unique keys)
343 419 for (auto it = impl->m_VariableToPlotMultiMap.cbegin(),
344 420 end = impl->m_VariableToPlotMultiMap.cend();
345 421 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
346 422 // 'Remove variable' action
347 423 graphMenu.addAction(tr("Remove variable %1").arg(it->first->name()),
348 424 [ this, var = it->first ]() { removeVariable(var); });
349 425 }
350 426
351 427 if (!graphMenu.isEmpty()) {
352 428 graphMenu.exec(QCursor::pos());
353 429 }
354 430 }
355 431
356 432 void VisualizationGraphWidget::onRangeChanged(const QCPRange &t1, const QCPRange &t2)
357 433 {
358 434 qCDebug(LOG_VisualizationGraphWidget()) << tr("TORM: VisualizationGraphWidget::onRangeChanged")
359 435 << QThread::currentThread()->objectName() << "DoAcqui"
360 436 << impl->m_DoAcquisition;
361 437
362 438 auto graphRange = SqpRange{t1.lower, t1.upper};
363 439 auto oldGraphRange = SqpRange{t2.lower, t2.upper};
364 440
365 441 if (impl->m_DoAcquisition) {
366 442 QVector<std::shared_ptr<Variable> > variableUnderGraphVector;
367 443
368 444 for (auto it = impl->m_VariableToPlotMultiMap.begin(),
369 445 end = impl->m_VariableToPlotMultiMap.end();
370 446 it != end; it = impl->m_VariableToPlotMultiMap.upper_bound(it->first)) {
371 447 variableUnderGraphVector.push_back(it->first);
372 448 }
373 449 emit requestDataLoading(std::move(variableUnderGraphVector), graphRange,
374 450 !impl->m_IsCalibration);
375 451
376 452 if (!impl->m_IsCalibration) {
377 453 qCDebug(LOG_VisualizationGraphWidget())
378 454 << tr("TORM: VisualizationGraphWidget::Synchronize notify !!")
379 455 << QThread::currentThread()->objectName() << graphRange << oldGraphRange;
380 456 emit synchronize(graphRange, oldGraphRange);
381 457 }
382 458 }
459
460 auto pos = mapFromGlobal(QCursor::pos());
461 auto axisPos = impl->posToAxisPos(pos, plot());
462 if (auto parentZone = parentZoneWidget()) {
463 if (impl->pointIsInAxisRect(axisPos, plot())) {
464 parentZone->notifyMouseMoveInGraph(pos, axisPos, this);
465 }
466 else {
467 parentZone->notifyMouseLeaveGraph(this);
468 }
469 }
470 else {
471 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
472 }
383 473 }
384 474
385 475 void VisualizationGraphWidget::onMouseMove(QMouseEvent *event) noexcept
386 476 {
387 477 // Handles plot rendering when mouse is moving
388 478 impl->m_RenderingDelegate->onMouseMove(event);
389 479
480 auto axisPos = impl->posToAxisPos(event->pos(), plot());
481
390 482 if (impl->m_DrawingRect) {
391 auto axisPos = impl->posToAxisPos(event->pos(), plot());
392 483 impl->m_DrawingRect->bottomRight->setCoords(axisPos);
393 484 }
394 485
486 if (auto parentZone = parentZoneWidget()) {
487 if (impl->pointIsInAxisRect(axisPos, plot())) {
488 parentZone->notifyMouseMoveInGraph(event->pos(), axisPos, this);
489 }
490 else {
491 parentZone->notifyMouseLeaveGraph(this);
492 }
493 }
494 else {
495 qCWarning(LOG_VisualizationGraphWidget()) << "onMouseMove: No parent zone widget";
496 }
497
395 498 VisualizationDragWidget::mouseMoveEvent(event);
396 499 }
397 500
398 501 void VisualizationGraphWidget::onMouseWheel(QWheelEvent *event) noexcept
399 502 {
400 503 auto value = event->angleDelta().x() + event->angleDelta().y();
401 504 if (value != 0) {
402 505
403 506 auto direction = value > 0 ? 1.0 : -1.0;
404 507 auto isZoomX = event->modifiers().testFlag(HORIZONTAL_ZOOM_MODIFIER);
405 508 auto isZoomY = event->modifiers().testFlag(VERTICAL_ZOOM_MODIFIER);
406 509 impl->m_IsCalibration = event->modifiers().testFlag(VERTICAL_PAN_MODIFIER);
407 510
408 511 auto zoomOrientations = QFlags<Qt::Orientation>{};
409 512 zoomOrientations.setFlag(Qt::Horizontal, isZoomX);
410 513 zoomOrientations.setFlag(Qt::Vertical, isZoomY);
411 514
412 515 ui->widget->axisRect()->setRangeZoom(zoomOrientations);
413 516
414 517 if (!isZoomX && !isZoomY) {
415 518 auto axis = plot().axisRect()->axis(QCPAxis::atBottom);
416 519 auto diff = direction * (axis->range().size() * (PAN_SPEED / 100.0));
417 520
418 521 axis->setRange(axis->range() + diff);
419 522
420 523 if (plot().noAntialiasingOnDrag()) {
421 524 plot().setNotAntialiasedElements(QCP::aeAll);
422 525 }
423 526
424 527 plot().replot(QCustomPlot::rpQueuedReplot);
425 528 }
426 529 }
427 530 }
428 531
429 532 void VisualizationGraphWidget::onMousePress(QMouseEvent *event) noexcept
430 533 {
431 534 if (sqpApp->plotsInteractionMode() == SqpApplication::PlotsInteractionMode::ZoomBox) {
432 535 impl->startDrawingRect(event->pos(), plot());
433 536 }
434 537
435 538 VisualizationDragWidget::mousePressEvent(event);
436 539 }
437 540
438 541 void VisualizationGraphWidget::onMouseRelease(QMouseEvent *event) noexcept
439 542 {
440 543 if (impl->m_DrawingRect) {
441 544
442 545 auto axisX = plot().axisRect()->axis(QCPAxis::atBottom);
443 546 auto axisY = plot().axisRect()->axis(QCPAxis::atLeft);
444 547
445 548 auto newAxisXRange = QCPRange{impl->m_DrawingRect->topLeft->coords().x(),
446 549 impl->m_DrawingRect->bottomRight->coords().x()};
447 550
448 551 auto newAxisYRange = QCPRange{impl->m_DrawingRect->topLeft->coords().y(),
449 552 impl->m_DrawingRect->bottomRight->coords().y()};
450 553
451 554 impl->removeDrawingRect(plot());
452 555
453 556 if (newAxisXRange.size() > axisX->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)
454 557 && newAxisYRange.size() > axisY->range().size() * (ZOOM_BOX_MIN_SIZE / 100.0)) {
455 558 axisX->setRange(newAxisXRange);
456 559 axisY->setRange(newAxisYRange);
457 560
458 561 plot().replot(QCustomPlot::rpQueuedReplot);
459 562 }
460 563 }
461 564
462 565 impl->m_IsCalibration = false;
463 566 }
464 567
465 568 void VisualizationGraphWidget::onDataCacheVariableUpdated()
466 569 {
467 570 auto graphRange = ui->widget->xAxis->range();
468 571 auto dateTime = SqpRange{graphRange.lower, graphRange.upper};
469 572
470 573 for (auto &variableEntry : impl->m_VariableToPlotMultiMap) {
471 574 auto variable = variableEntry.first;
472 575 qCDebug(LOG_VisualizationGraphWidget())
473 576 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated S" << variable->range();
474 577 qCDebug(LOG_VisualizationGraphWidget())
475 578 << "TORM: VisualizationGraphWidget::onDataCacheVariableUpdated E" << dateTime;
476 579 if (dateTime.contains(variable->range()) || dateTime.intersect(variable->range())) {
477 580 VisualizationGraphHelper::updateData(variableEntry.second, variable->dataSeries(),
478 581 variable->range());
479 582 }
480 583 }
481 584 }
482 585
483 586 void VisualizationGraphWidget::onUpdateVarDisplaying(std::shared_ptr<Variable> variable,
484 587 const SqpRange &range)
485 588 {
486 589 auto it = impl->m_VariableToPlotMultiMap.find(variable);
487 590 if (it != impl->m_VariableToPlotMultiMap.end()) {
488 591 VisualizationGraphHelper::updateData(it->second, variable->dataSeries(), range);
489 592 }
490 593 }
@@ -1,507 +1,560
1 1 #include "Visualization/VisualizationZoneWidget.h"
2 2
3 3 #include "Visualization/IVisualizationWidgetVisitor.h"
4 4 #include "Visualization/QCustomPlotSynchronizer.h"
5 5 #include "Visualization/VisualizationGraphWidget.h"
6 6 #include "Visualization/VisualizationWidget.h"
7 7 #include "ui_VisualizationZoneWidget.h"
8 8
9 9 #include "Common/MimeTypesDef.h"
10 10 #include "Common/VisualizationDef.h"
11 11
12 12 #include <Data/SqpRange.h>
13 13 #include <Time/TimeController.h>
14 14 #include <Variable/Variable.h>
15 15 #include <Variable/VariableController.h>
16 16
17 17 #include <Visualization/operations/FindVariableOperation.h>
18 18
19 19 #include <DragAndDrop/DragDropHelper.h>
20 20 #include <QUuid>
21 21 #include <SqpApplication.h>
22 22 #include <cmath>
23 23
24 24 #include <QLayout>
25 25
26 26 Q_LOGGING_CATEGORY(LOG_VisualizationZoneWidget, "VisualizationZoneWidget")
27 27
28 28 namespace {
29 29
30 30
31 31 /// Generates a default name for a new graph, according to the number of graphs already displayed in
32 32 /// the zone
33 33 QString defaultGraphName(const QLayout &layout)
34 34 {
35 35 auto count = 0;
36 36 for (auto i = 0; i < layout.count(); ++i) {
37 37 if (dynamic_cast<VisualizationGraphWidget *>(layout.itemAt(i)->widget())) {
38 38 count++;
39 39 }
40 40 }
41 41
42 42 return QObject::tr("Graph %1").arg(count + 1);
43 43 }
44 44
45 45 /**
46 46 * Applies a function to all graphs of the zone represented by its layout
47 47 * @param layout the layout that contains graphs
48 48 * @param fun the function to apply to each graph
49 49 */
50 50 template <typename Fun>
51 51 void processGraphs(QLayout &layout, Fun fun)
52 52 {
53 53 for (auto i = 0; i < layout.count(); ++i) {
54 54 if (auto item = layout.itemAt(i)) {
55 55 if (auto visualizationGraphWidget
56 = dynamic_cast<VisualizationGraphWidget *>(item->widget())) {
56 = qobject_cast<VisualizationGraphWidget *>(item->widget())) {
57 57 fun(*visualizationGraphWidget);
58 58 }
59 59 }
60 60 }
61 61 }
62 62
63 63 } // namespace
64 64
65 65 struct VisualizationZoneWidget::VisualizationZoneWidgetPrivate {
66 66
67 67 explicit VisualizationZoneWidgetPrivate()
68 68 : m_SynchronisationGroupId{QUuid::createUuid()},
69 69 m_Synchronizer{std::make_unique<QCustomPlotSynchronizer>()}
70 70 {
71 71 }
72 72 QUuid m_SynchronisationGroupId;
73 73 std::unique_ptr<IGraphSynchronizer> m_Synchronizer;
74 74
75 75 // Returns the first graph in the zone or nullptr if there is no graph inside
76 76 VisualizationGraphWidget *firstGraph(const VisualizationZoneWidget *zoneWidget) const
77 77 {
78 78 VisualizationGraphWidget *firstGraph = nullptr;
79 79 auto layout = zoneWidget->ui->dragDropContainer->layout();
80 80 if (layout->count() > 0) {
81 81 if (auto visualizationGraphWidget
82 82 = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
83 83 firstGraph = visualizationGraphWidget;
84 84 }
85 85 }
86 86
87 87 return firstGraph;
88 88 }
89 89
90 90 void dropGraph(int index, VisualizationZoneWidget *zoneWidget);
91 91 void dropVariables(const QList<std::shared_ptr<Variable> > &variables, int index,
92 92 VisualizationZoneWidget *zoneWidget);
93 93 };
94 94
95 95 VisualizationZoneWidget::VisualizationZoneWidget(const QString &name, QWidget *parent)
96 96 : VisualizationDragWidget{parent},
97 97 ui{new Ui::VisualizationZoneWidget},
98 98 impl{spimpl::make_unique_impl<VisualizationZoneWidgetPrivate>()}
99 99 {
100 100 ui->setupUi(this);
101 101
102 102 ui->zoneNameLabel->setText(name);
103 103
104 104 ui->dragDropContainer->setPlaceHolderType(DragDropHelper::PlaceHolderType::Graph);
105 105 ui->dragDropContainer->setMimeType(MIME_TYPE_GRAPH,
106 106 VisualizationDragDropContainer::DropBehavior::Inserted);
107 107 ui->dragDropContainer->setMimeType(
108 108 MIME_TYPE_VARIABLE_LIST, VisualizationDragDropContainer::DropBehavior::InsertedAndMerged);
109 109 ui->dragDropContainer->setMimeType(MIME_TYPE_TIME_RANGE,
110 110 VisualizationDragDropContainer::DropBehavior::Merged);
111 111 ui->dragDropContainer->setMimeType(MIME_TYPE_ZONE,
112 112 VisualizationDragDropContainer::DropBehavior::Forbidden);
113 113 ui->dragDropContainer->setAcceptMimeDataFunction([this](auto mimeData) {
114 114 return sqpApp->dragDropHelper().checkMimeDataForVisualization(mimeData,
115 115 ui->dragDropContainer);
116 116 });
117 117
118 118 connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccuredInContainer, this,
119 119 &VisualizationZoneWidget::dropMimeData);
120 120 connect(ui->dragDropContainer, &VisualizationDragDropContainer::dropOccuredOnWidget, this,
121 121 &VisualizationZoneWidget::dropMimeDataOnGraph);
122 122
123 123 // 'Close' options : widget is deleted when closed
124 124 setAttribute(Qt::WA_DeleteOnClose);
125 125 connect(ui->closeButton, &QToolButton::clicked, this, &VisualizationZoneWidget::close);
126 126 ui->closeButton->setIcon(sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton));
127 127
128 128 // Synchronisation id
129 129 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronizationGroupId",
130 130 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
131 131 }
132 132
133 133 VisualizationZoneWidget::~VisualizationZoneWidget()
134 134 {
135 135 delete ui;
136 136 }
137 137
138 138 void VisualizationZoneWidget::addGraph(VisualizationGraphWidget *graphWidget)
139 139 {
140 140 // Synchronize new graph with others in the zone
141 141 impl->m_Synchronizer->addGraph(*graphWidget);
142 142
143 143 ui->dragDropContainer->addDragWidget(graphWidget);
144 144 }
145 145
146 146 void VisualizationZoneWidget::insertGraph(int index, VisualizationGraphWidget *graphWidget)
147 147 {
148 148 // Synchronize new graph with others in the zone
149 149 impl->m_Synchronizer->addGraph(*graphWidget);
150 150
151 151 ui->dragDropContainer->insertDragWidget(index, graphWidget);
152 152 }
153 153
154 154 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable)
155 155 {
156 156 return createGraph(variable, -1);
157 157 }
158 158
159 159 VisualizationGraphWidget *VisualizationZoneWidget::createGraph(std::shared_ptr<Variable> variable,
160 160 int index)
161 161 {
162 162 auto graphWidget
163 163 = new VisualizationGraphWidget{defaultGraphName(*ui->dragDropContainer->layout()), this};
164 164
165 165
166 166 // Set graph properties
167 167 graphWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
168 168 graphWidget->setMinimumHeight(GRAPH_MINIMUM_HEIGHT);
169 169
170 170
171 171 // Lambda to synchronize zone widget
172 172 auto synchronizeZoneWidget = [this, graphWidget](const SqpRange &graphRange,
173 173 const SqpRange &oldGraphRange) {
174 174
175 175 auto zoomType = VariableController::getZoomType(graphRange, oldGraphRange);
176 176 auto frameLayout = ui->dragDropContainer->layout();
177 177 for (auto i = 0; i < frameLayout->count(); ++i) {
178 178 auto graphChild
179 179 = dynamic_cast<VisualizationGraphWidget *>(frameLayout->itemAt(i)->widget());
180 180 if (graphChild && (graphChild != graphWidget)) {
181 181
182 182 auto graphChildRange = graphChild->graphRange();
183 183 switch (zoomType) {
184 184 case AcquisitionZoomType::ZoomIn: {
185 185 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
186 186 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
187 187 graphChildRange.m_TStart += deltaLeft;
188 188 graphChildRange.m_TEnd -= deltaRight;
189 189 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomIn");
190 190 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
191 191 << deltaLeft;
192 192 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
193 193 << deltaRight;
194 194 qCDebug(LOG_VisualizationZoneWidget())
195 195 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
196 196
197 197 break;
198 198 }
199 199
200 200 case AcquisitionZoomType::ZoomOut: {
201 201 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: ZoomOut");
202 202 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
203 203 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
204 204 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaLeft")
205 205 << deltaLeft;
206 206 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: deltaRight")
207 207 << deltaRight;
208 208 qCDebug(LOG_VisualizationZoneWidget())
209 209 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
210 210 graphChildRange.m_TStart -= deltaLeft;
211 211 graphChildRange.m_TEnd += deltaRight;
212 212 break;
213 213 }
214 214 case AcquisitionZoomType::PanRight: {
215 215 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanRight");
216 216 auto deltaLeft = graphRange.m_TStart - oldGraphRange.m_TStart;
217 217 auto deltaRight = graphRange.m_TEnd - oldGraphRange.m_TEnd;
218 218 graphChildRange.m_TStart += deltaLeft;
219 219 graphChildRange.m_TEnd += deltaRight;
220 220 qCDebug(LOG_VisualizationZoneWidget())
221 221 << tr("TORM: dt") << graphRange.m_TEnd - graphRange.m_TStart;
222 222 break;
223 223 }
224 224 case AcquisitionZoomType::PanLeft: {
225 225 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: PanLeft");
226 226 auto deltaLeft = oldGraphRange.m_TStart - graphRange.m_TStart;
227 227 auto deltaRight = oldGraphRange.m_TEnd - graphRange.m_TEnd;
228 228 graphChildRange.m_TStart -= deltaLeft;
229 229 graphChildRange.m_TEnd -= deltaRight;
230 230 break;
231 231 }
232 232 case AcquisitionZoomType::Unknown: {
233 233 qCDebug(LOG_VisualizationZoneWidget())
234 234 << tr("Impossible to synchronize: zoom type unknown");
235 235 break;
236 236 }
237 237 default:
238 238 qCCritical(LOG_VisualizationZoneWidget())
239 239 << tr("Impossible to synchronize: zoom type not take into account");
240 240 // No action
241 241 break;
242 242 }
243 243 graphChild->enableAcquisition(false);
244 244 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: Range before: ")
245 245 << graphChild->graphRange();
246 246 qCDebug(LOG_VisualizationZoneWidget()) << tr("TORM: Range after : ")
247 247 << graphChildRange;
248 248 qCDebug(LOG_VisualizationZoneWidget())
249 249 << tr("TORM: child dt") << graphChildRange.m_TEnd - graphChildRange.m_TStart;
250 250 graphChild->setGraphRange(graphChildRange);
251 251 graphChild->enableAcquisition(true);
252 252 }
253 253 }
254 254 };
255 255
256 256 // connection for synchronization
257 257 connect(graphWidget, &VisualizationGraphWidget::synchronize, synchronizeZoneWidget);
258 258 connect(graphWidget, &VisualizationGraphWidget::variableAdded, this,
259 259 &VisualizationZoneWidget::onVariableAdded);
260 260 connect(graphWidget, &VisualizationGraphWidget::variableAboutToBeRemoved, this,
261 261 &VisualizationZoneWidget::onVariableAboutToBeRemoved);
262 262
263 263 auto range = SqpRange{};
264 264 if (auto firstGraph = impl->firstGraph(this)) {
265 265 // Case of a new graph in a existant zone
266 266 range = firstGraph->graphRange();
267 267 }
268 268 else {
269 269 // Case of a new graph as the first of the zone
270 270 range = variable->range();
271 271 }
272 272
273 273 this->insertGraph(index, graphWidget);
274 274
275 275 graphWidget->addVariable(variable, range);
276 276 graphWidget->setYRange(variable);
277 277
278 278 return graphWidget;
279 279 }
280 280
281 281 VisualizationGraphWidget *
282 282 VisualizationZoneWidget::createGraph(const QList<std::shared_ptr<Variable> > variables, int index)
283 283 {
284 284 if (variables.isEmpty()) {
285 285 return nullptr;
286 286 }
287 287
288 288 auto graphWidget = createGraph(variables.first(), index);
289 289 for (auto variableIt = variables.cbegin() + 1; variableIt != variables.cend(); ++variableIt) {
290 290 graphWidget->addVariable(*variableIt, graphWidget->graphRange());
291 291 }
292 292
293 293 return graphWidget;
294 294 }
295 295
296 296 void VisualizationZoneWidget::accept(IVisualizationWidgetVisitor *visitor)
297 297 {
298 298 if (visitor) {
299 299 visitor->visitEnter(this);
300 300
301 301 // Apply visitor to graph children: widgets different from graphs are not visited (no
302 302 // action)
303 303 processGraphs(
304 304 *ui->dragDropContainer->layout(),
305 305 [visitor](VisualizationGraphWidget &graphWidget) { graphWidget.accept(visitor); });
306 306
307 307 visitor->visitLeave(this);
308 308 }
309 309 else {
310 310 qCCritical(LOG_VisualizationZoneWidget()) << tr("Can't visit widget : the visitor is null");
311 311 }
312 312 }
313 313
314 314 bool VisualizationZoneWidget::canDrop(const Variable &variable) const
315 315 {
316 316 // A tab can always accomodate a variable
317 317 Q_UNUSED(variable);
318 318 return true;
319 319 }
320 320
321 321 bool VisualizationZoneWidget::contains(const Variable &variable) const
322 322 {
323 323 Q_UNUSED(variable);
324 324 return false;
325 325 }
326 326
327 327 QString VisualizationZoneWidget::name() const
328 328 {
329 329 return ui->zoneNameLabel->text();
330 330 }
331 331
332 332 QMimeData *VisualizationZoneWidget::mimeData() const
333 333 {
334 334 auto mimeData = new QMimeData;
335 335 mimeData->setData(MIME_TYPE_ZONE, QByteArray{});
336 336
337 337 if (auto firstGraph = impl->firstGraph(this)) {
338 338 auto timeRangeData = TimeController::mimeDataForTimeRange(firstGraph->graphRange());
339 339 mimeData->setData(MIME_TYPE_TIME_RANGE, timeRangeData);
340 340 }
341 341
342 342 return mimeData;
343 343 }
344 344
345 345 bool VisualizationZoneWidget::isDragAllowed() const
346 346 {
347 347 return true;
348 348 }
349 349
350 void VisualizationZoneWidget::notifyMouseMoveInGraph(const QPointF &graphPosition,
351 const QPointF &plotPosition,
352 VisualizationGraphWidget *graphWidget)
353 {
354 processGraphs(*ui->dragDropContainer->layout(), [&graphPosition, &plotPosition, &graphWidget](
355 VisualizationGraphWidget &processedGraph) {
356
357 switch (sqpApp->plotsCursorMode()) {
358 case SqpApplication::PlotsCursorMode::Vertical:
359 processedGraph.removeHorizontalCursor();
360 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
361 break;
362 case SqpApplication::PlotsCursorMode::Temporal:
363 processedGraph.addVerticalCursor(plotPosition.x());
364 processedGraph.removeHorizontalCursor();
365 break;
366 case SqpApplication::PlotsCursorMode::Horizontal:
367 processedGraph.removeVerticalCursor();
368 if (&processedGraph == graphWidget) {
369 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
370 }
371 else {
372 processedGraph.removeHorizontalCursor();
373 }
374 break;
375 case SqpApplication::PlotsCursorMode::Cross:
376 if (&processedGraph == graphWidget) {
377 processedGraph.addVerticalCursorAtViewportPosition(graphPosition.x());
378 processedGraph.addHorizontalCursorAtViewportPosition(graphPosition.y());
379 }
380 else {
381 processedGraph.removeHorizontalCursor();
382 processedGraph.removeVerticalCursor();
383 }
384 break;
385 case SqpApplication::PlotsCursorMode::NoCursor:
386 processedGraph.removeHorizontalCursor();
387 processedGraph.removeVerticalCursor();
388 break;
389 }
390
391
392 });
393 }
394
395 void VisualizationZoneWidget::notifyMouseLeaveGraph(VisualizationGraphWidget *graphWidget)
396 {
397 processGraphs(*ui->dragDropContainer->layout(), [](VisualizationGraphWidget &processedGraph) {
398 processedGraph.removeHorizontalCursor();
399 processedGraph.removeVerticalCursor();
400 });
401 }
402
350 403 void VisualizationZoneWidget::closeEvent(QCloseEvent *event)
351 404 {
352 405 // Closes graphs in the zone
353 406 processGraphs(*ui->dragDropContainer->layout(),
354 407 [](VisualizationGraphWidget &graphWidget) { graphWidget.close(); });
355 408
356 409 // Delete synchronization group from variable controller
357 410 QMetaObject::invokeMethod(&sqpApp->variableController(), "onRemoveSynchronizationGroupId",
358 411 Qt::QueuedConnection, Q_ARG(QUuid, impl->m_SynchronisationGroupId));
359 412
360 413 QWidget::closeEvent(event);
361 414 }
362 415
363 416 void VisualizationZoneWidget::onVariableAdded(std::shared_ptr<Variable> variable)
364 417 {
365 418 QMetaObject::invokeMethod(&sqpApp->variableController(), "onAddSynchronized",
366 419 Qt::QueuedConnection, Q_ARG(std::shared_ptr<Variable>, variable),
367 420 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
368 421 }
369 422
370 423 void VisualizationZoneWidget::onVariableAboutToBeRemoved(std::shared_ptr<Variable> variable)
371 424 {
372 425 QMetaObject::invokeMethod(&sqpApp->variableController(), "desynchronize", Qt::QueuedConnection,
373 426 Q_ARG(std::shared_ptr<Variable>, variable),
374 427 Q_ARG(QUuid, impl->m_SynchronisationGroupId));
375 428 }
376 429
377 430 void VisualizationZoneWidget::dropMimeData(int index, const QMimeData *mimeData)
378 431 {
379 432 if (mimeData->hasFormat(MIME_TYPE_GRAPH)) {
380 433 impl->dropGraph(index, this);
381 434 }
382 435 else if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
383 436 auto variables = sqpApp->variableController().variablesForMimeData(
384 437 mimeData->data(MIME_TYPE_VARIABLE_LIST));
385 438 impl->dropVariables(variables, index, this);
386 439 }
387 440 else {
388 441 qCWarning(LOG_VisualizationZoneWidget())
389 442 << tr("VisualizationZoneWidget::dropMimeData, unknown MIME data received.");
390 443 }
391 444 }
392 445
393 446 void VisualizationZoneWidget::dropMimeDataOnGraph(VisualizationDragWidget *dragWidget,
394 447 const QMimeData *mimeData)
395 448 {
396 449 auto graphWidget = qobject_cast<VisualizationGraphWidget *>(dragWidget);
397 450 if (!graphWidget) {
398 451 qCWarning(LOG_VisualizationZoneWidget())
399 452 << tr("VisualizationZoneWidget::dropMimeDataOnGraph, dropping in an unknown widget, "
400 453 "drop aborted");
401 454 Q_ASSERT(false);
402 455 return;
403 456 }
404 457
405 458 if (mimeData->hasFormat(MIME_TYPE_VARIABLE_LIST)) {
406 459 auto variables = sqpApp->variableController().variablesForMimeData(
407 460 mimeData->data(MIME_TYPE_VARIABLE_LIST));
408 461 for (const auto &var : variables) {
409 462 graphWidget->addVariable(var, graphWidget->graphRange());
410 463 }
411 464 }
412 465 else if (mimeData->hasFormat(MIME_TYPE_TIME_RANGE)) {
413 466 auto range = TimeController::timeRangeForMimeData(mimeData->data(MIME_TYPE_TIME_RANGE));
414 467 graphWidget->setGraphRange(range);
415 468 }
416 469 else {
417 470 qCWarning(LOG_VisualizationZoneWidget())
418 471 << tr("VisualizationZoneWidget::dropMimeDataOnGraph, unknown MIME data received.");
419 472 }
420 473 }
421 474
422 475 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropGraph(
423 476 int index, VisualizationZoneWidget *zoneWidget)
424 477 {
425 478 auto &helper = sqpApp->dragDropHelper();
426 479
427 480 auto graphWidget = qobject_cast<VisualizationGraphWidget *>(helper.getCurrentDragWidget());
428 481 if (!graphWidget) {
429 482 qCWarning(LOG_VisualizationZoneWidget())
430 483 << tr("VisualizationZoneWidget::dropGraph, drop aborted, the dropped graph is not "
431 484 "found or invalid.");
432 485 Q_ASSERT(false);
433 486 return;
434 487 }
435 488
436 489 auto parentDragDropContainer
437 490 = qobject_cast<VisualizationDragDropContainer *>(graphWidget->parentWidget());
438 491 if (!parentDragDropContainer) {
439 492 qCWarning(LOG_VisualizationZoneWidget())
440 493 << tr("VisualizationZoneWidget::dropGraph, drop aborted, the parent container of "
441 494 "the dropped graph is not found.");
442 495 Q_ASSERT(false);
443 496 return;
444 497 }
445 498
446 499 const auto &variables = graphWidget->variables();
447 500
448 501 if (parentDragDropContainer != zoneWidget->ui->dragDropContainer && !variables.isEmpty()) {
449 502 // The drop didn't occur in the same zone
450 503
451 504 // Abort the requests for the variables (if any)
452 505 // Commented, because it's not sure if it's needed or not
453 506 // for (const auto& var : variables)
454 507 //{
455 508 // sqpApp->variableController().onAbortProgressRequested(var);
456 509 //}
457 510
458 511 auto previousParentZoneWidget = graphWidget->parentZoneWidget();
459 512 auto nbGraph = parentDragDropContainer->countDragWidget();
460 513 if (nbGraph == 1) {
461 514 // This is the only graph in the previous zone, close the zone
462 515 helper.delayedCloseWidget(previousParentZoneWidget);
463 516 }
464 517 else {
465 518 // Close the graph
466 519 helper.delayedCloseWidget(graphWidget);
467 520 }
468 521
469 522 // Creates the new graph in the zone
470 523 zoneWidget->createGraph(variables, index);
471 524 }
472 525 else {
473 526 // The drop occurred in the same zone or the graph is empty
474 527 // Simple move of the graph, no variable operation associated
475 528 parentDragDropContainer->layout()->removeWidget(graphWidget);
476 529
477 530 if (variables.isEmpty() && parentDragDropContainer != zoneWidget->ui->dragDropContainer) {
478 531 // The graph is empty and dropped in a different zone.
479 532 // Take the range of the first graph in the zone (if existing).
480 533 auto layout = zoneWidget->ui->dragDropContainer->layout();
481 534 if (layout->count() > 0) {
482 535 if (auto visualizationGraphWidget
483 536 = qobject_cast<VisualizationGraphWidget *>(layout->itemAt(0)->widget())) {
484 537 graphWidget->setGraphRange(visualizationGraphWidget->graphRange());
485 538 }
486 539 }
487 540 }
488 541
489 542 zoneWidget->ui->dragDropContainer->insertDragWidget(index, graphWidget);
490 543 }
491 544 }
492 545
493 546 void VisualizationZoneWidget::VisualizationZoneWidgetPrivate::dropVariables(
494 547 const QList<std::shared_ptr<Variable> > &variables, int index,
495 548 VisualizationZoneWidget *zoneWidget)
496 549 {
497 550 // Note: the AcceptMimeDataFunction (set on the drop container) ensure there is a single and
498 551 // compatible variable here
499 552 if (variables.count() > 1) {
500 553 qCWarning(LOG_VisualizationZoneWidget())
501 554 << tr("VisualizationZoneWidget::dropVariables, dropping multiple variables, operation "
502 555 "aborted.");
503 556 return;
504 557 }
505 558
506 559 zoneWidget->createGraph(variables, index);
507 560 }
General Comments 1
Under Review
author

Auto status change to "Under Review"

You need to be logged in to leave comments. Login now