@@ -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" |
@@ -112,7 +112,13 | |||||
112 | <widget class="QWidget" name="commonPropertyInspectorWidget" native="true"/> |
|
112 | <widget class="QWidget" name="commonPropertyInspectorWidget" native="true"/> | |
113 | </item> |
|
113 | </item> | |
114 | <item> |
|
114 | <item> | |
115 |
<widget class=" |
|
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 | </item> |
|
122 | </item> | |
117 | </layout> |
|
123 | </layout> | |
118 | </widget> |
|
124 | </widget> |
@@ -25,10 +25,14 class SCIQLOP_CORE_EXPORT DataSourceItem { | |||||
25 | public: |
|
25 | public: | |
26 | /// Key associated with the name of the item |
|
26 | /// Key associated with the name of the item | |
27 | static const QString NAME_DATA_KEY; |
|
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 | explicit DataSourceItem(DataSourceItemType type, const QString &name); |
|
31 | explicit DataSourceItem(DataSourceItemType type, const QString &name); | |
30 | explicit DataSourceItem(DataSourceItemType type, QVariantHash data = {}); |
|
32 | explicit DataSourceItem(DataSourceItemType type, QVariantHash data = {}); | |
31 |
|
33 | |||
|
34 | std::unique_ptr<DataSourceItem> clone() const; | |||
|
35 | ||||
32 | /// @return the actions of the item as a vector |
|
36 | /// @return the actions of the item as a vector | |
33 | QVector<DataSourceItemAction *> actions() const noexcept; |
|
37 | QVector<DataSourceItemAction *> actions() const noexcept; | |
34 |
|
38 | |||
@@ -64,6 +68,46 public: | |||||
64 | /// Gets all data |
|
68 | /// Gets all data | |
65 | QVariantHash data() const noexcept; |
|
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 | bool isRoot() const noexcept; |
|
111 | bool isRoot() const noexcept; | |
68 |
|
112 | |||
69 | QString name() const noexcept; |
|
113 | QString name() const noexcept; |
@@ -35,6 +35,8 public: | |||||
35 | */ |
|
35 | */ | |
36 | explicit DataSourceItemAction(const QString &name, ExecuteFunction fun); |
|
36 | explicit DataSourceItemAction(const QString &name, ExecuteFunction fun); | |
37 |
|
37 | |||
|
38 | std::unique_ptr<DataSourceItemAction> clone() const; | |||
|
39 | ||||
38 | QString name() const noexcept; |
|
40 | QString name() const noexcept; | |
39 |
|
41 | |||
40 | /// Sets the data source item concerned by the action |
|
42 | /// Sets the data source item concerned by the action |
@@ -31,6 +31,7 core_sources = [ | |||||
31 | 'src/DataSource/DataSourceController.cpp', |
|
31 | 'src/DataSource/DataSourceController.cpp', | |
32 | 'src/DataSource/DataSourceItem.cpp', |
|
32 | 'src/DataSource/DataSourceItem.cpp', | |
33 | 'src/DataSource/DataSourceItemAction.cpp', |
|
33 | 'src/DataSource/DataSourceItemAction.cpp', | |
|
34 | 'src/DataSource/DataSourceItemMergeHelper.cpp', | |||
34 | 'src/Network/NetworkController.cpp', |
|
35 | 'src/Network/NetworkController.cpp', | |
35 | 'src/Plugin/PluginManager.cpp', |
|
36 | 'src/Plugin/PluginManager.cpp', | |
36 | 'src/Settings/SqpSettingsDefs.cpp', |
|
37 | 'src/Settings/SqpSettingsDefs.cpp', |
@@ -12,28 +12,6 | |||||
12 |
|
12 | |||
13 | Q_LOGGING_CATEGORY(LOG_DataSourceController, "DataSourceController") |
|
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 | class DataSourceController::DataSourceControllerPrivate { |
|
15 | class DataSourceController::DataSourceControllerPrivate { | |
38 | public: |
|
16 | public: | |
39 | QMutex m_WorkingMutex; |
|
17 | QMutex m_WorkingMutex; | |
@@ -131,8 +109,7 void DataSourceController::loadProductItem(const QUuid &dataSourceUid, | |||||
131 | auto it = impl->m_DataProviders.find(dataSourceUid); |
|
109 | auto it = impl->m_DataProviders.find(dataSourceUid); | |
132 | auto dataProvider = (it != impl->m_DataProviders.end()) ? it->second : nullptr; |
|
110 | auto dataProvider = (it != impl->m_DataProviders.end()) ? it->second : nullptr; | |
133 |
|
111 | |||
134 |
emit variableCreationRequested(productItem.name(), |
|
112 | emit variableCreationRequested(productItem.name(), productItem.data(), dataProvider); | |
135 | dataProvider); |
|
|||
136 | } |
|
113 | } | |
137 | else { |
|
114 | else { | |
138 | qCWarning(LOG_DataSourceController()) << tr("Can't load an item that is not a product"); |
|
115 | qCWarning(LOG_DataSourceController()) << tr("Can't load an item that is not a product"); |
@@ -1,9 +1,11 | |||||
1 | #include <DataSource/DataSourceItem.h> |
|
1 | #include <DataSource/DataSourceItem.h> | |
2 | #include <DataSource/DataSourceItemAction.h> |
|
2 | #include <DataSource/DataSourceItemAction.h> | |
|
3 | #include <DataSource/DataSourceItemMergeHelper.h> | |||
3 |
|
4 | |||
4 | #include <QVector> |
|
5 | #include <QVector> | |
5 |
|
6 | |||
6 | const QString DataSourceItem::NAME_DATA_KEY = QStringLiteral("name"); |
|
7 | const QString DataSourceItem::NAME_DATA_KEY = QStringLiteral("name"); | |
|
8 | const QString DataSourceItem::PLUGIN_DATA_KEY = QStringLiteral("plugin"); | |||
7 |
|
9 | |||
8 | struct DataSourceItem::DataSourceItemPrivate { |
|
10 | struct DataSourceItem::DataSourceItemPrivate { | |
9 | explicit DataSourceItemPrivate(DataSourceItemType type, QVariantHash data) |
|
11 | explicit DataSourceItemPrivate(DataSourceItemType type, QVariantHash data) | |
@@ -28,6 +30,23 DataSourceItem::DataSourceItem(DataSourceItemType type, QVariantHash 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 | QVector<DataSourceItemAction *> DataSourceItem::actions() const noexcept |
|
50 | QVector<DataSourceItemAction *> DataSourceItem::actions() const noexcept | |
32 | { |
|
51 | { | |
33 | auto result = QVector<DataSourceItemAction *>{}; |
|
52 | auto result = QVector<DataSourceItemAction *>{}; | |
@@ -75,6 +94,11 QVariantHash DataSourceItem::data() const noexcept | |||||
75 | return impl->m_Data; |
|
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 | bool DataSourceItem::isRoot() const noexcept |
|
102 | bool DataSourceItem::isRoot() const noexcept | |
79 | { |
|
103 | { | |
80 | return impl->m_Parent == nullptr; |
|
104 | return impl->m_Parent == nullptr; | |
@@ -146,7 +170,7 bool DataSourceItem::operator==(const DataSourceItem &other) | |||||
146 | if (std::tie(impl->m_Type, impl->m_Data) == std::tie(other.impl->m_Type, other.impl->m_Data)) { |
|
170 | if (std::tie(impl->m_Type, impl->m_Data) == std::tie(other.impl->m_Type, other.impl->m_Data)) { | |
147 | // Compares contents of items' children |
|
171 | // Compares contents of items' children | |
148 | return std::equal(std::cbegin(impl->m_Children), std::cend(impl->m_Children), |
|
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 | [](const auto &itemChild, const auto &otherChild) { |
|
174 | [](const auto &itemChild, const auto &otherChild) { | |
151 | return *itemChild == *otherChild; |
|
175 | return *itemChild == *otherChild; | |
152 | }); |
|
176 | }); |
@@ -22,6 +22,11 DataSourceItemAction::DataSourceItemAction(const QString &name, ExecuteFunction | |||||
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 | QString DataSourceItemAction::name() const noexcept |
|
30 | QString DataSourceItemAction::name() const noexcept | |
26 | { |
|
31 | { | |
27 | return impl->m_Name; |
|
32 | return impl->m_Name; |
@@ -10,6 +10,7 tests = [ | |||||
10 | [['Data/TestTwoDimArrayData.cpp'],'test_2d','Two Dim Array test'], |
|
10 | [['Data/TestTwoDimArrayData.cpp'],'test_2d','Two Dim Array test'], | |
11 | [['Data/TestDataSeriesUtils.cpp'],'test_dataseries_util','Data series utils test'], |
|
11 | [['Data/TestDataSeriesUtils.cpp'],'test_dataseries_util','Data series utils test'], | |
12 | [['DataSource/TestDataSourceController.cpp'],'test_data_source','DataSourceController test'], |
|
12 | [['DataSource/TestDataSourceController.cpp'],'test_data_source','DataSourceController test'], | |
|
13 | [['DataSource/TestDataSourceItem.cpp'],'test_data_source_item','DataSourceItem test'], | |||
13 | [['Variable/TestVariableCacheController.cpp'],'test_variable_cache','VariableCacheController test'], |
|
14 | [['Variable/TestVariableCacheController.cpp'],'test_variable_cache','VariableCacheController test'], | |
14 | [['Variable/TestVariable.cpp'],'test_variable','Variable test'], |
|
15 | [['Variable/TestVariable.cpp'],'test_variable','Variable test'], | |
15 | [['Variable/TestVariableSync.cpp'],'test_variable_sync','Variable synchronization test'] |
|
16 | [['Variable/TestVariableSync.cpp'],'test_variable_sync','Variable synchronization test'] | |
@@ -19,7 +20,9 amdatest_sources = [ | |||||
19 | 'Data/DataSeriesBuilders.h', |
|
20 | 'Data/DataSeriesBuilders.h', | |
20 | 'Data/DataSeriesBuilders.cpp', |
|
21 | 'Data/DataSeriesBuilders.cpp', | |
21 | 'Data/DataSeriesTestsUtils.h', |
|
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 | foreach unit_test : tests |
|
28 | foreach unit_test : tests |
@@ -3,6 +3,8 | |||||
3 |
|
3 | |||
4 | #include <QWidget> |
|
4 | #include <QWidget> | |
5 |
|
5 | |||
|
6 | #include <memory> | |||
|
7 | ||||
6 | namespace Ui { |
|
8 | namespace Ui { | |
7 | class DataSourceWidget; |
|
9 | class DataSourceWidget; | |
8 | } // Ui |
|
10 | } // Ui | |
@@ -29,7 +31,10 public slots: | |||||
29 | void addDataSource(DataSourceItem *dataSource) noexcept; |
|
31 | void addDataSource(DataSourceItem *dataSource) noexcept; | |
30 |
|
32 | |||
31 | private: |
|
33 | private: | |
|
34 | void updateTreeWidget() noexcept; | |||
|
35 | ||||
32 | Ui::DataSourceWidget *ui; |
|
36 | Ui::DataSourceWidget *ui; | |
|
37 | std::unique_ptr<DataSourceItem> m_Root; | |||
33 |
|
38 | |||
34 | private slots: |
|
39 | private slots: | |
35 | /// Slot called when the filtering text has changed |
|
40 | /// Slot called when the filtering text has changed |
@@ -11,6 +11,41 namespace { | |||||
11 | // Column indexes |
|
11 | // Column indexes | |
12 | const auto NAME_COLUMN = 0; |
|
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 | QIcon itemIcon(const DataSourceItem *dataSource) |
|
49 | QIcon itemIcon(const DataSourceItem *dataSource) | |
15 | { |
|
50 | { | |
16 | if (dataSource) { |
|
51 | if (dataSource) { | |
@@ -92,10 +127,15 QString itemTooltip(const DataSourceItem *dataSource) noexcept | |||||
92 | } // namespace |
|
127 | } // namespace | |
93 |
|
128 | |||
94 | struct DataSourceTreeWidgetItem::DataSourceTreeWidgetItemPrivate { |
|
129 | struct DataSourceTreeWidgetItem::DataSourceTreeWidgetItemPrivate { | |
95 |
explicit DataSourceTreeWidgetItemPrivate(const DataSourceItem *data) |
|
130 | explicit DataSourceTreeWidgetItemPrivate(const DataSourceItem *data) | |
|
131 | : m_Data{data}, m_Name{completeName(*m_Data)} | |||
|
132 | { | |||
|
133 | } | |||
96 |
|
134 | |||
97 | /// Model used to retrieve data source information |
|
135 | /// Model used to retrieve data source information | |
98 | const DataSourceItem *m_Data; |
|
136 | const DataSourceItem *m_Data; | |
|
137 | /// Name displayed | |||
|
138 | QString m_Name; | |||
99 | /// Actions associated to the item. The parent of the item (QTreeWidget) takes the ownership of |
|
139 | /// Actions associated to the item. The parent of the item (QTreeWidget) takes the ownership of | |
100 | /// the actions |
|
140 | /// the actions | |
101 | QList<QAction *> m_Actions; |
|
141 | QList<QAction *> m_Actions; | |
@@ -151,7 +191,7 QVariant DataSourceTreeWidgetItem::data(int column, int role) const | |||||
151 | if (impl->m_Data) { |
|
191 | if (impl->m_Data) { | |
152 | switch (column) { |
|
192 | switch (column) { | |
153 | case NAME_COLUMN: |
|
193 | case NAME_COLUMN: | |
154 |
return impl->m_ |
|
194 | return impl->m_Name; | |
155 | default: |
|
195 | default: | |
156 | // No action |
|
196 | // No action | |
157 | break; |
|
197 | break; |
@@ -36,7 +36,11 DataSourceTreeWidgetItem *createTreeWidgetItem(DataSourceItem *dataSource) | |||||
36 |
|
36 | |||
37 | } // namespace |
|
37 | } // namespace | |
38 |
|
38 | |||
39 |
DataSourceWidget::DataSourceWidget(QWidget *parent) |
|
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 | ui->setupUi(this); |
|
45 | ui->setupUi(this); | |
42 |
|
46 | |||
@@ -51,6 +55,9 DataSourceWidget::DataSourceWidget(QWidget *parent) : QWidget{parent}, ui{new Ui | |||||
51 |
|
55 | |||
52 | // Connection to filter tree |
|
56 | // Connection to filter tree | |
53 | connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &DataSourceWidget::filterChanged); |
|
57 | connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &DataSourceWidget::filterChanged); | |
|
58 | ||||
|
59 | // First init | |||
|
60 | updateTreeWidget(); | |||
54 | } |
|
61 | } | |
55 |
|
62 | |||
56 | DataSourceWidget::~DataSourceWidget() noexcept |
|
63 | DataSourceWidget::~DataSourceWidget() noexcept | |
@@ -60,11 +67,27 DataSourceWidget::~DataSourceWidget() noexcept | |||||
60 |
|
67 | |||
61 | void DataSourceWidget::addDataSource(DataSourceItem *dataSource) noexcept |
|
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 |
|
70 | // Merges the data source (without taking its root) | |
64 | // takes the ownership of the item |
|
|||
65 | if (dataSource) { |
|
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(); | |||
|
77 | } | |||
67 | } |
|
78 | } | |
|
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); | |||
68 | } |
|
91 | } | |
69 |
|
92 | |||
70 | void DataSourceWidget::filterChanged(const QString &text) noexcept |
|
93 | void DataSourceWidget::filterChanged(const QString &text) noexcept |
@@ -223,7 +223,7 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate( | |||||
223 | void VisualizationGraphRenderingDelegate::onMouseDoubleClick(QMouseEvent *event) noexcept |
|
223 | void VisualizationGraphRenderingDelegate::onMouseDoubleClick(QMouseEvent *event) noexcept | |
224 | { |
|
224 | { | |
225 | // Opens color scale editor if color scale is double clicked |
|
225 | // Opens color scale editor if color scale is double clicked | |
226 |
auto colorScale = |
|
226 | auto colorScale = static_cast<QCPColorScale *>(impl->m_Plot.layoutElementAt(event->pos())); | |
227 | if (impl->m_ColorScale.m_Scale == colorScale) { |
|
227 | if (impl->m_ColorScale.m_Scale == colorScale) { | |
228 | if (ColorScaleEditor{impl->m_ColorScale}.exec() == QDialog::Accepted) { |
|
228 | if (ColorScaleEditor{impl->m_ColorScale}.exec() == QDialog::Accepted) { | |
229 | impl->m_Plot.replot(); |
|
229 | impl->m_Plot.replot(); |
@@ -31,11 +31,14 void associateActions(DataSourceItem &item, const QUuid &dataSourceUid) | |||||
31 | }; |
|
31 | }; | |
32 |
|
32 | |||
33 | const auto itemType = item.type(); |
|
33 | const auto itemType = item.type(); | |
34 | if (itemType == DataSourceItemType::PRODUCT) { |
|
34 | if (itemType == DataSourceItemType::PRODUCT || itemType == DataSourceItemType::COMPONENT) { | |
35 | addLoadAction(QObject::tr("Load %1 product").arg(item.name())); |
|
35 | // Adds plugin name to item metadata | |
36 | } |
|
36 | item.setData(DataSourceItem::PLUGIN_DATA_KEY, DATA_SOURCE_NAME); | |
37 | else if (itemType == DataSourceItemType::COMPONENT) { |
|
37 | ||
38 | addLoadAction(QObject::tr("Load %1 component").arg(item.name())); |
|
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 | auto count = item.childCount(); |
|
44 | auto count = item.childCount(); |
@@ -25,6 +25,10 std::unique_ptr<DataSourceItem> createProductItem(const QVariantHash &data, | |||||
25 | const QUuid &dataSourceUid) |
|
25 | const QUuid &dataSourceUid) | |
26 | { |
|
26 | { | |
27 | auto result = std::make_unique<DataSourceItem>(DataSourceItemType::PRODUCT, data); |
|
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 | auto productName = data.value(DataSourceItem::NAME_DATA_KEY).toString(); |
|
32 | auto productName = data.value(DataSourceItem::NAME_DATA_KEY).toString(); | |
29 |
|
33 | |||
30 | // Add action to load product from DataSourceController |
|
34 | // Add action to load product from DataSourceController | |
@@ -44,7 +48,7 std::unique_ptr<DataSourceItem> createDataSourceItem(const QUuid &dataSourceUid) | |||||
44 | { |
|
48 | { | |
45 | // Magnetic field products |
|
49 | // Magnetic field products | |
46 | auto magneticFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE, |
|
50 | auto magneticFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE, | |
47 | QStringLiteral("Magnetic field")); |
|
51 | QStringLiteral("_Magnetic field")); | |
48 | magneticFieldFolder->appendChild( |
|
52 | magneticFieldFolder->appendChild( | |
49 | createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 10 Hz")}, |
|
53 | createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 10 Hz")}, | |
50 | {COSINUS_TYPE_KEY, "scalar"}, |
|
54 | {COSINUS_TYPE_KEY, "scalar"}, | |
@@ -83,7 +87,7 std::unique_ptr<DataSourceItem> createDataSourceItem(const QUuid &dataSourceUid) | |||||
83 |
|
87 | |||
84 | // Electric field products |
|
88 | // Electric field products | |
85 | auto electricFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE, |
|
89 | auto electricFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE, | |
86 | QStringLiteral("Electric field")); |
|
90 | QStringLiteral("_Electric field")); | |
87 |
|
91 | |||
88 | // Root |
|
92 | // Root | |
89 | auto root = std::make_unique<DataSourceItem>(DataSourceItemType::NODE, DATA_SOURCE_NAME); |
|
93 | auto root = std::make_unique<DataSourceItem>(DataSourceItemType::NODE, DATA_SOURCE_NAME); |
General Comments 0
You need to be logged in to leave comments.
Login now