##// END OF EJS Templates
Merge pull request 371 from SciQLop-fork develop...
trabillard -
r1055:184d761e8d0c merge
parent child
Show More
@@ -0,0 +1,15
1 #ifndef SCIQLOP_DATASOURCEITEMMERGEHELPER_H
2 #define SCIQLOP_DATASOURCEITEMMERGEHELPER_H
3
4 class DataSourceItem;
5
6 /**
7 * @brief The DataSourceItemMergeHelper struct is used to merge two data source items
8 * @sa DataSourceItem::merge()
9 */
10 struct DataSourceItemMergeHelper {
11 /// Merges source item into dest item
12 static void merge(const DataSourceItem &source, DataSourceItem &dest);
13 };
14
15 #endif // SCIQLOP_DATASOURCEITEMMERGEHELPER_H
@@ -0,0 +1,55
1 #include "DataSource/DataSourceItemMergeHelper.h"
2
3 #include <DataSource/DataSourceItem.h>
4
5 namespace {
6
7 /**
8 * Finds in a tree an item similar to the item passed in parameter
9 * @param item the item for which to find a similar item
10 * @param root the root item of the tree
11 * @return the similar item if found, nullptr otherwise
12 */
13 DataSourceItem *findSimilarItem(const DataSourceItem &item, const DataSourceItem &root)
14 {
15 // An item is considered similar to the another item if:
16 // - the items are both nodes AND
17 // - the names of the items are identical
18
19 if (item.type() != DataSourceItemType::NODE) {
20 return nullptr;
21 }
22
23 DataSourceItem *result{nullptr};
24 bool found{false};
25 for (auto i = 0, count = root.childCount(); i < count && !found; ++i) {
26 auto child = root.child(i);
27
28 found = child->type() == DataSourceItemType::NODE
29 && QString::compare(child->name(), item.name(), Qt::CaseInsensitive) == 0;
30 if (found) {
31 result = child;
32 }
33 }
34
35 return result;
36 }
37
38 } // namespace
39
40 void DataSourceItemMergeHelper::merge(const DataSourceItem &source, DataSourceItem &dest)
41 {
42 // Checks if the source item can be merged into the destination item (i.e. there is a child item
43 // similar to the source item)
44 if (auto subItem = findSimilarItem(source, dest)) {
45 // If there is an item similar to the source item, applies the merge recursively
46 for (auto i = 0, count = source.childCount(); i < count; ++i) {
47 merge(*source.child(i), *subItem);
48 }
49 }
50 else {
51 // If no item is similar to the source item, the item is copied as the child of the
52 // destination item
53 dest.appendChild(source.clone());
54 }
55 }
@@ -0,0 +1,73
1 #include "DataSourceItemBuilder.h"
2
3 DataSourceItemBuilder &DataSourceItemBuilder::root(const QString &name)
4 {
5 m_Root = std::make_shared<DataSourceItem>(DataSourceItemType::NODE, name);
6 m_Items.push(m_Root.get());
7 return *this;
8 }
9
10 DataSourceItemBuilder &DataSourceItemBuilder::root(QVariantHash data)
11 {
12 m_Root = std::make_shared<DataSourceItem>(DataSourceItemType::NODE, data);
13 m_Items.push(m_Root.get());
14 return *this;
15 }
16
17 DataSourceItemBuilder &DataSourceItemBuilder::node(const QString &name)
18 {
19 return append(DataSourceItemType::NODE, name);
20 }
21
22 DataSourceItemBuilder &DataSourceItemBuilder::node(QVariantHash data)
23 {
24 return append(DataSourceItemType::NODE, std::move(data));
25 }
26
27 DataSourceItemBuilder &DataSourceItemBuilder::product(const QString &name)
28 {
29 return append(DataSourceItemType::PRODUCT, name);
30 }
31
32 DataSourceItemBuilder &DataSourceItemBuilder::product(QVariantHash data)
33 {
34 return append(DataSourceItemType::PRODUCT, std::move(data));
35 }
36
37 DataSourceItemBuilder &DataSourceItemBuilder::component(const QString &name)
38 {
39 return append(DataSourceItemType::COMPONENT, name);
40 }
41
42 DataSourceItemBuilder &DataSourceItemBuilder::component(QVariantHash data)
43 {
44 return append(DataSourceItemType::COMPONENT, std::move(data));
45 }
46
47 DataSourceItemBuilder &DataSourceItemBuilder::end()
48 {
49 m_Items.pop();
50 return *this;
51 }
52
53 std::shared_ptr<DataSourceItem> DataSourceItemBuilder::build()
54 {
55 return m_Root;
56 }
57
58 DataSourceItemBuilder &DataSourceItemBuilder::append(DataSourceItemType type, const QString &name)
59 {
60 append(type, QVariantHash{{DataSourceItem::NAME_DATA_KEY, name}});
61 return *this;
62 }
63
64 DataSourceItemBuilder &DataSourceItemBuilder::append(DataSourceItemType type, QVariantHash data)
65 {
66 auto parentItem = m_Items.top();
67
68 auto insertIndex = parentItem->childCount();
69 parentItem->appendChild(std::make_unique<DataSourceItem>(type, std::move(data)));
70
71 m_Items.push(parentItem->child(insertIndex));
72 return *this;
73 }
@@ -0,0 +1,45
1 #ifndef SCIQLOP_DATASOURCEITEMBUILDER_H
2 #define SCIQLOP_DATASOURCEITEMBUILDER_H
3
4 #include <DataSource/DataSourceItem.h>
5
6 #include <memory>
7 #include <stack>
8
9 /**
10 * @brief The DataSourceItemBuilder class aims to facilitate the creation of a DataSourceItem for unit tests
11 * @sa DataSourceItem
12 */
13 class DataSourceItemBuilder {
14 public:
15 /// Inits root item
16 DataSourceItemBuilder & root(const QString &name);
17 DataSourceItemBuilder & root(QVariantHash data);
18
19 /// Adds node into the current item
20 DataSourceItemBuilder & node(const QString &name);
21 DataSourceItemBuilder & node(QVariantHash data);
22
23 /// Adds product into the current item
24 DataSourceItemBuilder & product(const QString &name);
25 DataSourceItemBuilder & product(QVariantHash data);
26
27 /// Adds component into the current item
28 DataSourceItemBuilder & component(const QString &name);
29 DataSourceItemBuilder & component(QVariantHash data);
30
31 /// Closes the build of the current item
32 DataSourceItemBuilder& end();
33
34 /// Creates the DataSourceItem
35 std::shared_ptr<DataSourceItem> build();
36
37 private:
38 DataSourceItemBuilder& append(DataSourceItemType type, const QString &name);
39 DataSourceItemBuilder& append(DataSourceItemType type, QVariantHash data);
40
41 std::shared_ptr<DataSourceItem> m_Root{nullptr};
42 std::stack<DataSourceItem*> m_Items;
43 };
44
45 #endif // SCIQLOP_DATASOURCEITEMBUILDER_H
@@ -0,0 +1,207
1 #include <DataSource/DataSourceItem.h>
2
3 #include "DataSourceItemBuilder.h"
4
5 #include <QObject>
6 #include <QtTest>
7
8 #include <iostream>
9
10 namespace {
11
12 void printItem(std::ostream &out, const DataSourceItem &item, int level = 0)
13 {
14 for (auto i = 0; i < level; ++i) {
15 out << " ";
16 }
17
18 out << item.name().toStdString() << "\n";
19
20 for (auto i = 0, count = item.childCount(); i < count; ++i) {
21 printItem(out, *item.child(i), level + 1);
22 }
23 }
24
25 std::ostream &operator<<(std::ostream &out, const DataSourceItem &item)
26 {
27 printItem(out, item, 0);
28 return out;
29 }
30
31 } // namespace
32
33 Q_DECLARE_METATYPE(std::shared_ptr<DataSourceItem>)
34
35 class TestDataSourceItem : public QObject {
36 Q_OBJECT
37 private slots:
38 void testMerge_data();
39 void testMerge();
40 };
41
42 void TestDataSourceItem::testMerge_data()
43 {
44 QTest::addColumn<std::shared_ptr<DataSourceItem> >("source");
45 QTest::addColumn<std::shared_ptr<DataSourceItem> >("dest");
46 QTest::addColumn<std::shared_ptr<DataSourceItem> >("expectedResult");
47
48 QTest::newRow("merge (basic case)") << DataSourceItemBuilder{}
49 .root("A2")
50 .node("- B2")
51 .product("-- P2")
52 .end() // P2
53 .end() // B2
54 .end() // A2
55 .build()
56 << DataSourceItemBuilder{}
57 .root("A1")
58 .node("- B1")
59 .product("-- P1")
60 .end() // P1
61 .end() // B1
62 .end() // A1
63 .build()
64 << DataSourceItemBuilder{}
65 .root("A1")
66 .node("- B1")
67 .product("-- P1")
68 .end() // P1
69 .end() // B1
70 .node("- B2")
71 .product("-- P2")
72 .end() // P2
73 .end() // B2
74 .end() // A1
75 .build();
76
77 QTest::newRow("merge (some of the source and destination trees are identical)")
78 << DataSourceItemBuilder{}
79 .root("A2")
80 .node("- B1")
81 .node("-- C1")
82 .product("--- P2")
83 .end() // P2
84 .end() // C1
85 .node("-- C2")
86 .end() // C2
87 .end() // B1
88 .end() // A2
89 .build()
90 << DataSourceItemBuilder{}
91 .root("A1")
92 .node("- B1")
93 .node("-- C1")
94 .product("--- P1")
95 .end() // P1
96 .end() // C1
97 .end() // B1
98 .end() // A1
99 .build()
100 << DataSourceItemBuilder{}
101 .root("A1")
102 .node("- B1")
103 .node("-- C1")
104 .product("--- P1")
105 .end() // P1
106 .product("--- P2")
107 .end() // P2
108 .end() // C1
109 .node("-- C2")
110 .end() // C2
111 .end() // B1
112 .end() // A1
113 .build();
114
115 QTest::newRow("merge (products with the same name and tree are kept)")
116 << DataSourceItemBuilder{}
117 .root("A2")
118 .node("- B1")
119 .node("-- C1")
120 .product({{"name", "--- P1"}, {"from", "source"}})
121 .end() // P1
122 .end() // C1
123 .end() // B1
124 .end() // A2
125 .build()
126 << DataSourceItemBuilder{}
127 .root("A1")
128 .node("- B1")
129 .node("-- C1")
130 .product({{"name", "--- P1"}, {"from", "dest"}})
131 .end() // P1
132 .end() // C1
133 .end() // B1
134 .end() // A1
135 .build()
136 << DataSourceItemBuilder{}
137 .root("A1")
138 .node("- B1")
139 .node("-- C1")
140 .product({{"name", "--- P1"}, {"from", "dest"}})
141 .end() // P1 (dest)
142 .product({{"name", "--- P1"}, {"from", "source"}})
143 .end() // P1 (source)
144 .end() // C1
145 .end() // B1
146 .end() // A1
147 .build();
148
149 QTest::newRow("merge (for same nodes, metadata of dest node are kept)")
150 << DataSourceItemBuilder{}
151 .root("A2")
152 .node("- B1")
153 .node({{"name", "-- C1"}, {"from", "source"}})
154 .product("--- P2")
155 .end() // P1
156 .end() // C1
157 .end() // B1
158 .end() // A2
159 .build()
160 << DataSourceItemBuilder{}
161 .root("A1")
162 .node("- B1")
163 .node({{"name", "-- C1"}, {"from", "dest"}})
164 .product("--- P1")
165 .end() // P1
166 .end() // C1
167 .end() // B1
168 .end() // A1
169 .build()
170 << DataSourceItemBuilder{}
171 .root("A1")
172 .node("- B1")
173 .node({{"name", "-- C1"}, {"from", "dest"}})
174 .product("--- P1")
175 .end() // P1
176 .product("--- P2")
177 .end() // P2
178 .end() // C1 (dest)
179 .end() // B1
180 .end() // A1
181 .build();
182 }
183
184 void TestDataSourceItem::testMerge()
185 {
186 QFETCH(std::shared_ptr<DataSourceItem>, source);
187 QFETCH(std::shared_ptr<DataSourceItem>, dest);
188 QFETCH(std::shared_ptr<DataSourceItem>, expectedResult);
189
190 // Uncomment to print trees
191 // std::cout << "source: \n" << *source << "\n";
192 // std::cout << "dest: \n" << *dest << "\n";
193
194 // Merges source in dest (not taking source root)
195 for (auto i = 0, count = source->childCount(); i < count; ++i) {
196 dest->merge(*source->child(i));
197 }
198
199 // Uncomment to print trees
200 // std::cout << "dest after merge: \n" << *dest << "\n";
201
202 // Checks merge result
203 QVERIFY(*dest == *expectedResult);
204 }
205
206 QTEST_MAIN(TestDataSourceItem)
207 #include "TestDataSourceItem.moc"
@@ -1,164 +1,170
1 1 <?xml version="1.0" encoding="UTF-8"?>
2 2 <ui version="4.0">
3 3 <class>MainWindow</class>
4 4 <widget class="QMainWindow" name="MainWindow">
5 5 <property name="geometry">
6 6 <rect>
7 7 <x>0</x>
8 8 <y>0</y>
9 9 <width>800</width>
10 10 <height>600</height>
11 11 </rect>
12 12 </property>
13 13 <property name="windowTitle">
14 14 <string>SciQlop v0.0.1</string>
15 15 </property>
16 16 <property name="dockNestingEnabled">
17 17 <bool>true</bool>
18 18 </property>
19 19 <widget class="QWidget" name="centralWidget">
20 20 <property name="enabled">
21 21 <bool>true</bool>
22 22 </property>
23 23 <property name="sizePolicy">
24 24 <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
25 25 <horstretch>0</horstretch>
26 26 <verstretch>0</verstretch>
27 27 </sizepolicy>
28 28 </property>
29 29 <property name="maximumSize">
30 30 <size>
31 31 <width>16777215</width>
32 32 <height>16777215</height>
33 33 </size>
34 34 </property>
35 35 <layout class="QHBoxLayout" name="horizontalLayout">
36 36 <property name="spacing">
37 37 <number>0</number>
38 38 </property>
39 39 <property name="leftMargin">
40 40 <number>0</number>
41 41 </property>
42 42 <property name="topMargin">
43 43 <number>0</number>
44 44 </property>
45 45 <property name="rightMargin">
46 46 <number>0</number>
47 47 </property>
48 48 <property name="bottomMargin">
49 49 <number>0</number>
50 50 </property>
51 51 <item>
52 52 <widget class="QSplitter" name="splitter">
53 53 <property name="orientation">
54 54 <enum>Qt::Horizontal</enum>
55 55 </property>
56 56 <widget class="QWidget" name="leftMainInspectorWidget" native="true">
57 57 <layout class="QVBoxLayout" name="verticalLayout">
58 58 <property name="spacing">
59 59 <number>0</number>
60 60 </property>
61 61 <property name="leftMargin">
62 62 <number>0</number>
63 63 </property>
64 64 <property name="topMargin">
65 65 <number>0</number>
66 66 </property>
67 67 <property name="rightMargin">
68 68 <number>0</number>
69 69 </property>
70 70 <property name="bottomMargin">
71 71 <number>0</number>
72 72 </property>
73 73 <item>
74 74 <widget class="DataSourceWidget" name="dataSourceWidget" native="true"/>
75 75 </item>
76 76 <item>
77 77 <widget class="QWidget" name="dateTimeWidget" native="true"/>
78 78 </item>
79 79 <item>
80 80 <widget class="VariableInspectorWidget" name="variableInspectorWidget" native="true"/>
81 81 </item>
82 82 </layout>
83 83 </widget>
84 84 <widget class="SqpSidePane" name="leftInspectorSidePane" native="true"/>
85 85 <widget class="VisualizationWidget" name="view" native="true">
86 86 <property name="sizePolicy">
87 87 <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
88 88 <horstretch>0</horstretch>
89 89 <verstretch>0</verstretch>
90 90 </sizepolicy>
91 91 </property>
92 92 </widget>
93 93 <widget class="SqpSidePane" name="rightInspectorSidePane" native="true"/>
94 94 <widget class="QWidget" name="rightMainInspectorWidget" native="true">
95 95 <layout class="QVBoxLayout" name="verticalLayout_3">
96 96 <property name="spacing">
97 97 <number>0</number>
98 98 </property>
99 99 <property name="leftMargin">
100 100 <number>0</number>
101 101 </property>
102 102 <property name="topMargin">
103 103 <number>0</number>
104 104 </property>
105 105 <property name="rightMargin">
106 106 <number>0</number>
107 107 </property>
108 108 <property name="bottomMargin">
109 109 <number>0</number>
110 110 </property>
111 111 <item>
112 112 <widget class="QWidget" name="commonPropertyInspectorWidget" native="true"/>
113 113 </item>
114 114 <item>
115 <widget class="DataSourceWidget" name="catalogWidget" native="true"/>
115 <widget class="QTreeWidget" name="catalogWidget">
116 <column>
117 <property name="text">
118 <string notr="true">Name</string>
119 </property>
120 </column>
121 </widget>
116 122 </item>
117 123 </layout>
118 124 </widget>
119 125 </widget>
120 126 </item>
121 127 </layout>
122 128 </widget>
123 129 <widget class="QMenuBar" name="menuBar">
124 130 <property name="geometry">
125 131 <rect>
126 132 <x>0</x>
127 133 <y>0</y>
128 134 <width>800</width>
129 135 <height>28</height>
130 136 </rect>
131 137 </property>
132 138 </widget>
133 139 <widget class="QStatusBar" name="statusBar"/>
134 140 </widget>
135 141 <layoutdefault spacing="6" margin="11"/>
136 142 <customwidgets>
137 143 <customwidget>
138 144 <class>VisualizationWidget</class>
139 145 <extends>QWidget</extends>
140 146 <header location="global">Visualization/VisualizationWidget.h</header>
141 147 <container>1</container>
142 148 </customwidget>
143 149 <customwidget>
144 150 <class>SqpSidePane</class>
145 151 <extends>QWidget</extends>
146 152 <header location="global">SidePane/SqpSidePane.h</header>
147 153 <container>1</container>
148 154 </customwidget>
149 155 <customwidget>
150 156 <class>DataSourceWidget</class>
151 157 <extends>QWidget</extends>
152 158 <header location="global">DataSource/DataSourceWidget.h</header>
153 159 <container>1</container>
154 160 </customwidget>
155 161 <customwidget>
156 162 <class>VariableInspectorWidget</class>
157 163 <extends>QWidget</extends>
158 164 <header location="global">Variable/VariableInspectorWidget.h</header>
159 165 <container>1</container>
160 166 </customwidget>
161 167 </customwidgets>
162 168 <resources/>
163 169 <connections/>
164 170 </ui>
@@ -1,110 +1,154
1 1 #ifndef SCIQLOP_DATASOURCEITEM_H
2 2 #define SCIQLOP_DATASOURCEITEM_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Common/spimpl.h>
7 7
8 8 #include <QVariant>
9 9 #include <QVector>
10 10
11 11 class DataSourceItemAction;
12 12
13 13 /**
14 14 * Possible types of an item
15 15 */
16 16 enum class DataSourceItemType { NODE, PRODUCT, COMPONENT };
17 17
18 18 /**
19 19 * @brief The DataSourceItem class aims to represent a structure element of a data source.
20 20 * A data source has a tree structure that is made up of a main DataSourceItem object (root)
21 21 * containing other DataSourceItem objects (children).
22 22 * For each DataSourceItem can be associated a set of data representing it.
23 23 */
24 24 class SCIQLOP_CORE_EXPORT DataSourceItem {
25 25 public:
26 26 /// Key associated with the name of the item
27 27 static const QString NAME_DATA_KEY;
28 /// Key associated with the plugin of the item
29 static const QString PLUGIN_DATA_KEY;
28 30
29 31 explicit DataSourceItem(DataSourceItemType type, const QString &name);
30 32 explicit DataSourceItem(DataSourceItemType type, QVariantHash data = {});
31 33
34 std::unique_ptr<DataSourceItem> clone() const;
35
32 36 /// @return the actions of the item as a vector
33 37 QVector<DataSourceItemAction *> actions() const noexcept;
34 38
35 39 /**
36 40 * Adds an action to the item. The item takes ownership of the action, and the action is
37 41 * automatically associated to the item
38 42 * @param action the action to add
39 43 */
40 44 void addAction(std::unique_ptr<DataSourceItemAction> action) noexcept;
41 45
42 46 /**
43 47 * Adds a child to the item. The item takes ownership of the child.
44 48 * @param child the child to add
45 49 */
46 50 void appendChild(std::unique_ptr<DataSourceItem> child) noexcept;
47 51
48 52 /**
49 53 * Returns the item's child associated to an index
50 54 * @param childIndex the index to search
51 55 * @return a pointer to the child if index is valid, nullptr otherwise
52 56 */
53 57 DataSourceItem *child(int childIndex) const noexcept;
54 58
55 59 int childCount() const noexcept;
56 60
57 61 /**
58 62 * Get the data associated to a key
59 63 * @param key the key to search
60 64 * @return the data found if key is valid, default QVariant otherwise
61 65 */
62 66 QVariant data(const QString &key) const noexcept;
63 67
64 68 /// Gets all data
65 69 QVariantHash data() const noexcept;
66 70
71 /**
72 * Merge in the item the source item passed as parameter.
73 *
74 * The merge is done by adding as child of the item the complete tree represented by the source
75 * item. If a part of the tree already exists in the item (based on the name of the nodes), it
76 * is merged by completing the existing tree by items "leaves" (products, components or nodes
77 * with no child).
78 *
79 * For example, with item representing the tree:
80 * R (root node)
81 * - N1 (node)
82 * -- N11 (node)
83 * --- P1 (product)
84 * --- P2 (product)
85 * - N2 (node)
86 *
87 * and the source item representing the tree:
88 * N1 (root node)
89 * - N11 (node)
90 * -- P3 (product)
91 * - N12 (node)
92 *
93 * The leaves of the source item to merge into the item are N1/N11/P3 and N1/N12 => we therefore
94 * have the following merge result:
95 * R
96 * - N1
97 * -- N11
98 * --- P1
99 * --- P2
100 * --- P3 (added leaf)
101 * -- N12 (added leaf)
102 *
103 * @param item the source item
104 * @remarks No control is performed on products or components that are merged into the same tree
105 * part (two products or components may have the same name)
106 * @remarks the merge is made by copy (source item is not changed and still exists after the
107 * operation)
108 */
109 void merge(const DataSourceItem &item);
110
67 111 bool isRoot() const noexcept;
68 112
69 113 QString name() const noexcept;
70 114
71 115 /**
72 116 * Get the item's parent
73 117 * @return a pointer to the parent if it exists, nullptr if the item is a root
74 118 */
75 119 DataSourceItem *parentItem() const noexcept;
76 120
77 121 /**
78 122 * Gets the item's root
79 123 * @return the top parent, the item itself if it's the root item
80 124 */
81 125 const DataSourceItem &rootItem() const noexcept;
82 126
83 127 /**
84 128 * Sets or appends a value to a key
85 129 * @param key the key
86 130 * @param value the value
87 131 * @param append if true, the value is added to the values already existing for the key,
88 132 * otherwise it replaces the existing values
89 133 */
90 134 void setData(const QString &key, const QVariant &value, bool append = false) noexcept;
91 135
92 136 DataSourceItemType type() const noexcept;
93 137
94 138 /**
95 139 * @brief Searches the first child matching the specified data.
96 140 * @param data The data to search.
97 141 * @param recursive So the search recursively.
98 142 * @return the item matching the data or nullptr if it was not found.
99 143 */
100 144 DataSourceItem *findItem(const QVariantHash &data, bool recursive);
101 145
102 146 bool operator==(const DataSourceItem &other);
103 147 bool operator!=(const DataSourceItem &other);
104 148
105 149 private:
106 150 class DataSourceItemPrivate;
107 151 spimpl::unique_impl_ptr<DataSourceItemPrivate> impl;
108 152 };
109 153
110 154 #endif // SCIQLOP_DATASOURCEITEMMODEL_H
@@ -1,52 +1,54
1 1 #ifndef SCIQLOP_DATASOURCEITEMACTION_H
2 2 #define SCIQLOP_DATASOURCEITEMACTION_H
3 3
4 4 #include "CoreGlobal.h"
5 5
6 6 #include <Common/spimpl.h>
7 7
8 8 #include <QLoggingCategory>
9 9 #include <QObject>
10 10
11 11 #include <functional>
12 12
13 13 Q_DECLARE_LOGGING_CATEGORY(LOG_DataSourceItemAction)
14 14
15 15 class DataSourceItem;
16 16
17 17 /**
18 18 * @brief The DataSourceItemAction class represents an action on a data source item.
19 19 *
20 20 * An action is a function that will be executed when the slot execute() is called.
21 21 */
22 22 class SCIQLOP_CORE_EXPORT DataSourceItemAction : public QObject {
23 23
24 24 Q_OBJECT
25 25
26 26 public:
27 27 /// Signature of the function associated to the action
28 28 using ExecuteFunction = std::function<void(DataSourceItem &dataSourceItem)>;
29 29
30 30 /**
31 31 * Ctor
32 32 * @param name the name of the action
33 33 * @param fun the function that will be called when the action is executed
34 34 * @sa execute()
35 35 */
36 36 explicit DataSourceItemAction(const QString &name, ExecuteFunction fun);
37 37
38 std::unique_ptr<DataSourceItemAction> clone() const;
39
38 40 QString name() const noexcept;
39 41
40 42 /// Sets the data source item concerned by the action
41 43 void setDataSourceItem(DataSourceItem *dataSourceItem) noexcept;
42 44
43 45 public slots:
44 46 /// Executes the action
45 47 void execute();
46 48
47 49 private:
48 50 class DataSourceItemActionPrivate;
49 51 spimpl::unique_impl_ptr<DataSourceItemActionPrivate> impl;
50 52 };
51 53
52 54 #endif // SCIQLOP_DATASOURCEITEMACTION_H
@@ -1,65 +1,66
1 1
2 2 core_moc_headers = [
3 3 'include/Data/IDataProvider.h',
4 4 'include/DataSource/DataSourceController.h',
5 5 'include/DataSource/DataSourceItemAction.h',
6 6 'include/Network/NetworkController.h',
7 7 'include/Time/TimeController.h',
8 8 'include/Variable/Variable.h',
9 9 'include/Variable/VariableCacheController.h',
10 10 'include/Variable/VariableController.h',
11 11 'include/Variable/VariableAcquisitionWorker.h',
12 12 'include/Variable/VariableSynchronizationGroup.h',
13 13 'include/Variable/VariableModel.h',
14 14 'include/Visualization/VisualizationController.h'
15 15 ]
16 16
17 17
18 18 core_moc_files = qt5.preprocess(moc_headers : core_moc_headers)
19 19
20 20 core_sources = [
21 21 'src/Common/DateUtils.cpp',
22 22 'src/Common/StringUtils.cpp',
23 23 'src/Common/MimeTypesDef.cpp',
24 24 'src/Data/ScalarSeries.cpp',
25 25 'src/Data/SpectrogramSeries.cpp',
26 26 'src/Data/DataSeriesIterator.cpp',
27 27 'src/Data/ArrayDataIterator.cpp',
28 28 'src/Data/VectorSeries.cpp',
29 29 'src/Data/OptionalAxis.cpp',
30 30 'src/Data/DataSeriesUtils.cpp',
31 31 'src/DataSource/DataSourceController.cpp',
32 32 'src/DataSource/DataSourceItem.cpp',
33 33 'src/DataSource/DataSourceItemAction.cpp',
34 'src/DataSource/DataSourceItemMergeHelper.cpp',
34 35 'src/Network/NetworkController.cpp',
35 36 'src/Plugin/PluginManager.cpp',
36 37 'src/Settings/SqpSettingsDefs.cpp',
37 38 'src/Time/TimeController.cpp',
38 39 'src/Variable/Variable.cpp',
39 40 'src/Variable/VariableCacheController.cpp',
40 41 'src/Variable/VariableController.cpp',
41 42 'src/Variable/VariableAcquisitionWorker.cpp',
42 43 'src/Variable/VariableSynchronizationGroup.cpp',
43 44 'src/Variable/VariableModel.cpp',
44 45 'src/Visualization/VisualizationController.cpp'
45 46 ]
46 47
47 48 core_inc = include_directories(['include', '../plugin/include'])
48 49
49 50 sciqlop_core_lib = library('sciqlopcore',
50 51 core_sources,
51 52 core_moc_files,
52 53 cpp_args : '-DCORE_LIB',
53 54 include_directories : core_inc,
54 55 dependencies : [qt5core, qt5network],
55 56 install : true
56 57 )
57 58
58 59
59 60 sciqlop_core = declare_dependency(link_with : sciqlop_core_lib,
60 61 include_directories : core_inc,
61 62 dependencies : [qt5core, qt5network])
62 63
63 64
64 65 subdir('tests')
65 66
@@ -1,192 +1,169
1 1 #include "DataSource/DataSourceController.h"
2 2 #include "DataSource/DataSourceItem.h"
3 3
4 4 #include <Data/IDataProvider.h>
5 5
6 6 #include <QMutex>
7 7 #include <QThread>
8 8
9 9 #include <QDataStream>
10 10 #include <QDir>
11 11 #include <QStandardPaths>
12 12
13 13 Q_LOGGING_CATEGORY(LOG_DataSourceController, "DataSourceController")
14 14
15 namespace {
16
17 /**
18 * Builds the metadata of the variable that will be generated from the loading of an item
19 * @param dataSourceItem the data source item from which to generate the metadata
20 * @return the metadata of the variable
21 */
22 QVariantHash variableMetadata(const DataSourceItem &dataSourceItem)
23 {
24 // Variable metadata contains...
25
26 // ... all metadata of the item
27 auto result = dataSourceItem.data();
28
29 // ... and the name of the plugin, recovered from root item
30 result.insert(QStringLiteral("plugin"), dataSourceItem.rootItem().name());
31
32 return result;
33 }
34
35 } // namespace
36
37 15 class DataSourceController::DataSourceControllerPrivate {
38 16 public:
39 17 QMutex m_WorkingMutex;
40 18 /// Data sources registered
41 19 QHash<QUuid, QString> m_DataSources;
42 20 /// Data sources structures
43 21 std::map<QUuid, std::unique_ptr<DataSourceItem> > m_DataSourceItems;
44 22 /// Data providers registered
45 23 /// @remarks Data providers are stored as shared_ptr as they can be sent to a variable and
46 24 /// continue to live without necessarily the data source controller
47 25 std::map<QUuid, std::shared_ptr<IDataProvider> > m_DataProviders;
48 26
49 27 // Search for the first datasource item matching the specified data
50 28 DataSourceItem *findDataSourceItem(const QVariantHash &data)
51 29 {
52 30 DataSourceItem *sourceItem = nullptr;
53 31 for (const auto &item : m_DataSourceItems) {
54 32 sourceItem = item.second->findItem(data, true);
55 33 if (sourceItem) {
56 34 break;
57 35 }
58 36 }
59 37
60 38 return sourceItem;
61 39 }
62 40 };
63 41
64 42 DataSourceController::DataSourceController(QObject *parent)
65 43 : impl{spimpl::make_unique_impl<DataSourceControllerPrivate>()}
66 44 {
67 45 qCDebug(LOG_DataSourceController()) << tr("DataSourceController construction")
68 46 << QThread::currentThread();
69 47 }
70 48
71 49 DataSourceController::~DataSourceController()
72 50 {
73 51 qCDebug(LOG_DataSourceController()) << tr("DataSourceController destruction")
74 52 << QThread::currentThread();
75 53 this->waitForFinish();
76 54 }
77 55
78 56 QUuid DataSourceController::registerDataSource(const QString &dataSourceName) noexcept
79 57 {
80 58 auto dataSourceUid = QUuid::createUuid();
81 59 impl->m_DataSources.insert(dataSourceUid, dataSourceName);
82 60
83 61 return dataSourceUid;
84 62 }
85 63
86 64 void DataSourceController::setDataSourceItem(
87 65 const QUuid &dataSourceUid, std::unique_ptr<DataSourceItem> dataSourceItem) noexcept
88 66 {
89 67 if (!dataSourceItem) {
90 68 qCWarning(LOG_DataSourceController())
91 69 << tr("Data source item can't be registered (null item)");
92 70 return;
93 71 }
94 72
95 73 if (impl->m_DataSources.contains(dataSourceUid)) {
96 74 // The data provider is implicitly converted to a shared_ptr
97 75 impl->m_DataSourceItems.insert(std::make_pair(dataSourceUid, std::move(dataSourceItem)));
98 76
99 77 // Retrieves the data source item to emit the signal with it
100 78 auto it = impl->m_DataSourceItems.find(dataSourceUid);
101 79 if (it != impl->m_DataSourceItems.end()) {
102 80 emit dataSourceItemSet(it->second.get());
103 81 }
104 82 }
105 83 else {
106 84 qCWarning(LOG_DataSourceController()) << tr("Can't set data source item for uid %1 : no "
107 85 "data source has been registered with the uid")
108 86 .arg(dataSourceUid.toString());
109 87 }
110 88 }
111 89
112 90 void DataSourceController::setDataProvider(const QUuid &dataSourceUid,
113 91 std::unique_ptr<IDataProvider> dataProvider) noexcept
114 92 {
115 93 if (impl->m_DataSources.contains(dataSourceUid)) {
116 94 impl->m_DataProviders.insert(std::make_pair(dataSourceUid, std::move(dataProvider)));
117 95 }
118 96 else {
119 97 qCWarning(LOG_DataSourceController()) << tr("Can't set data provider for uid %1 : no data "
120 98 "source has been registered with the uid")
121 99 .arg(dataSourceUid.toString());
122 100 }
123 101 }
124 102
125 103 void DataSourceController::loadProductItem(const QUuid &dataSourceUid,
126 104 const DataSourceItem &productItem) noexcept
127 105 {
128 106 if (productItem.type() == DataSourceItemType::PRODUCT
129 107 || productItem.type() == DataSourceItemType::COMPONENT) {
130 108 /// Retrieves the data provider of the data source (if any)
131 109 auto it = impl->m_DataProviders.find(dataSourceUid);
132 110 auto dataProvider = (it != impl->m_DataProviders.end()) ? it->second : nullptr;
133 111
134 emit variableCreationRequested(productItem.name(), variableMetadata(productItem),
135 dataProvider);
112 emit variableCreationRequested(productItem.name(), productItem.data(), dataProvider);
136 113 }
137 114 else {
138 115 qCWarning(LOG_DataSourceController()) << tr("Can't load an item that is not a product");
139 116 }
140 117 }
141 118
142 119 QByteArray DataSourceController::mimeDataForProductsData(const QVariantList &productsData)
143 120 {
144 121 QByteArray encodedData;
145 122 QDataStream stream{&encodedData, QIODevice::WriteOnly};
146 123
147 124 stream << productsData;
148 125
149 126 return encodedData;
150 127 }
151 128
152 129 QVariantList DataSourceController::productsDataForMimeData(const QByteArray &mimeData)
153 130 {
154 131 QDataStream stream{mimeData};
155 132
156 133 QVariantList productList;
157 134 stream >> productList;
158 135
159 136 return productList;
160 137 }
161 138
162 139 void DataSourceController::initialize()
163 140 {
164 141 qCDebug(LOG_DataSourceController()) << tr("DataSourceController init")
165 142 << QThread::currentThread();
166 143 impl->m_WorkingMutex.lock();
167 144 qCDebug(LOG_DataSourceController()) << tr("DataSourceController init END");
168 145 }
169 146
170 147 void DataSourceController::finalize()
171 148 {
172 149 impl->m_WorkingMutex.unlock();
173 150 }
174 151
175 152 void DataSourceController::requestVariable(const QVariantHash &productData)
176 153 {
177 154 auto sourceItem = impl->findDataSourceItem(productData);
178 155
179 156 if (sourceItem) {
180 157 auto sourceName = sourceItem->rootItem().name();
181 158 auto sourceId = impl->m_DataSources.key(sourceName);
182 159 loadProductItem(sourceId, *sourceItem);
183 160 }
184 161 else {
185 162 qCWarning(LOG_DataSourceController()) << tr("requestVariable, product data not found");
186 163 }
187 164 }
188 165
189 166 void DataSourceController::waitForFinish()
190 167 {
191 168 QMutexLocker locker{&impl->m_WorkingMutex};
192 169 }
@@ -1,162 +1,186
1 1 #include <DataSource/DataSourceItem.h>
2 2 #include <DataSource/DataSourceItemAction.h>
3 #include <DataSource/DataSourceItemMergeHelper.h>
3 4
4 5 #include <QVector>
5 6
6 7 const QString DataSourceItem::NAME_DATA_KEY = QStringLiteral("name");
8 const QString DataSourceItem::PLUGIN_DATA_KEY = QStringLiteral("plugin");
7 9
8 10 struct DataSourceItem::DataSourceItemPrivate {
9 11 explicit DataSourceItemPrivate(DataSourceItemType type, QVariantHash data)
10 12 : m_Parent{nullptr}, m_Children{}, m_Type{type}, m_Data{std::move(data)}, m_Actions{}
11 13 {
12 14 }
13 15
14 16 DataSourceItem *m_Parent;
15 17 std::vector<std::unique_ptr<DataSourceItem> > m_Children;
16 18 DataSourceItemType m_Type;
17 19 QVariantHash m_Data;
18 20 std::vector<std::unique_ptr<DataSourceItemAction> > m_Actions;
19 21 };
20 22
21 23 DataSourceItem::DataSourceItem(DataSourceItemType type, const QString &name)
22 24 : DataSourceItem{type, QVariantHash{{NAME_DATA_KEY, name}}}
23 25 {
24 26 }
25 27
26 28 DataSourceItem::DataSourceItem(DataSourceItemType type, QVariantHash data)
27 29 : impl{spimpl::make_unique_impl<DataSourceItemPrivate>(type, std::move(data))}
28 30 {
29 31 }
30 32
33 std::unique_ptr<DataSourceItem> DataSourceItem::clone() const
34 {
35 auto result = std::make_unique<DataSourceItem>(impl->m_Type, impl->m_Data);
36
37 // Clones children
38 for (const auto &child : impl->m_Children) {
39 result->appendChild(std::move(child->clone()));
40 }
41
42 // Clones actions
43 for (const auto &action : impl->m_Actions) {
44 result->addAction(std::move(action->clone()));
45 }
46
47 return result;
48 }
49
31 50 QVector<DataSourceItemAction *> DataSourceItem::actions() const noexcept
32 51 {
33 52 auto result = QVector<DataSourceItemAction *>{};
34 53
35 54 std::transform(std::cbegin(impl->m_Actions), std::cend(impl->m_Actions),
36 55 std::back_inserter(result), [](const auto &action) { return action.get(); });
37 56
38 57 return result;
39 58 }
40 59
41 60 void DataSourceItem::addAction(std::unique_ptr<DataSourceItemAction> action) noexcept
42 61 {
43 62 action->setDataSourceItem(this);
44 63 impl->m_Actions.push_back(std::move(action));
45 64 }
46 65
47 66 void DataSourceItem::appendChild(std::unique_ptr<DataSourceItem> child) noexcept
48 67 {
49 68 child->impl->m_Parent = this;
50 69 impl->m_Children.push_back(std::move(child));
51 70 }
52 71
53 72 DataSourceItem *DataSourceItem::child(int childIndex) const noexcept
54 73 {
55 74 if (childIndex < 0 || childIndex >= childCount()) {
56 75 return nullptr;
57 76 }
58 77 else {
59 78 return impl->m_Children.at(childIndex).get();
60 79 }
61 80 }
62 81
63 82 int DataSourceItem::childCount() const noexcept
64 83 {
65 84 return impl->m_Children.size();
66 85 }
67 86
68 87 QVariant DataSourceItem::data(const QString &key) const noexcept
69 88 {
70 89 return impl->m_Data.value(key);
71 90 }
72 91
73 92 QVariantHash DataSourceItem::data() const noexcept
74 93 {
75 94 return impl->m_Data;
76 95 }
77 96
97 void DataSourceItem::merge(const DataSourceItem &item)
98 {
99 DataSourceItemMergeHelper::merge(item, *this);
100 }
101
78 102 bool DataSourceItem::isRoot() const noexcept
79 103 {
80 104 return impl->m_Parent == nullptr;
81 105 }
82 106
83 107 QString DataSourceItem::name() const noexcept
84 108 {
85 109 return data(NAME_DATA_KEY).toString();
86 110 }
87 111
88 112 DataSourceItem *DataSourceItem::parentItem() const noexcept
89 113 {
90 114 return impl->m_Parent;
91 115 }
92 116
93 117 const DataSourceItem &DataSourceItem::rootItem() const noexcept
94 118 {
95 119 return isRoot() ? *this : parentItem()->rootItem();
96 120 }
97 121
98 122 void DataSourceItem::setData(const QString &key, const QVariant &value, bool append) noexcept
99 123 {
100 124 auto it = impl->m_Data.constFind(key);
101 125 if (append && it != impl->m_Data.constEnd()) {
102 126 // Case of an existing value to which we want to add to the new value
103 127 if (it->canConvert<QVariantList>()) {
104 128 auto variantList = it->value<QVariantList>();
105 129 variantList.append(value);
106 130
107 131 impl->m_Data.insert(key, variantList);
108 132 }
109 133 else {
110 134 impl->m_Data.insert(key, QVariantList{*it, value});
111 135 }
112 136 }
113 137 else {
114 138 // Other cases :
115 139 // - new value in map OR
116 140 // - replacement of an existing value (not appending)
117 141 impl->m_Data.insert(key, value);
118 142 }
119 143 }
120 144
121 145 DataSourceItemType DataSourceItem::type() const noexcept
122 146 {
123 147 return impl->m_Type;
124 148 }
125 149
126 150 DataSourceItem *DataSourceItem::findItem(const QVariantHash &data, bool recursive)
127 151 {
128 152 for (const auto &child : impl->m_Children) {
129 153 if (child->impl->m_Data == data) {
130 154 return child.get();
131 155 }
132 156
133 157 if (recursive) {
134 158 if (auto foundItem = child->findItem(data, true)) {
135 159 return foundItem;
136 160 }
137 161 }
138 162 }
139 163
140 164 return nullptr;
141 165 }
142 166
143 167 bool DataSourceItem::operator==(const DataSourceItem &other)
144 168 {
145 169 // Compares items' attributes
146 170 if (std::tie(impl->m_Type, impl->m_Data) == std::tie(other.impl->m_Type, other.impl->m_Data)) {
147 171 // Compares contents of items' children
148 172 return std::equal(std::cbegin(impl->m_Children), std::cend(impl->m_Children),
149 std::cbegin(other.impl->m_Children),
173 std::cbegin(other.impl->m_Children), std::cend(other.impl->m_Children),
150 174 [](const auto &itemChild, const auto &otherChild) {
151 175 return *itemChild == *otherChild;
152 176 });
153 177 }
154 178 else {
155 179 return false;
156 180 }
157 181 }
158 182
159 183 bool DataSourceItem::operator!=(const DataSourceItem &other)
160 184 {
161 185 return !(*this == other);
162 186 }
@@ -1,44 +1,49
1 1 #include <DataSource/DataSourceItemAction.h>
2 2
3 3 #include <functional>
4 4
5 5 Q_LOGGING_CATEGORY(LOG_DataSourceItemAction, "DataSourceItemAction")
6 6
7 7 struct DataSourceItemAction::DataSourceItemActionPrivate {
8 8 explicit DataSourceItemActionPrivate(const QString &name,
9 9 DataSourceItemAction::ExecuteFunction fun)
10 10 : m_Name{name}, m_Fun{std::move(fun)}, m_DataSourceItem{nullptr}
11 11 {
12 12 }
13 13
14 14 QString m_Name;
15 15 DataSourceItemAction::ExecuteFunction m_Fun;
16 16 /// Item associated to the action (can be null, in which case the action will not be executed)
17 17 DataSourceItem *m_DataSourceItem;
18 18 };
19 19
20 20 DataSourceItemAction::DataSourceItemAction(const QString &name, ExecuteFunction fun)
21 21 : impl{spimpl::make_unique_impl<DataSourceItemActionPrivate>(name, std::move(fun))}
22 22 {
23 23 }
24 24
25 std::unique_ptr<DataSourceItemAction> DataSourceItemAction::clone() const
26 {
27 return std::make_unique<DataSourceItemAction>(impl->m_Name, impl->m_Fun);
28 }
29
25 30 QString DataSourceItemAction::name() const noexcept
26 31 {
27 32 return impl->m_Name;
28 33 }
29 34
30 35 void DataSourceItemAction::setDataSourceItem(DataSourceItem *dataSourceItem) noexcept
31 36 {
32 37 impl->m_DataSourceItem = dataSourceItem;
33 38 }
34 39
35 40 void DataSourceItemAction::execute()
36 41 {
37 42 if (impl->m_DataSourceItem) {
38 43 impl->m_Fun(*impl->m_DataSourceItem);
39 44 }
40 45 else {
41 46 qCDebug(LOG_DataSourceItemAction())
42 47 << tr("Can't execute action : no item has been associated");
43 48 }
44 49 }
@@ -1,33 +1,36
1 1
2 2
3 3 tests = [
4 4 [['Common/TestStringUtils.cpp'],'test_string_utils','StringUtils test'],
5 5 [['Data/TestScalarSeries.cpp'],'test_scalar','ScalarSeries test'],
6 6 [['Data/TestSpectrogramSeries.cpp'],'test_spectrogram','SpectrogramSeries test'],
7 7 [['Data/TestVectorSeries.cpp'],'test_vector','VectorSeries test'],
8 8 [['Data/TestOneDimArrayData.cpp'],'test_1d','One Dim Array test'],
9 9 [['Data/TestOptionalAxis.cpp'],'test_optional_axis','OptionalAxis test'],
10 10 [['Data/TestTwoDimArrayData.cpp'],'test_2d','Two Dim Array test'],
11 11 [['Data/TestDataSeriesUtils.cpp'],'test_dataseries_util','Data series utils test'],
12 12 [['DataSource/TestDataSourceController.cpp'],'test_data_source','DataSourceController test'],
13 [['DataSource/TestDataSourceItem.cpp'],'test_data_source_item','DataSourceItem test'],
13 14 [['Variable/TestVariableCacheController.cpp'],'test_variable_cache','VariableCacheController test'],
14 15 [['Variable/TestVariable.cpp'],'test_variable','Variable test'],
15 16 [['Variable/TestVariableSync.cpp'],'test_variable_sync','Variable synchronization test']
16 17 ]
17 18
18 19 amdatest_sources = [
19 20 'Data/DataSeriesBuilders.h',
20 21 'Data/DataSeriesBuilders.cpp',
21 22 'Data/DataSeriesTestsUtils.h',
22 'Data/DataSeriesTestsUtils.cpp'
23 'Data/DataSeriesTestsUtils.cpp',
24 'DataSource/DataSourceItemBuilder.h',
25 'DataSource/DataSourceItemBuilder.cpp'
23 26 ]
24 27
25 28 foreach unit_test : tests
26 29 test_moc_files = qt5.preprocess(moc_sources : unit_test[0])
27 30 test_exe = executable(unit_test[1],unit_test[0] , test_moc_files,
28 31 dependencies : [sciqlop_core, qt5test],
29 32 cpp_args : ['-DCORE_TESTS_RESOURCES_DIR="'+meson.current_source_dir()+'/../tests-resources"'],
30 33 sources : [amdatest_sources])
31 34 test(unit_test[2], test_exe, args: ['-teamcity', '-o', '@0@.teamcity.txt'.format(unit_test[1])])
32 35 endforeach
33 36
@@ -1,42 +1,47
1 1 #ifndef SCIQLOP_DATASOURCEWIDGET_H
2 2 #define SCIQLOP_DATASOURCEWIDGET_H
3 3
4 4 #include <QWidget>
5 5
6 #include <memory>
7
6 8 namespace Ui {
7 9 class DataSourceWidget;
8 10 } // Ui
9 11
10 12 class DataSourceItem;
11 13
12 14 /**
13 15 * @brief The DataSourceWidget handles the graphical representation (as a tree) of the data sources
14 16 * attached to SciQlop.
15 17 */
16 18 class DataSourceWidget : public QWidget {
17 19 Q_OBJECT
18 20
19 21 public:
20 22 explicit DataSourceWidget(QWidget *parent = 0);
21 23 virtual ~DataSourceWidget() noexcept;
22 24
23 25 public slots:
24 26 /**
25 27 * Adds a data source. An item associated to the data source is created and then added to the
26 28 * representation tree
27 29 * @param dataSource the data source to add. The pointer has to be not null
28 30 */
29 31 void addDataSource(DataSourceItem *dataSource) noexcept;
30 32
31 33 private:
34 void updateTreeWidget() noexcept;
35
32 36 Ui::DataSourceWidget *ui;
37 std::unique_ptr<DataSourceItem> m_Root;
33 38
34 39 private slots:
35 40 /// Slot called when the filtering text has changed
36 41 void filterChanged(const QString &text) noexcept;
37 42
38 43 /// Slot called when right clicking on an item in the tree (displays a menu)
39 44 void onTreeMenuRequested(const QPoint &pos) noexcept;
40 45 };
41 46
42 47 #endif // SCIQLOP_DATASOURCEWIDGET_H
@@ -1,185 +1,225
1 1 #include <DataSource/DataSourceItem.h>
2 2 #include <DataSource/DataSourceItemAction.h>
3 3 #include <DataSource/DataSourceTreeWidgetItem.h>
4 4
5 5 #include <QAction>
6 6
7 7 Q_LOGGING_CATEGORY(LOG_DataSourceTreeWidgetItem, "DataSourceTreeWidgetItem")
8 8
9 9 namespace {
10 10
11 11 // Column indexes
12 12 const auto NAME_COLUMN = 0;
13 13
14 /**
15 * Generates the full name of an item.
16 *
17 * The full name of an item is its name possibly suffixed by the name of its plugin, in case there
18 * are items of the same name in its relatives
19 * @param item the item for which to generate the complete name
20 * @return the complete name of the item
21 */
22 QString completeName(const DataSourceItem &item)
23 {
24 auto name = item.name();
25
26 if (item.type() == DataSourceItemType::NODE) {
27 return name;
28 }
29
30 auto parentItem = item.parentItem();
31 if (!parentItem) {
32 return name;
33 }
34
35 // Finds in item's relatives items that have the same name
36 bool foundSameName = false;
37 for (auto i = 0, count = parentItem->childCount(); i < count && !foundSameName; ++i) {
38 auto child = parentItem->child(i);
39 foundSameName = child != &item
40 && QString::compare(child->name(), item.name(), Qt::CaseInsensitive) == 0;
41 }
42
43 // If the name of the item is not unique, it is completed by the plugin suffix
44 return foundSameName
45 ? QString{"%1 (%2)"}.arg(name, item.data(DataSourceItem::PLUGIN_DATA_KEY).toString())
46 : name;
47 }
48
14 49 QIcon itemIcon(const DataSourceItem *dataSource)
15 50 {
16 51 if (dataSource) {
17 52 auto dataSourceType = dataSource->type();
18 53 switch (dataSourceType) {
19 54 case DataSourceItemType::NODE: {
20 55 return dataSource->isRoot() ? QIcon{":/icones/dataSourceRoot.png"}
21 56 : QIcon{":/icones/dataSourceNode.png"};
22 57 }
23 58 case DataSourceItemType::PRODUCT:
24 59 return QIcon{":/icones/dataSourceProduct.png"};
25 60 case DataSourceItemType::COMPONENT:
26 61 return QIcon{":/icones/dataSourceComponent.png"};
27 62 default:
28 63 // No action
29 64 break;
30 65 }
31 66
32 67 qCWarning(LOG_DataSourceTreeWidgetItem())
33 68 << QObject::tr("Can't set data source icon : unknown data source type");
34 69 }
35 70 else {
36 71 qCCritical(LOG_DataSourceTreeWidgetItem())
37 72 << QObject::tr("Can't set data source icon : the data source is null");
38 73 }
39 74
40 75 // Default cases
41 76 return QIcon{};
42 77 }
43 78
44 79 /// @return the tooltip text for a variant. The text depends on whether the data is a simple variant
45 80 /// or a list of variants
46 81 QString tooltipValue(const QVariant &variant) noexcept
47 82 {
48 83 // If the variant is a list of variants, the text of the tooltip is of the form: {val1, val2,
49 84 // ...}
50 85 if (variant.canConvert<QVariantList>()) {
51 86 auto valueString = QStringLiteral("{");
52 87
53 88 auto variantList = variant.value<QVariantList>();
54 89 for (auto it = variantList.cbegin(), end = variantList.cend(); it != end; ++it) {
55 90 valueString.append(it->toString());
56 91
57 92 if (std::distance(it, end) != 1) {
58 93 valueString.append(", ");
59 94 }
60 95 }
61 96
62 97 valueString.append(QStringLiteral("}"));
63 98
64 99 return valueString;
65 100 }
66 101 else {
67 102 return variant.toString();
68 103 }
69 104 }
70 105
71 106 QString itemTooltip(const DataSourceItem *dataSource) noexcept
72 107 {
73 108 // The tooltip displays all item's data
74 109 if (dataSource) {
75 110 auto result = QString{};
76 111
77 112 const auto &data = dataSource->data();
78 113 for (auto it = data.cbegin(), end = data.cend(); it != end; ++it) {
79 114 result.append(QString{"<b>%1:</b> %2<br/>"}.arg(it.key(), tooltipValue(it.value())));
80 115 }
81 116
82 117 return result;
83 118 }
84 119 else {
85 120 qCCritical(LOG_DataSourceTreeWidgetItem())
86 121 << QObject::tr("Can't set data source tooltip : the data source is null");
87 122
88 123 return QString{};
89 124 }
90 125 }
91 126
92 127 } // namespace
93 128
94 129 struct DataSourceTreeWidgetItem::DataSourceTreeWidgetItemPrivate {
95 explicit DataSourceTreeWidgetItemPrivate(const DataSourceItem *data) : m_Data{data} {}
130 explicit DataSourceTreeWidgetItemPrivate(const DataSourceItem *data)
131 : m_Data{data}, m_Name{completeName(*m_Data)}
132 {
133 }
96 134
97 135 /// Model used to retrieve data source information
98 136 const DataSourceItem *m_Data;
137 /// Name displayed
138 QString m_Name;
99 139 /// Actions associated to the item. The parent of the item (QTreeWidget) takes the ownership of
100 140 /// the actions
101 141 QList<QAction *> m_Actions;
102 142 };
103 143
104 144 DataSourceTreeWidgetItem::DataSourceTreeWidgetItem(const DataSourceItem *data, int type)
105 145 : DataSourceTreeWidgetItem{nullptr, data, type}
106 146 {
107 147 }
108 148
109 149 DataSourceTreeWidgetItem::DataSourceTreeWidgetItem(QTreeWidget *parent, const DataSourceItem *data,
110 150 int type)
111 151 : QTreeWidgetItem{parent, type},
112 152 impl{spimpl::make_unique_impl<DataSourceTreeWidgetItemPrivate>(data)}
113 153 {
114 154 // Sets the icon and the tooltip depending on the data source
115 155 setIcon(0, itemIcon(impl->m_Data));
116 156 setToolTip(0, itemTooltip(impl->m_Data));
117 157
118 158 // Generates tree actions based on the item actions
119 159 auto createTreeAction = [this, &parent](const auto &itemAction) {
120 160 auto treeAction = new QAction{itemAction->name(), parent};
121 161
122 162 // Executes item action when tree action is triggered
123 163 QObject::connect(treeAction, &QAction::triggered, itemAction,
124 164 &DataSourceItemAction::execute);
125 165
126 166 return treeAction;
127 167 };
128 168
129 169 auto itemActions = impl->m_Data->actions();
130 170 std::transform(std::cbegin(itemActions), std::cend(itemActions),
131 171 std::back_inserter(impl->m_Actions), createTreeAction);
132 172
133 173 // Sets the flags of the items
134 174 auto flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
135 175 if (data->type() == DataSourceItemType::COMPONENT
136 176 || data->type() == DataSourceItemType::PRODUCT) {
137 177 flags |= Qt::ItemIsDragEnabled;
138 178 }
139 179
140 180 setFlags(flags);
141 181 }
142 182
143 183 const DataSourceItem *DataSourceTreeWidgetItem::data() const
144 184 {
145 185 return impl->m_Data;
146 186 }
147 187
148 188 QVariant DataSourceTreeWidgetItem::data(int column, int role) const
149 189 {
150 190 if (role == Qt::DisplayRole) {
151 191 if (impl->m_Data) {
152 192 switch (column) {
153 193 case NAME_COLUMN:
154 return impl->m_Data->name();
194 return impl->m_Name;
155 195 default:
156 196 // No action
157 197 break;
158 198 }
159 199
160 200 qCWarning(LOG_DataSourceTreeWidgetItem())
161 201 << QObject::tr("Can't get data (unknown column %1)").arg(column);
162 202 }
163 203 else {
164 204 qCCritical(LOG_DataSourceTreeWidgetItem()) << QObject::tr("Can't get data (null item)");
165 205 }
166 206
167 207 return QVariant{};
168 208 }
169 209 else {
170 210 return QTreeWidgetItem::data(column, role);
171 211 }
172 212 }
173 213
174 214 void DataSourceTreeWidgetItem::setData(int column, int role, const QVariant &value)
175 215 {
176 216 // Data can't be changed by edition
177 217 if (role != Qt::EditRole) {
178 218 QTreeWidgetItem::setData(column, role, value);
179 219 }
180 220 }
181 221
182 222 QList<QAction *> DataSourceTreeWidgetItem::actions() const noexcept
183 223 {
184 224 return impl->m_Actions;
185 225 }
@@ -1,100 +1,123
1 1 #include <DataSource/DataSourceWidget.h>
2 2
3 3 #include <ui_DataSourceWidget.h>
4 4
5 5 #include <DataSource/DataSourceItem.h>
6 6 #include <DataSource/DataSourceTreeWidgetHelper.h>
7 7 #include <DataSource/DataSourceTreeWidgetItem.h>
8 8
9 9 #include <QMenu>
10 10
11 11 namespace {
12 12
13 13 /// Number of columns displayed in the tree
14 14 const auto TREE_NB_COLUMNS = 1;
15 15
16 16 /// Header labels for the tree
17 17 const auto TREE_HEADER_LABELS = QStringList{QObject::tr("Name")};
18 18
19 19 /**
20 20 * Creates the item associated to a data source
21 21 * @param dataSource the data source for which to create the item
22 22 * @return the new item
23 23 */
24 24 DataSourceTreeWidgetItem *createTreeWidgetItem(DataSourceItem *dataSource)
25 25 {
26 26 // Creates item for the data source
27 27 auto item = new DataSourceTreeWidgetItem{dataSource};
28 28
29 29 // Generates items for the children of the data source
30 30 for (auto i = 0; i < dataSource->childCount(); ++i) {
31 31 item->addChild(createTreeWidgetItem(dataSource->child(i)));
32 32 }
33 33
34 34 return item;
35 35 }
36 36
37 37 } // namespace
38 38
39 DataSourceWidget::DataSourceWidget(QWidget *parent) : QWidget{parent}, ui{new Ui::DataSourceWidget}
39 DataSourceWidget::DataSourceWidget(QWidget *parent)
40 : QWidget{parent},
41 ui{new Ui::DataSourceWidget},
42 m_Root{
43 std::make_unique<DataSourceItem>(DataSourceItemType::NODE, QStringLiteral("Sources"))}
40 44 {
41 45 ui->setupUi(this);
42 46
43 47 // Set tree properties
44 48 ui->treeWidget->setColumnCount(TREE_NB_COLUMNS);
45 49 ui->treeWidget->setHeaderLabels(TREE_HEADER_LABELS);
46 50 ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
47 51
48 52 // Connection to show a menu when right clicking on the tree
49 53 connect(ui->treeWidget, &QTreeWidget::customContextMenuRequested, this,
50 54 &DataSourceWidget::onTreeMenuRequested);
51 55
52 56 // Connection to filter tree
53 57 connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &DataSourceWidget::filterChanged);
58
59 // First init
60 updateTreeWidget();
54 61 }
55 62
56 63 DataSourceWidget::~DataSourceWidget() noexcept
57 64 {
58 65 delete ui;
59 66 }
60 67
61 68 void DataSourceWidget::addDataSource(DataSourceItem *dataSource) noexcept
62 69 {
63 // Creates the item associated to the source and adds it to the tree widget. The tree widget
64 // takes the ownership of the item
70 // Merges the data source (without taking its root)
65 71 if (dataSource) {
66 ui->treeWidget->addTopLevelItem(createTreeWidgetItem(dataSource));
72 for (auto i = 0, count = dataSource->childCount(); i < count; ++i) {
73 m_Root->merge(*dataSource->child(i));
74 }
75
76 updateTreeWidget();
67 77 }
68 78 }
69 79
80 void DataSourceWidget::updateTreeWidget() noexcept
81 {
82 ui->treeWidget->clear();
83
84 auto rootItem = createTreeWidgetItem(m_Root.get());
85 ui->treeWidget->addTopLevelItem(rootItem);
86 rootItem->setExpanded(true);
87
88 // Sorts tree
89 ui->treeWidget->setSortingEnabled(true);
90 ui->treeWidget->sortByColumn(0, Qt::AscendingOrder);
91 }
92
70 93 void DataSourceWidget::filterChanged(const QString &text) noexcept
71 94 {
72 95 auto validateItem = [&text](const DataSourceTreeWidgetItem &item) {
73 96 auto regExp = QRegExp{text, Qt::CaseInsensitive, QRegExp::Wildcard};
74 97
75 98 // An item is valid if any of its metadata validates the text filter
76 99 auto itemMetadata = item.data()->data();
77 100 auto itemMetadataEnd = itemMetadata.cend();
78 101 auto acceptFilter
79 102 = [&regExp](const auto &variant) { return variant.toString().contains(regExp); };
80 103
81 104 return std::find_if(itemMetadata.cbegin(), itemMetadataEnd, acceptFilter)
82 105 != itemMetadataEnd;
83 106 };
84 107
85 108 // Applies filter on tree widget
86 109 DataSourceTreeWidgetHelper::filter(*ui->treeWidget, validateItem);
87 110 }
88 111
89 112 void DataSourceWidget::onTreeMenuRequested(const QPoint &pos) noexcept
90 113 {
91 114 // Retrieves the selected item in the tree, and build the menu from its actions
92 115 if (auto selectedItem = dynamic_cast<DataSourceTreeWidgetItem *>(ui->treeWidget->itemAt(pos))) {
93 116 QMenu treeMenu{};
94 117 treeMenu.addActions(selectedItem->actions());
95 118
96 119 if (!treeMenu.isEmpty()) {
97 120 treeMenu.exec(QCursor::pos());
98 121 }
99 122 }
100 123 }
@@ -1,331 +1,331
1 1 #include "Visualization/VisualizationGraphRenderingDelegate.h"
2 2 #include "Visualization/AxisRenderingUtils.h"
3 3 #include "Visualization/ColorScaleEditor.h"
4 4 #include "Visualization/PlottablesRenderingUtils.h"
5 5 #include "Visualization/SqpColorScale.h"
6 6 #include "Visualization/VisualizationGraphWidget.h"
7 7 #include "Visualization/qcustomplot.h"
8 8
9 9 #include <Common/DateUtils.h>
10 10
11 11 #include <Data/IDataSeries.h>
12 12
13 13 #include <SqpApplication.h>
14 14
15 15 namespace {
16 16
17 17 /// Name of the axes layer in QCustomPlot
18 18 const auto AXES_LAYER = QStringLiteral("axes");
19 19
20 20 /// Icon used to show x-axis properties
21 21 const auto HIDE_AXIS_ICON_PATH = QStringLiteral(":/icones/down.png");
22 22
23 23 /// Name of the overlay layer in QCustomPlot
24 24 const auto OVERLAY_LAYER = QStringLiteral("overlay");
25 25
26 26 /// Pixmap used to show x-axis properties
27 27 const auto SHOW_AXIS_ICON_PATH = QStringLiteral(":/icones/up.png");
28 28
29 29 /// Tooltip format for graphs
30 30 const auto GRAPH_TOOLTIP_FORMAT = QStringLiteral("key: %1\nvalue: %2");
31 31
32 32 /// Tooltip format for colormaps
33 33 const auto COLORMAP_TOOLTIP_FORMAT = QStringLiteral("x: %1\ny: %2\nvalue: %3");
34 34
35 35 /// Offset used to shift the tooltip of the mouse
36 36 const auto TOOLTIP_OFFSET = QPoint{20, 20};
37 37
38 38 /// Tooltip display rectangle (the tooltip is hidden when the mouse leaves this rectangle)
39 39 const auto TOOLTIP_RECT = QRect{10, 10, 10, 10};
40 40
41 41 /// Timeout after which the tooltip is displayed
42 42 const auto TOOLTIP_TIMEOUT = 500;
43 43
44 44 void initPointTracerStyle(QCPItemTracer &tracer) noexcept
45 45 {
46 46 tracer.setInterpolating(false);
47 47 tracer.setStyle(QCPItemTracer::tsCircle);
48 48 tracer.setSize(3);
49 49 tracer.setPen(QPen(Qt::black));
50 50 tracer.setBrush(Qt::black);
51 51 }
52 52
53 53 QPixmap pixmap(const QString &iconPath) noexcept
54 54 {
55 55 return QIcon{iconPath}.pixmap(QSize{16, 16});
56 56 }
57 57
58 58 void initClosePixmapStyle(QCPItemPixmap &pixmap) noexcept
59 59 {
60 60 // Icon
61 61 pixmap.setPixmap(
62 62 sqpApp->style()->standardIcon(QStyle::SP_TitleBarCloseButton).pixmap(QSize{16, 16}));
63 63
64 64 // Position
65 65 pixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
66 66 pixmap.topLeft->setCoords(1, 0);
67 67 pixmap.setClipToAxisRect(false);
68 68
69 69 // Can be selected
70 70 pixmap.setSelectable(true);
71 71 }
72 72
73 73 void initXAxisPixmapStyle(QCPItemPixmap &itemPixmap) noexcept
74 74 {
75 75 // Icon
76 76 itemPixmap.setPixmap(pixmap(HIDE_AXIS_ICON_PATH));
77 77
78 78 // Position
79 79 itemPixmap.topLeft->setType(QCPItemPosition::ptAxisRectRatio);
80 80 itemPixmap.topLeft->setCoords(0, 1);
81 81 itemPixmap.setClipToAxisRect(false);
82 82
83 83 // Can be selected
84 84 itemPixmap.setSelectable(true);
85 85 }
86 86
87 87 void initTitleTextStyle(QCPItemText &text) noexcept
88 88 {
89 89 // Font and background styles
90 90 text.setColor(Qt::gray);
91 91 text.setBrush(Qt::white);
92 92
93 93 // Position
94 94 text.setPositionAlignment(Qt::AlignTop | Qt::AlignLeft);
95 95 text.position->setType(QCPItemPosition::ptAxisRectRatio);
96 96 text.position->setCoords(0.5, 0);
97 97 }
98 98
99 99 /**
100 100 * Returns the cell index (x or y) of a colormap according to the coordinate passed in parameter.
101 101 * This method handles the fact that a colormap axis can be logarithmic or linear.
102 102 * @param colormap the colormap for which to calculate the index
103 103 * @param coord the coord to convert to cell index
104 104 * @param xCoord calculates the x index if true, calculates y index if false
105 105 * @return the cell index
106 106 */
107 107 int colorMapCellIndex(const QCPColorMap &colormap, double coord, bool xCoord)
108 108 {
109 109 // Determines the axis of the colormap according to xCoord, and whether it is logarithmic or not
110 110 auto isLogarithmic = (xCoord ? colormap.keyAxis() : colormap.valueAxis())->scaleType()
111 111 == QCPAxis::stLogarithmic;
112 112
113 113 if (isLogarithmic) {
114 114 // For a logarithmic axis we can't use the conversion method of colormap, so we calculate
115 115 // the index manually based on the position of the coordinate on the axis
116 116
117 117 // Gets the axis range and the number of values between range bounds to calculate the step
118 118 // between each value of the range
119 119 auto range = xCoord ? colormap.data()->keyRange() : colormap.data()->valueRange();
120 120 auto nbValues = (xCoord ? colormap.data()->keySize() : colormap.data()->valueSize()) - 1;
121 121 auto valueStep
122 122 = (std::log10(range.upper) - std::log10(range.lower)) / static_cast<double>(nbValues);
123 123
124 124 // According to the coord position, calculates the closest index in the range
125 125 return std::round((std::log10(coord) - std::log10(range.lower)) / valueStep);
126 126 }
127 127 else {
128 128 // For a linear axis, we use the conversion method of colormap
129 129 int index;
130 130 if (xCoord) {
131 131 colormap.data()->coordToCell(coord, 0., &index, nullptr);
132 132 }
133 133 else {
134 134 colormap.data()->coordToCell(0., coord, nullptr, &index);
135 135 }
136 136
137 137 return index;
138 138 }
139 139 }
140 140
141 141 } // namespace
142 142
143 143 struct VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegatePrivate {
144 144 explicit VisualizationGraphRenderingDelegatePrivate(VisualizationGraphWidget &graphWidget)
145 145 : m_Plot{graphWidget.plot()},
146 146 m_PointTracer{new QCPItemTracer{&m_Plot}},
147 147 m_TracerTimer{},
148 148 m_ClosePixmap{new QCPItemPixmap{&m_Plot}},
149 149 m_TitleText{new QCPItemText{&m_Plot}},
150 150 m_XAxisPixmap{new QCPItemPixmap{&m_Plot}},
151 151 m_ShowXAxis{true},
152 152 m_XAxisLabel{},
153 153 m_ColorScale{SqpColorScale{m_Plot}}
154 154 {
155 155 initPointTracerStyle(*m_PointTracer);
156 156
157 157 m_TracerTimer.setInterval(TOOLTIP_TIMEOUT);
158 158 m_TracerTimer.setSingleShot(true);
159 159
160 160 // Inits "close button" in plot overlay
161 161 m_ClosePixmap->setLayer(OVERLAY_LAYER);
162 162 initClosePixmapStyle(*m_ClosePixmap);
163 163
164 164 // Connects pixmap selection to graph widget closing
165 165 QObject::connect(m_ClosePixmap, &QCPItemPixmap::selectionChanged,
166 166 [&graphWidget](bool selected) {
167 167 if (selected) {
168 168 graphWidget.close();
169 169 }
170 170 });
171 171
172 172 // Inits graph name in plot overlay
173 173 m_TitleText->setLayer(OVERLAY_LAYER);
174 174 m_TitleText->setText(graphWidget.name());
175 175 initTitleTextStyle(*m_TitleText);
176 176
177 177 // Inits "show x-axis button" in plot overlay
178 178 m_XAxisPixmap->setLayer(OVERLAY_LAYER);
179 179 initXAxisPixmapStyle(*m_XAxisPixmap);
180 180
181 181 // Connects pixmap selection to graph x-axis showing/hiding
182 182 QObject::connect(m_XAxisPixmap, &QCPItemPixmap::selectionChanged, [this]() {
183 183 if (m_XAxisPixmap->selected()) {
184 184 // Changes the selection state and refreshes the x-axis
185 185 m_ShowXAxis = !m_ShowXAxis;
186 186 updateXAxisState();
187 187 m_Plot.layer(AXES_LAYER)->replot();
188 188
189 189 // Deselects the x-axis pixmap and updates icon
190 190 m_XAxisPixmap->setSelected(false);
191 191 m_XAxisPixmap->setPixmap(
192 192 pixmap(m_ShowXAxis ? HIDE_AXIS_ICON_PATH : SHOW_AXIS_ICON_PATH));
193 193 m_Plot.layer(OVERLAY_LAYER)->replot();
194 194 }
195 195 });
196 196 }
197 197
198 198 /// Updates state of x-axis according to the current selection of x-axis pixmap
199 199 /// @remarks the method doesn't call plot refresh
200 200 void updateXAxisState() noexcept
201 201 {
202 202 m_Plot.xAxis->setTickLabels(m_ShowXAxis);
203 203 m_Plot.xAxis->setLabel(m_ShowXAxis ? m_XAxisLabel : QString{});
204 204 }
205 205
206 206 QCustomPlot &m_Plot;
207 207 QCPItemTracer *m_PointTracer;
208 208 QTimer m_TracerTimer;
209 209 QCPItemPixmap *m_ClosePixmap; /// Graph's close button
210 210 QCPItemText *m_TitleText; /// Graph's title
211 211 QCPItemPixmap *m_XAxisPixmap;
212 212 bool m_ShowXAxis; /// X-axis properties are shown or hidden
213 213 QString m_XAxisLabel;
214 214 SqpColorScale m_ColorScale; /// Color scale used for some types of graphs (as spectrograms)
215 215 };
216 216
217 217 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate(
218 218 VisualizationGraphWidget &graphWidget)
219 219 : impl{spimpl::make_unique_impl<VisualizationGraphRenderingDelegatePrivate>(graphWidget)}
220 220 {
221 221 }
222 222
223 223 void VisualizationGraphRenderingDelegate::onMouseDoubleClick(QMouseEvent *event) noexcept
224 224 {
225 225 // Opens color scale editor if color scale is double clicked
226 auto colorScale = dynamic_cast<QCPColorScale *>(impl->m_Plot.layoutElementAt(event->pos()));
226 auto colorScale = static_cast<QCPColorScale *>(impl->m_Plot.layoutElementAt(event->pos()));
227 227 if (impl->m_ColorScale.m_Scale == colorScale) {
228 228 if (ColorScaleEditor{impl->m_ColorScale}.exec() == QDialog::Accepted) {
229 229 impl->m_Plot.replot();
230 230 }
231 231 }
232 232 }
233 233
234 234 void VisualizationGraphRenderingDelegate::onMouseMove(QMouseEvent *event) noexcept
235 235 {
236 236 // Cancels pending refresh
237 237 impl->m_TracerTimer.disconnect();
238 238
239 239 // Reinits tracers
240 240 impl->m_PointTracer->setGraph(nullptr);
241 241 impl->m_PointTracer->setVisible(false);
242 242 impl->m_Plot.replot();
243 243
244 244 QString tooltip{};
245 245
246 246 // Gets the graph under the mouse position
247 247 auto eventPos = event->pos();
248 248 if (auto graph = qobject_cast<QCPGraph *>(impl->m_Plot.plottableAt(eventPos))) {
249 249 auto mouseKey = graph->keyAxis()->pixelToCoord(eventPos.x());
250 250 auto graphData = graph->data();
251 251
252 252 // Gets the closest data point to the mouse
253 253 auto graphDataIt = graphData->findBegin(mouseKey);
254 254 if (graphDataIt != graphData->constEnd()) {
255 255 // Sets tooltip
256 256 auto key = formatValue(graphDataIt->key, *graph->keyAxis());
257 257 auto value = formatValue(graphDataIt->value, *graph->valueAxis());
258 258 tooltip = GRAPH_TOOLTIP_FORMAT.arg(key, value);
259 259
260 260 // Displays point tracer
261 261 impl->m_PointTracer->setGraph(graph);
262 262 impl->m_PointTracer->setGraphKey(graphDataIt->key);
263 263 impl->m_PointTracer->setLayer(
264 264 impl->m_Plot.layer("main")); // Tracer is set on top of the plot's main layer
265 265 impl->m_PointTracer->setVisible(true);
266 266 impl->m_Plot.replot();
267 267 }
268 268 }
269 269 else if (auto colorMap = qobject_cast<QCPColorMap *>(impl->m_Plot.plottableAt(eventPos))) {
270 270 // Gets x and y coords
271 271 auto x = colorMap->keyAxis()->pixelToCoord(eventPos.x());
272 272 auto y = colorMap->valueAxis()->pixelToCoord(eventPos.y());
273 273
274 274 // Calculates x and y cell indexes, and retrieves the underlying value
275 275 auto xCellIndex = colorMapCellIndex(*colorMap, x, true);
276 276 auto yCellIndex = colorMapCellIndex(*colorMap, y, false);
277 277 auto value = colorMap->data()->cell(xCellIndex, yCellIndex);
278 278
279 279 // Sets tooltips
280 280 tooltip = COLORMAP_TOOLTIP_FORMAT.arg(formatValue(x, *colorMap->keyAxis()),
281 281 formatValue(y, *colorMap->valueAxis()),
282 282 formatValue(value, *colorMap->colorScale()->axis()));
283 283 }
284 284
285 285 if (!tooltip.isEmpty()) {
286 286 // Starts timer to show tooltip after timeout
287 287 auto showTooltip = [tooltip, eventPos, this]() {
288 288 QToolTip::showText(impl->m_Plot.mapToGlobal(eventPos) + TOOLTIP_OFFSET, tooltip,
289 289 &impl->m_Plot, TOOLTIP_RECT);
290 290 };
291 291
292 292 QObject::connect(&impl->m_TracerTimer, &QTimer::timeout, showTooltip);
293 293 impl->m_TracerTimer.start();
294 294 }
295 295 }
296 296
297 297 void VisualizationGraphRenderingDelegate::onPlotUpdated() noexcept
298 298 {
299 299 // Updates color scale bounds
300 300 impl->m_ColorScale.updateDataRange();
301 301 impl->m_Plot.replot();
302 302 }
303 303
304 304 void VisualizationGraphRenderingDelegate::setAxesProperties(
305 305 std::shared_ptr<IDataSeries> dataSeries) noexcept
306 306 {
307 307 // Stores x-axis label to be able to retrieve it when x-axis pixmap is unselected
308 308 impl->m_XAxisLabel = dataSeries->xAxisUnit().m_Name;
309 309
310 310 auto axisHelper = IAxisHelperFactory::create(dataSeries);
311 311 axisHelper->setProperties(impl->m_Plot, impl->m_ColorScale);
312 312
313 313 // Updates x-axis state
314 314 impl->updateXAxisState();
315 315
316 316 impl->m_Plot.layer(AXES_LAYER)->replot();
317 317 }
318 318
319 319 void VisualizationGraphRenderingDelegate::setPlottablesProperties(
320 320 std::shared_ptr<IDataSeries> dataSeries, PlottablesMap &plottables) noexcept
321 321 {
322 322 auto plottablesHelper = IPlottablesHelperFactory::create(dataSeries);
323 323 plottablesHelper->setProperties(plottables);
324 324 }
325 325
326 326 void VisualizationGraphRenderingDelegate::showGraphOverlay(bool show) noexcept
327 327 {
328 328 auto overlay = impl->m_Plot.layer(OVERLAY_LAYER);
329 329 overlay->setVisible(show);
330 330 overlay->replot();
331 331 }
@@ -1,74 +1,77
1 1 #include "AmdaPlugin.h"
2 2 #include "AmdaDefs.h"
3 3 #include "AmdaParser.h"
4 4 #include "AmdaProvider.h"
5 5
6 6 #include <DataSource/DataSourceController.h>
7 7 #include <DataSource/DataSourceItem.h>
8 8 #include <DataSource/DataSourceItemAction.h>
9 9
10 10 #include <SqpApplication.h>
11 11
12 12 Q_LOGGING_CATEGORY(LOG_AmdaPlugin, "AmdaPlugin")
13 13
14 14 namespace {
15 15
16 16 /// Name of the data source
17 17 const auto DATA_SOURCE_NAME = QStringLiteral("AMDA");
18 18
19 19 /// Path of the file used to generate the data source item for AMDA
20 20 const auto JSON_FILE_PATH = QStringLiteral(":/samples/AmdaSampleV3.json");
21 21
22 22 void associateActions(DataSourceItem &item, const QUuid &dataSourceUid)
23 23 {
24 24 auto addLoadAction = [&item, dataSourceUid](const QString &label) {
25 25 item.addAction(
26 26 std::make_unique<DataSourceItemAction>(label, [dataSourceUid](DataSourceItem &item) {
27 27 if (auto app = sqpApp) {
28 28 app->dataSourceController().loadProductItem(dataSourceUid, item);
29 29 }
30 30 }));
31 31 };
32 32
33 33 const auto itemType = item.type();
34 if (itemType == DataSourceItemType::PRODUCT) {
35 addLoadAction(QObject::tr("Load %1 product").arg(item.name()));
36 }
37 else if (itemType == DataSourceItemType::COMPONENT) {
38 addLoadAction(QObject::tr("Load %1 component").arg(item.name()));
34 if (itemType == DataSourceItemType::PRODUCT || itemType == DataSourceItemType::COMPONENT) {
35 // Adds plugin name to item metadata
36 item.setData(DataSourceItem::PLUGIN_DATA_KEY, DATA_SOURCE_NAME);
37
38 // Adds load action
39 auto actionLabel = QObject::tr(
40 itemType == DataSourceItemType::PRODUCT ? "Load %1 product" : "Load %1 component");
41 addLoadAction(actionLabel.arg(item.name()));
39 42 }
40 43
41 44 auto count = item.childCount();
42 45 for (auto i = 0; i < count; ++i) {
43 46 if (auto child = item.child(i)) {
44 47 associateActions(*child, dataSourceUid);
45 48 }
46 49 }
47 50 }
48 51
49 52 } // namespace
50 53
51 54 void AmdaPlugin::initialize()
52 55 {
53 56 if (auto app = sqpApp) {
54 57 // Registers to the data source controller
55 58 auto &dataSourceController = app->dataSourceController();
56 59 auto dataSourceUid = dataSourceController.registerDataSource(DATA_SOURCE_NAME);
57 60
58 61 // Sets data source tree
59 62 if (auto dataSourceItem = AmdaParser::readJson(JSON_FILE_PATH)) {
60 63 associateActions(*dataSourceItem, dataSourceUid);
61 64
62 65 dataSourceController.setDataSourceItem(dataSourceUid, std::move(dataSourceItem));
63 66 }
64 67 else {
65 68 qCCritical(LOG_AmdaPlugin()) << tr("No data source item could be generated for AMDA");
66 69 }
67 70
68 71 // Sets data provider
69 72 dataSourceController.setDataProvider(dataSourceUid, std::make_unique<AmdaProvider>());
70 73 }
71 74 else {
72 75 qCWarning(LOG_AmdaPlugin()) << tr("Can't access to SciQlop application");
73 76 }
74 77 }
@@ -1,114 +1,118
1 1 #include "MockPlugin.h"
2 2 #include "CosinusProvider.h"
3 3 #include "MockDefs.h"
4 4
5 5 #include <DataSource/DataSourceController.h>
6 6 #include <DataSource/DataSourceItem.h>
7 7 #include <DataSource/DataSourceItemAction.h>
8 8
9 9 #include <SqpApplication.h>
10 10
11 11 Q_LOGGING_CATEGORY(LOG_MockPlugin, "MockPlugin")
12 12
13 13 namespace {
14 14
15 15 /// Name of the data source
16 16 const auto DATA_SOURCE_NAME = QStringLiteral("MMS");
17 17
18 18 /// Creates the data provider relative to the plugin
19 19 std::unique_ptr<IDataProvider> createDataProvider() noexcept
20 20 {
21 21 return std::make_unique<CosinusProvider>();
22 22 }
23 23
24 24 std::unique_ptr<DataSourceItem> createProductItem(const QVariantHash &data,
25 25 const QUuid &dataSourceUid)
26 26 {
27 27 auto result = std::make_unique<DataSourceItem>(DataSourceItemType::PRODUCT, data);
28
29 // Adds plugin name to product metadata
30 result->setData(DataSourceItem::PLUGIN_DATA_KEY, DATA_SOURCE_NAME);
31
28 32 auto productName = data.value(DataSourceItem::NAME_DATA_KEY).toString();
29 33
30 34 // Add action to load product from DataSourceController
31 35 result->addAction(std::make_unique<DataSourceItemAction>(
32 36 QObject::tr("Load %1 product").arg(productName),
33 37 [productName, dataSourceUid](DataSourceItem &item) {
34 38 if (auto app = sqpApp) {
35 39 app->dataSourceController().loadProductItem(dataSourceUid, item);
36 40 }
37 41 }));
38 42
39 43 return result;
40 44 }
41 45
42 46 /// Creates the data source item relative to the plugin
43 47 std::unique_ptr<DataSourceItem> createDataSourceItem(const QUuid &dataSourceUid) noexcept
44 48 {
45 49 // Magnetic field products
46 50 auto magneticFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE,
47 QStringLiteral("Magnetic field"));
51 QStringLiteral("_Magnetic field"));
48 52 magneticFieldFolder->appendChild(
49 53 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 10 Hz")},
50 54 {COSINUS_TYPE_KEY, "scalar"},
51 55 {COSINUS_FREQUENCY_KEY, 10.}},
52 56 dataSourceUid));
53 57 magneticFieldFolder->appendChild(
54 58 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 60 Hz")},
55 59 {COSINUS_TYPE_KEY, "scalar"},
56 60 {COSINUS_FREQUENCY_KEY, 60.}},
57 61 dataSourceUid));
58 62 magneticFieldFolder->appendChild(
59 63 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 100 Hz")},
60 64 {COSINUS_TYPE_KEY, "scalar"},
61 65 {COSINUS_FREQUENCY_KEY, 100.}},
62 66 dataSourceUid));
63 67 magneticFieldFolder->appendChild(
64 68 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Vector 10 Hz")},
65 69 {COSINUS_TYPE_KEY, "vector"},
66 70 {COSINUS_FREQUENCY_KEY, 10.}},
67 71 dataSourceUid));
68 72 magneticFieldFolder->appendChild(
69 73 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Vector 60 Hz")},
70 74 {COSINUS_TYPE_KEY, "vector"},
71 75 {COSINUS_FREQUENCY_KEY, 60.}},
72 76 dataSourceUid));
73 77 magneticFieldFolder->appendChild(
74 78 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Vector 100 Hz")},
75 79 {COSINUS_TYPE_KEY, "vector"},
76 80 {COSINUS_FREQUENCY_KEY, 100.}},
77 81 dataSourceUid));
78 82 magneticFieldFolder->appendChild(
79 83 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Spectrogram 1 Hz")},
80 84 {COSINUS_TYPE_KEY, "spectrogram"},
81 85 {COSINUS_FREQUENCY_KEY, 1.}},
82 86 dataSourceUid));
83 87
84 88 // Electric field products
85 89 auto electricFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE,
86 QStringLiteral("Electric field"));
90 QStringLiteral("_Electric field"));
87 91
88 92 // Root
89 93 auto root = std::make_unique<DataSourceItem>(DataSourceItemType::NODE, DATA_SOURCE_NAME);
90 94 root->appendChild(std::move(magneticFieldFolder));
91 95 root->appendChild(std::move(electricFieldFolder));
92 96
93 97 return root;
94 98 }
95 99
96 100 } // namespace
97 101
98 102 void MockPlugin::initialize()
99 103 {
100 104 if (auto app = sqpApp) {
101 105 // Registers to the data source controller
102 106 auto &dataSourceController = app->dataSourceController();
103 107 auto dataSourceUid = dataSourceController.registerDataSource(DATA_SOURCE_NAME);
104 108
105 109 // Sets data source tree
106 110 dataSourceController.setDataSourceItem(dataSourceUid, createDataSourceItem(dataSourceUid));
107 111
108 112 // Sets data provider
109 113 dataSourceController.setDataProvider(dataSourceUid, createDataProvider());
110 114 }
111 115 else {
112 116 qCWarning(LOG_MockPlugin()) << tr("Can't access to SciQlop application");
113 117 }
114 118 }
General Comments 0
You need to be logged in to leave comments. Login now