@@ -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 | 112 | <widget class="QWidget" name="commonPropertyInspectorWidget" native="true"/> |
|
113 | 113 | </item> |
|
114 | 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 | 122 | </item> |
|
117 | 123 | </layout> |
|
118 | 124 | </widget> |
@@ -25,10 +25,14 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 | |
@@ -64,6 +68,46 public: | |||
|
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; |
@@ -35,6 +35,8 public: | |||
|
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 |
@@ -31,6 +31,7 core_sources = [ | |||
|
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', |
@@ -12,28 +12,6 | |||
|
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; |
@@ -131,8 +109,7 void DataSourceController::loadProductItem(const QUuid &dataSourceUid, | |||
|
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(), |
|
|
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"); |
@@ -1,9 +1,11 | |||
|
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) |
@@ -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 | 50 | QVector<DataSourceItemAction *> DataSourceItem::actions() const noexcept |
|
32 | 51 | { |
|
33 | 52 | auto result = QVector<DataSourceItemAction *>{}; |
@@ -75,6 +94,11 QVariantHash DataSourceItem::data() const noexcept | |||
|
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; |
@@ -146,7 +170,7 bool DataSourceItem::operator==(const DataSourceItem &other) | |||
|
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 | }); |
@@ -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 | 30 | QString DataSourceItemAction::name() const noexcept |
|
26 | 31 | { |
|
27 | 32 | return impl->m_Name; |
@@ -10,6 +10,7 tests = [ | |||
|
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'] |
@@ -19,7 +20,9 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 |
@@ -3,6 +3,8 | |||
|
3 | 3 | |
|
4 | 4 | #include <QWidget> |
|
5 | 5 | |
|
6 | #include <memory> | |
|
7 | ||
|
6 | 8 | namespace Ui { |
|
7 | 9 | class DataSourceWidget; |
|
8 | 10 | } // Ui |
@@ -29,7 +31,10 public slots: | |||
|
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 |
@@ -11,6 +11,41 namespace { | |||
|
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) { |
@@ -92,10 +127,15 QString itemTooltip(const DataSourceItem *dataSource) noexcept | |||
|
92 | 127 | } // namespace |
|
93 | 128 | |
|
94 | 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 | 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; |
@@ -151,7 +191,7 QVariant DataSourceTreeWidgetItem::data(int column, int role) const | |||
|
151 | 191 | if (impl->m_Data) { |
|
152 | 192 | switch (column) { |
|
153 | 193 | case NAME_COLUMN: |
|
154 |
return impl->m_ |
|
|
194 | return impl->m_Name; | |
|
155 | 195 | default: |
|
156 | 196 | // No action |
|
157 | 197 | break; |
@@ -36,7 +36,11 DataSourceTreeWidgetItem *createTreeWidgetItem(DataSourceItem *dataSource) | |||
|
36 | 36 | |
|
37 | 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 | 45 | ui->setupUi(this); |
|
42 | 46 | |
@@ -51,6 +55,9 DataSourceWidget::DataSourceWidget(QWidget *parent) : QWidget{parent}, ui{new Ui | |||
|
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 |
@@ -60,11 +67,27 DataSourceWidget::~DataSourceWidget() noexcept | |||
|
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(); | |
|
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 | 93 | void DataSourceWidget::filterChanged(const QString &text) noexcept |
@@ -223,7 +223,7 VisualizationGraphRenderingDelegate::VisualizationGraphRenderingDelegate( | |||
|
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 = |
|
|
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(); |
@@ -31,11 +31,14 void associateActions(DataSourceItem &item, const QUuid &dataSourceUid) | |||
|
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(); |
@@ -25,6 +25,10 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 |
@@ -44,7 +48,7 std::unique_ptr<DataSourceItem> createDataSourceItem(const QUuid &dataSourceUid) | |||
|
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"}, |
@@ -83,7 +87,7 std::unique_ptr<DataSourceItem> createDataSourceItem(const QUuid &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); |
General Comments 0
You need to be logged in to leave comments.
Login now