##// END OF EJS Templates
Use correct product ID when creating an event
trabillard -
r1287:74e2e6e57838
parent child
Show More
@@ -1,154 +1,156
1 #ifndef SCIQLOP_DATASOURCEITEM_H
1 #ifndef SCIQLOP_DATASOURCEITEM_H
2 #define SCIQLOP_DATASOURCEITEM_H
2 #define SCIQLOP_DATASOURCEITEM_H
3
3
4 #include "CoreGlobal.h"
4 #include "CoreGlobal.h"
5
5
6 #include <Common/spimpl.h>
6 #include <Common/spimpl.h>
7
7
8 #include <QVariant>
8 #include <QVariant>
9 #include <QVector>
9 #include <QVector>
10
10
11 class DataSourceItemAction;
11 class DataSourceItemAction;
12
12
13 /**
13 /**
14 * Possible types of an item
14 * Possible types of an item
15 */
15 */
16 enum class DataSourceItemType { NODE, PRODUCT, COMPONENT };
16 enum class DataSourceItemType { NODE, PRODUCT, COMPONENT };
17
17
18 /**
18 /**
19 * @brief The DataSourceItem class aims to represent a structure element of a data source.
19 * @brief The DataSourceItem class aims to represent a structure element of a data source.
20 * A data source has a tree structure that is made up of a main DataSourceItem object (root)
20 * A data source has a tree structure that is made up of a main DataSourceItem object (root)
21 * containing other DataSourceItem objects (children).
21 * containing other DataSourceItem objects (children).
22 * For each DataSourceItem can be associated a set of data representing it.
22 * For each DataSourceItem can be associated a set of data representing it.
23 */
23 */
24 class SCIQLOP_CORE_EXPORT DataSourceItem {
24 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
28 /// Key associated with the plugin of the item
29 static const QString PLUGIN_DATA_KEY;
29 static const QString PLUGIN_DATA_KEY;
30 /// Key associated with a unique id of the plugin
31 static const QString ID_DATA_KEY;
30
32
31 explicit DataSourceItem(DataSourceItemType type, const QString &name);
33 explicit DataSourceItem(DataSourceItemType type, const QString &name);
32 explicit DataSourceItem(DataSourceItemType type, QVariantHash data = {});
34 explicit DataSourceItem(DataSourceItemType type, QVariantHash data = {});
33
35
34 std::unique_ptr<DataSourceItem> clone() const;
36 std::unique_ptr<DataSourceItem> clone() const;
35
37
36 /// @return the actions of the item as a vector
38 /// @return the actions of the item as a vector
37 QVector<DataSourceItemAction *> actions() const noexcept;
39 QVector<DataSourceItemAction *> actions() const noexcept;
38
40
39 /**
41 /**
40 * Adds an action to the item. The item takes ownership of the action, and the action is
42 * Adds an action to the item. The item takes ownership of the action, and the action is
41 * automatically associated to the item
43 * automatically associated to the item
42 * @param action the action to add
44 * @param action the action to add
43 */
45 */
44 void addAction(std::unique_ptr<DataSourceItemAction> action) noexcept;
46 void addAction(std::unique_ptr<DataSourceItemAction> action) noexcept;
45
47
46 /**
48 /**
47 * Adds a child to the item. The item takes ownership of the child.
49 * Adds a child to the item. The item takes ownership of the child.
48 * @param child the child to add
50 * @param child the child to add
49 */
51 */
50 void appendChild(std::unique_ptr<DataSourceItem> child) noexcept;
52 void appendChild(std::unique_ptr<DataSourceItem> child) noexcept;
51
53
52 /**
54 /**
53 * Returns the item's child associated to an index
55 * Returns the item's child associated to an index
54 * @param childIndex the index to search
56 * @param childIndex the index to search
55 * @return a pointer to the child if index is valid, nullptr otherwise
57 * @return a pointer to the child if index is valid, nullptr otherwise
56 */
58 */
57 DataSourceItem *child(int childIndex) const noexcept;
59 DataSourceItem *child(int childIndex) const noexcept;
58
60
59 int childCount() const noexcept;
61 int childCount() const noexcept;
60
62
61 /**
63 /**
62 * Get the data associated to a key
64 * Get the data associated to a key
63 * @param key the key to search
65 * @param key the key to search
64 * @return the data found if key is valid, default QVariant otherwise
66 * @return the data found if key is valid, default QVariant otherwise
65 */
67 */
66 QVariant data(const QString &key) const noexcept;
68 QVariant data(const QString &key) const noexcept;
67
69
68 /// Gets all data
70 /// Gets all data
69 QVariantHash data() const noexcept;
71 QVariantHash data() const noexcept;
70
72
71 /**
73 /**
72 * Merge in the item the source item passed as parameter.
74 * Merge in the item the source item passed as parameter.
73 *
75 *
74 * The merge is done by adding as child of the item the complete tree represented by the source
76 * 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
77 * 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
78 * is merged by completing the existing tree by items "leaves" (products, components or nodes
77 * with no child).
79 * with no child).
78 *
80 *
79 * For example, with item representing the tree:
81 * For example, with item representing the tree:
80 * R (root node)
82 * R (root node)
81 * - N1 (node)
83 * - N1 (node)
82 * -- N11 (node)
84 * -- N11 (node)
83 * --- P1 (product)
85 * --- P1 (product)
84 * --- P2 (product)
86 * --- P2 (product)
85 * - N2 (node)
87 * - N2 (node)
86 *
88 *
87 * and the source item representing the tree:
89 * and the source item representing the tree:
88 * N1 (root node)
90 * N1 (root node)
89 * - N11 (node)
91 * - N11 (node)
90 * -- P3 (product)
92 * -- P3 (product)
91 * - N12 (node)
93 * - N12 (node)
92 *
94 *
93 * The leaves of the source item to merge into the item are N1/N11/P3 and N1/N12 => we therefore
95 * 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:
96 * have the following merge result:
95 * R
97 * R
96 * - N1
98 * - N1
97 * -- N11
99 * -- N11
98 * --- P1
100 * --- P1
99 * --- P2
101 * --- P2
100 * --- P3 (added leaf)
102 * --- P3 (added leaf)
101 * -- N12 (added leaf)
103 * -- N12 (added leaf)
102 *
104 *
103 * @param item the source item
105 * @param item the source item
104 * @remarks No control is performed on products or components that are merged into the same tree
106 * @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)
107 * 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
108 * @remarks the merge is made by copy (source item is not changed and still exists after the
107 * operation)
109 * operation)
108 */
110 */
109 void merge(const DataSourceItem &item);
111 void merge(const DataSourceItem &item);
110
112
111 bool isRoot() const noexcept;
113 bool isRoot() const noexcept;
112
114
113 QString name() const noexcept;
115 QString name() const noexcept;
114
116
115 /**
117 /**
116 * Get the item's parent
118 * Get the item's parent
117 * @return a pointer to the parent if it exists, nullptr if the item is a root
119 * @return a pointer to the parent if it exists, nullptr if the item is a root
118 */
120 */
119 DataSourceItem *parentItem() const noexcept;
121 DataSourceItem *parentItem() const noexcept;
120
122
121 /**
123 /**
122 * Gets the item's root
124 * Gets the item's root
123 * @return the top parent, the item itself if it's the root item
125 * @return the top parent, the item itself if it's the root item
124 */
126 */
125 const DataSourceItem &rootItem() const noexcept;
127 const DataSourceItem &rootItem() const noexcept;
126
128
127 /**
129 /**
128 * Sets or appends a value to a key
130 * Sets or appends a value to a key
129 * @param key the key
131 * @param key the key
130 * @param value the value
132 * @param value the value
131 * @param append if true, the value is added to the values already existing for the key,
133 * @param append if true, the value is added to the values already existing for the key,
132 * otherwise it replaces the existing values
134 * otherwise it replaces the existing values
133 */
135 */
134 void setData(const QString &key, const QVariant &value, bool append = false) noexcept;
136 void setData(const QString &key, const QVariant &value, bool append = false) noexcept;
135
137
136 DataSourceItemType type() const noexcept;
138 DataSourceItemType type() const noexcept;
137
139
138 /**
140 /**
139 * @brief Searches the first child matching the specified data.
141 * @brief Searches the first child matching the specified data.
140 * @param data The data to search.
142 * @param data The data to search.
141 * @param recursive So the search recursively.
143 * @param recursive So the search recursively.
142 * @return the item matching the data or nullptr if it was not found.
144 * @return the item matching the data or nullptr if it was not found.
143 */
145 */
144 DataSourceItem *findItem(const QVariantHash &data, bool recursive);
146 DataSourceItem *findItem(const QVariantHash &data, bool recursive);
145
147
146 bool operator==(const DataSourceItem &other);
148 bool operator==(const DataSourceItem &other);
147 bool operator!=(const DataSourceItem &other);
149 bool operator!=(const DataSourceItem &other);
148
150
149 private:
151 private:
150 class DataSourceItemPrivate;
152 class DataSourceItemPrivate;
151 spimpl::unique_impl_ptr<DataSourceItemPrivate> impl;
153 spimpl::unique_impl_ptr<DataSourceItemPrivate> impl;
152 };
154 };
153
155
154 #endif // SCIQLOP_DATASOURCEITEMMODEL_H
156 #endif // SCIQLOP_DATASOURCEITEMMODEL_H
@@ -1,186 +1,187
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 #include <DataSource/DataSourceItemMergeHelper.h>
4
4
5 #include <QVector>
5 #include <QVector>
6
6
7 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");
8 const QString DataSourceItem::PLUGIN_DATA_KEY = QStringLiteral("plugin");
9 const QString DataSourceItem::ID_DATA_KEY = QStringLiteral("uuid");
9
10
10 struct DataSourceItem::DataSourceItemPrivate {
11 struct DataSourceItem::DataSourceItemPrivate {
11 explicit DataSourceItemPrivate(DataSourceItemType type, QVariantHash data)
12 explicit DataSourceItemPrivate(DataSourceItemType type, QVariantHash data)
12 : m_Parent{nullptr}, m_Children{}, m_Type{type}, m_Data{std::move(data)}, m_Actions{}
13 : m_Parent{nullptr}, m_Children{}, m_Type{type}, m_Data{std::move(data)}, m_Actions{}
13 {
14 {
14 }
15 }
15
16
16 DataSourceItem *m_Parent;
17 DataSourceItem *m_Parent;
17 std::vector<std::unique_ptr<DataSourceItem> > m_Children;
18 std::vector<std::unique_ptr<DataSourceItem> > m_Children;
18 DataSourceItemType m_Type;
19 DataSourceItemType m_Type;
19 QVariantHash m_Data;
20 QVariantHash m_Data;
20 std::vector<std::unique_ptr<DataSourceItemAction> > m_Actions;
21 std::vector<std::unique_ptr<DataSourceItemAction> > m_Actions;
21 };
22 };
22
23
23 DataSourceItem::DataSourceItem(DataSourceItemType type, const QString &name)
24 DataSourceItem::DataSourceItem(DataSourceItemType type, const QString &name)
24 : DataSourceItem{type, QVariantHash{{NAME_DATA_KEY, name}}}
25 : DataSourceItem{type, QVariantHash{{NAME_DATA_KEY, name}}}
25 {
26 {
26 }
27 }
27
28
28 DataSourceItem::DataSourceItem(DataSourceItemType type, QVariantHash data)
29 DataSourceItem::DataSourceItem(DataSourceItemType type, QVariantHash data)
29 : impl{spimpl::make_unique_impl<DataSourceItemPrivate>(type, std::move(data))}
30 : impl{spimpl::make_unique_impl<DataSourceItemPrivate>(type, std::move(data))}
30 {
31 {
31 }
32 }
32
33
33 std::unique_ptr<DataSourceItem> DataSourceItem::clone() const
34 std::unique_ptr<DataSourceItem> DataSourceItem::clone() const
34 {
35 {
35 auto result = std::make_unique<DataSourceItem>(impl->m_Type, impl->m_Data);
36 auto result = std::make_unique<DataSourceItem>(impl->m_Type, impl->m_Data);
36
37
37 // Clones children
38 // Clones children
38 for (const auto &child : impl->m_Children) {
39 for (const auto &child : impl->m_Children) {
39 result->appendChild(std::move(child->clone()));
40 result->appendChild(std::move(child->clone()));
40 }
41 }
41
42
42 // Clones actions
43 // Clones actions
43 for (const auto &action : impl->m_Actions) {
44 for (const auto &action : impl->m_Actions) {
44 result->addAction(std::move(action->clone()));
45 result->addAction(std::move(action->clone()));
45 }
46 }
46
47
47 return result;
48 return result;
48 }
49 }
49
50
50 QVector<DataSourceItemAction *> DataSourceItem::actions() const noexcept
51 QVector<DataSourceItemAction *> DataSourceItem::actions() const noexcept
51 {
52 {
52 auto result = QVector<DataSourceItemAction *>{};
53 auto result = QVector<DataSourceItemAction *>{};
53
54
54 std::transform(std::cbegin(impl->m_Actions), std::cend(impl->m_Actions),
55 std::transform(std::cbegin(impl->m_Actions), std::cend(impl->m_Actions),
55 std::back_inserter(result), [](const auto &action) { return action.get(); });
56 std::back_inserter(result), [](const auto &action) { return action.get(); });
56
57
57 return result;
58 return result;
58 }
59 }
59
60
60 void DataSourceItem::addAction(std::unique_ptr<DataSourceItemAction> action) noexcept
61 void DataSourceItem::addAction(std::unique_ptr<DataSourceItemAction> action) noexcept
61 {
62 {
62 action->setDataSourceItem(this);
63 action->setDataSourceItem(this);
63 impl->m_Actions.push_back(std::move(action));
64 impl->m_Actions.push_back(std::move(action));
64 }
65 }
65
66
66 void DataSourceItem::appendChild(std::unique_ptr<DataSourceItem> child) noexcept
67 void DataSourceItem::appendChild(std::unique_ptr<DataSourceItem> child) noexcept
67 {
68 {
68 child->impl->m_Parent = this;
69 child->impl->m_Parent = this;
69 impl->m_Children.push_back(std::move(child));
70 impl->m_Children.push_back(std::move(child));
70 }
71 }
71
72
72 DataSourceItem *DataSourceItem::child(int childIndex) const noexcept
73 DataSourceItem *DataSourceItem::child(int childIndex) const noexcept
73 {
74 {
74 if (childIndex < 0 || childIndex >= childCount()) {
75 if (childIndex < 0 || childIndex >= childCount()) {
75 return nullptr;
76 return nullptr;
76 }
77 }
77 else {
78 else {
78 return impl->m_Children.at(childIndex).get();
79 return impl->m_Children.at(childIndex).get();
79 }
80 }
80 }
81 }
81
82
82 int DataSourceItem::childCount() const noexcept
83 int DataSourceItem::childCount() const noexcept
83 {
84 {
84 return impl->m_Children.size();
85 return impl->m_Children.size();
85 }
86 }
86
87
87 QVariant DataSourceItem::data(const QString &key) const noexcept
88 QVariant DataSourceItem::data(const QString &key) const noexcept
88 {
89 {
89 return impl->m_Data.value(key);
90 return impl->m_Data.value(key);
90 }
91 }
91
92
92 QVariantHash DataSourceItem::data() const noexcept
93 QVariantHash DataSourceItem::data() const noexcept
93 {
94 {
94 return impl->m_Data;
95 return impl->m_Data;
95 }
96 }
96
97
97 void DataSourceItem::merge(const DataSourceItem &item)
98 void DataSourceItem::merge(const DataSourceItem &item)
98 {
99 {
99 DataSourceItemMergeHelper::merge(item, *this);
100 DataSourceItemMergeHelper::merge(item, *this);
100 }
101 }
101
102
102 bool DataSourceItem::isRoot() const noexcept
103 bool DataSourceItem::isRoot() const noexcept
103 {
104 {
104 return impl->m_Parent == nullptr;
105 return impl->m_Parent == nullptr;
105 }
106 }
106
107
107 QString DataSourceItem::name() const noexcept
108 QString DataSourceItem::name() const noexcept
108 {
109 {
109 return data(NAME_DATA_KEY).toString();
110 return data(NAME_DATA_KEY).toString();
110 }
111 }
111
112
112 DataSourceItem *DataSourceItem::parentItem() const noexcept
113 DataSourceItem *DataSourceItem::parentItem() const noexcept
113 {
114 {
114 return impl->m_Parent;
115 return impl->m_Parent;
115 }
116 }
116
117
117 const DataSourceItem &DataSourceItem::rootItem() const noexcept
118 const DataSourceItem &DataSourceItem::rootItem() const noexcept
118 {
119 {
119 return isRoot() ? *this : parentItem()->rootItem();
120 return isRoot() ? *this : parentItem()->rootItem();
120 }
121 }
121
122
122 void DataSourceItem::setData(const QString &key, const QVariant &value, bool append) noexcept
123 void DataSourceItem::setData(const QString &key, const QVariant &value, bool append) noexcept
123 {
124 {
124 auto it = impl->m_Data.constFind(key);
125 auto it = impl->m_Data.constFind(key);
125 if (append && it != impl->m_Data.constEnd()) {
126 if (append && it != impl->m_Data.constEnd()) {
126 // Case of an existing value to which we want to add to the new value
127 // Case of an existing value to which we want to add to the new value
127 if (it->canConvert<QVariantList>()) {
128 if (it->canConvert<QVariantList>()) {
128 auto variantList = it->value<QVariantList>();
129 auto variantList = it->value<QVariantList>();
129 variantList.append(value);
130 variantList.append(value);
130
131
131 impl->m_Data.insert(key, variantList);
132 impl->m_Data.insert(key, variantList);
132 }
133 }
133 else {
134 else {
134 impl->m_Data.insert(key, QVariantList{*it, value});
135 impl->m_Data.insert(key, QVariantList{*it, value});
135 }
136 }
136 }
137 }
137 else {
138 else {
138 // Other cases :
139 // Other cases :
139 // - new value in map OR
140 // - new value in map OR
140 // - replacement of an existing value (not appending)
141 // - replacement of an existing value (not appending)
141 impl->m_Data.insert(key, value);
142 impl->m_Data.insert(key, value);
142 }
143 }
143 }
144 }
144
145
145 DataSourceItemType DataSourceItem::type() const noexcept
146 DataSourceItemType DataSourceItem::type() const noexcept
146 {
147 {
147 return impl->m_Type;
148 return impl->m_Type;
148 }
149 }
149
150
150 DataSourceItem *DataSourceItem::findItem(const QVariantHash &data, bool recursive)
151 DataSourceItem *DataSourceItem::findItem(const QVariantHash &data, bool recursive)
151 {
152 {
152 for (const auto &child : impl->m_Children) {
153 for (const auto &child : impl->m_Children) {
153 if (child->impl->m_Data == data) {
154 if (child->impl->m_Data == data) {
154 return child.get();
155 return child.get();
155 }
156 }
156
157
157 if (recursive) {
158 if (recursive) {
158 if (auto foundItem = child->findItem(data, true)) {
159 if (auto foundItem = child->findItem(data, true)) {
159 return foundItem;
160 return foundItem;
160 }
161 }
161 }
162 }
162 }
163 }
163
164
164 return nullptr;
165 return nullptr;
165 }
166 }
166
167
167 bool DataSourceItem::operator==(const DataSourceItem &other)
168 bool DataSourceItem::operator==(const DataSourceItem &other)
168 {
169 {
169 // Compares items' attributes
170 // Compares items' attributes
170 if (std::tie(impl->m_Type, impl->m_Data) == std::tie(other.impl->m_Type, other.impl->m_Data)) {
171 if (std::tie(impl->m_Type, impl->m_Data) == std::tie(other.impl->m_Type, other.impl->m_Data)) {
171 // Compares contents of items' children
172 // Compares contents of items' children
172 return std::equal(std::cbegin(impl->m_Children), std::cend(impl->m_Children),
173 return std::equal(std::cbegin(impl->m_Children), std::cend(impl->m_Children),
173 std::cbegin(other.impl->m_Children), std::cend(other.impl->m_Children),
174 std::cbegin(other.impl->m_Children), std::cend(other.impl->m_Children),
174 [](const auto &itemChild, const auto &otherChild) {
175 [](const auto &itemChild, const auto &otherChild) {
175 return *itemChild == *otherChild;
176 return *itemChild == *otherChild;
176 });
177 });
177 }
178 }
178 else {
179 else {
179 return false;
180 return false;
180 }
181 }
181 }
182 }
182
183
183 bool DataSourceItem::operator!=(const DataSourceItem &other)
184 bool DataSourceItem::operator!=(const DataSourceItem &other)
184 {
185 {
185 return !(*this == other);
186 return !(*this == other);
186 }
187 }
@@ -1,133 +1,135
1 #include "Catalogue/CatalogueActionManager.h"
1 #include "Catalogue/CatalogueActionManager.h"
2
2
3 #include <Actions/ActionsGuiController.h>
3 #include <Actions/ActionsGuiController.h>
4 #include <Catalogue/CatalogueController.h>
4 #include <Catalogue/CatalogueController.h>
5 #include <DataSource/DataSourceItem.h>
5 #include <SqpApplication.h>
6 #include <SqpApplication.h>
6 #include <Variable/Variable.h>
7 #include <Variable/Variable.h>
7 #include <Visualization/VisualizationGraphWidget.h>
8 #include <Visualization/VisualizationGraphWidget.h>
8 #include <Visualization/VisualizationSelectionZoneItem.h>
9 #include <Visualization/VisualizationSelectionZoneItem.h>
9
10
10 #include <Catalogue/CatalogueEventsWidget.h>
11 #include <Catalogue/CatalogueEventsWidget.h>
11 #include <Catalogue/CatalogueExplorer.h>
12 #include <Catalogue/CatalogueExplorer.h>
12 #include <Catalogue/CatalogueSideBarWidget.h>
13 #include <Catalogue/CatalogueSideBarWidget.h>
13 #include <Catalogue/CreateEventDialog.h>
14 #include <Catalogue/CreateEventDialog.h>
14
15
15 #include <DBCatalogue.h>
16 #include <DBCatalogue.h>
16 #include <DBEvent.h>
17 #include <DBEvent.h>
17 #include <DBEventProduct.h>
18 #include <DBEventProduct.h>
18
19
19 #include <QBoxLayout>
20 #include <QBoxLayout>
20 #include <QComboBox>
21 #include <QComboBox>
21 #include <QDialog>
22 #include <QDialog>
22 #include <QDialogButtonBox>
23 #include <QDialogButtonBox>
23 #include <QLineEdit>
24 #include <QLineEdit>
24 #include <memory>
25 #include <memory>
25
26
26 struct CatalogueActionManager::CatalogueActionManagerPrivate {
27 struct CatalogueActionManager::CatalogueActionManagerPrivate {
27
28
28 CatalogueExplorer *m_CatalogueExplorer = nullptr;
29 CatalogueExplorer *m_CatalogueExplorer = nullptr;
29
30
30 CatalogueActionManagerPrivate(CatalogueExplorer *catalogueExplorer)
31 CatalogueActionManagerPrivate(CatalogueExplorer *catalogueExplorer)
31 : m_CatalogueExplorer(catalogueExplorer)
32 : m_CatalogueExplorer(catalogueExplorer)
32 {
33 {
33 }
34 }
34
35
35 void createEventFromZones(const QString &eventName,
36 void createEventFromZones(const QString &eventName,
36 const QVector<VisualizationSelectionZoneItem *> &zones,
37 const QVector<VisualizationSelectionZoneItem *> &zones,
37 const std::shared_ptr<DBCatalogue> &catalogue = nullptr)
38 const std::shared_ptr<DBCatalogue> &catalogue = nullptr)
38 {
39 {
39 auto event = std::make_shared<DBEvent>();
40 auto event = std::make_shared<DBEvent>();
40 event->setName(eventName);
41 event->setName(eventName);
41
42
42 std::list<DBEventProduct> productList;
43 std::list<DBEventProduct> productList;
43 for (auto zone : zones) {
44 for (auto zone : zones) {
44 auto graph = zone->parentGraphWidget();
45 auto graph = zone->parentGraphWidget();
45 for (auto var : graph->variables()) {
46 for (auto var : graph->variables()) {
46 auto eventProduct = std::make_shared<DBEventProduct>();
47 auto eventProduct = std::make_shared<DBEventProduct>();
47 eventProduct->setEvent(*event);
48 eventProduct->setEvent(*event);
48
49
49 auto zoneRange = zone->range();
50 auto zoneRange = zone->range();
50 eventProduct->setTStart(zoneRange.m_TStart);
51 eventProduct->setTStart(zoneRange.m_TStart);
51 eventProduct->setTEnd(zoneRange.m_TEnd);
52 eventProduct->setTEnd(zoneRange.m_TEnd);
52
53
53 eventProduct->setProductId(var->metadata().value("id", "TODO").toString()); // todo
54 eventProduct->setProductId(
55 var->metadata().value(DataSourceItem::ID_DATA_KEY, "UnknownID").toString());
54
56
55 productList.push_back(*eventProduct);
57 productList.push_back(*eventProduct);
56 }
58 }
57 }
59 }
58
60
59 event->setEventProducts(productList);
61 event->setEventProducts(productList);
60
62
61 sqpApp->catalogueController().addEvent(event);
63 sqpApp->catalogueController().addEvent(event);
62
64
63
65
64 if (catalogue) {
66 if (catalogue) {
65 // TODO
67 // TODO
66 // catalogue->addEvent(event);
68 // catalogue->addEvent(event);
67 m_CatalogueExplorer->sideBarWidget().setCatalogueChanges(catalogue, true);
69 m_CatalogueExplorer->sideBarWidget().setCatalogueChanges(catalogue, true);
68 if (m_CatalogueExplorer->eventsWidget().displayedCatalogues().contains(catalogue)) {
70 if (m_CatalogueExplorer->eventsWidget().displayedCatalogues().contains(catalogue)) {
69 m_CatalogueExplorer->eventsWidget().addEvent(event);
71 m_CatalogueExplorer->eventsWidget().addEvent(event);
70 m_CatalogueExplorer->eventsWidget().setEventChanges(event, true);
72 m_CatalogueExplorer->eventsWidget().setEventChanges(event, true);
71 }
73 }
72 }
74 }
73 else if (m_CatalogueExplorer->eventsWidget().isAllEventsDisplayed()) {
75 else if (m_CatalogueExplorer->eventsWidget().isAllEventsDisplayed()) {
74 m_CatalogueExplorer->eventsWidget().addEvent(event);
76 m_CatalogueExplorer->eventsWidget().addEvent(event);
75 m_CatalogueExplorer->eventsWidget().setEventChanges(event, true);
77 m_CatalogueExplorer->eventsWidget().setEventChanges(event, true);
76 }
78 }
77 }
79 }
78 };
80 };
79
81
80 CatalogueActionManager::CatalogueActionManager(CatalogueExplorer *catalogueExplorer)
82 CatalogueActionManager::CatalogueActionManager(CatalogueExplorer *catalogueExplorer)
81 : impl{spimpl::make_unique_impl<CatalogueActionManagerPrivate>(catalogueExplorer)}
83 : impl{spimpl::make_unique_impl<CatalogueActionManagerPrivate>(catalogueExplorer)}
82 {
84 {
83 }
85 }
84
86
85 void CatalogueActionManager::installSelectionZoneActions()
87 void CatalogueActionManager::installSelectionZoneActions()
86 {
88 {
87 auto &actionController = sqpApp->actionsGuiController();
89 auto &actionController = sqpApp->actionsGuiController();
88
90
89 auto createEventEnableFuntion = [](auto zones) {
91 auto createEventEnableFuntion = [](auto zones) {
90 QSet<VisualizationGraphWidget *> usedGraphs;
92 QSet<VisualizationGraphWidget *> usedGraphs;
91 for (auto zone : zones) {
93 for (auto zone : zones) {
92 auto graph = zone->parentGraphWidget();
94 auto graph = zone->parentGraphWidget();
93 if (!usedGraphs.contains(graph)) {
95 if (!usedGraphs.contains(graph)) {
94 usedGraphs.insert(graph);
96 usedGraphs.insert(graph);
95 }
97 }
96 else {
98 else {
97 return false;
99 return false;
98 }
100 }
99 }
101 }
100
102
101 return true;
103 return true;
102 };
104 };
103
105
104 auto createEventAction = actionController.addSectionZoneAction(
106 auto createEventAction = actionController.addSectionZoneAction(
105 {QObject::tr("Catalogues")}, QObject::tr("New Event..."), [this](auto zones) {
107 {QObject::tr("Catalogues")}, QObject::tr("New Event..."), [this](auto zones) {
106 CreateEventDialog dialog(
108 CreateEventDialog dialog(
107 impl->m_CatalogueExplorer->sideBarWidget().getCatalogues("Default"));
109 impl->m_CatalogueExplorer->sideBarWidget().getCatalogues("Default"));
108 dialog.hideCatalogueChoice();
110 dialog.hideCatalogueChoice();
109 if (dialog.exec() == QDialog::Accepted) {
111 if (dialog.exec() == QDialog::Accepted) {
110 impl->createEventFromZones(dialog.eventName(), zones);
112 impl->createEventFromZones(dialog.eventName(), zones);
111 }
113 }
112 });
114 });
113 createEventAction->setEnableFunction(createEventEnableFuntion);
115 createEventAction->setEnableFunction(createEventEnableFuntion);
114
116
115 auto createEventInCatalogueAction = actionController.addSectionZoneAction(
117 auto createEventInCatalogueAction = actionController.addSectionZoneAction(
116 {QObject::tr("Catalogues")}, QObject::tr("New Event in Catalogue..."), [this](auto zones) {
118 {QObject::tr("Catalogues")}, QObject::tr("New Event in Catalogue..."), [this](auto zones) {
117 CreateEventDialog dialog(
119 CreateEventDialog dialog(
118 impl->m_CatalogueExplorer->sideBarWidget().getCatalogues("Default"));
120 impl->m_CatalogueExplorer->sideBarWidget().getCatalogues("Default"));
119 if (dialog.exec() == QDialog::Accepted) {
121 if (dialog.exec() == QDialog::Accepted) {
120 auto selectedCatalogue = dialog.selectedCatalogue();
122 auto selectedCatalogue = dialog.selectedCatalogue();
121 if (!selectedCatalogue) {
123 if (!selectedCatalogue) {
122 selectedCatalogue = std::make_shared<DBCatalogue>();
124 selectedCatalogue = std::make_shared<DBCatalogue>();
123 selectedCatalogue->setName(dialog.catalogueName());
125 selectedCatalogue->setName(dialog.catalogueName());
124 // sqpApp->catalogueController().addCatalogue(selectedCatalogue); TODO
126 // sqpApp->catalogueController().addCatalogue(selectedCatalogue); TODO
125 impl->m_CatalogueExplorer->sideBarWidget().addCatalogue(selectedCatalogue,
127 impl->m_CatalogueExplorer->sideBarWidget().addCatalogue(selectedCatalogue,
126 "Default");
128 "Default");
127 }
129 }
128
130
129 impl->createEventFromZones(dialog.eventName(), zones, selectedCatalogue);
131 impl->createEventFromZones(dialog.eventName(), zones, selectedCatalogue);
130 }
132 }
131 });
133 });
132 createEventInCatalogueAction->setEnableFunction(createEventEnableFuntion);
134 createEventInCatalogueAction->setEnableFunction(createEventEnableFuntion);
133 }
135 }
@@ -1,78 +1,79
1 #include "AmdaPlugin.h"
1 #include "AmdaPlugin.h"
2 #include "AmdaDefs.h"
2 #include "AmdaDefs.h"
3 #include "AmdaParser.h"
3 #include "AmdaParser.h"
4 #include "AmdaProvider.h"
4 #include "AmdaProvider.h"
5 #include "AmdaServer.h"
5 #include "AmdaServer.h"
6
6
7 #include <DataSource/DataSourceController.h>
7 #include <DataSource/DataSourceController.h>
8 #include <DataSource/DataSourceItem.h>
8 #include <DataSource/DataSourceItem.h>
9 #include <DataSource/DataSourceItemAction.h>
9 #include <DataSource/DataSourceItemAction.h>
10
10
11 #include <SqpApplication.h>
11 #include <SqpApplication.h>
12
12
13 Q_LOGGING_CATEGORY(LOG_AmdaPlugin, "AmdaPlugin")
13 Q_LOGGING_CATEGORY(LOG_AmdaPlugin, "AmdaPlugin")
14
14
15 namespace {
15 namespace {
16
16
17 /// Path of the file used to generate the data source item for AMDA
17 /// Path of the file used to generate the data source item for AMDA
18 const auto JSON_FILE_PATH = QStringLiteral(":/samples/AmdaSampleV3.json");
18 const auto JSON_FILE_PATH = QStringLiteral(":/samples/AmdaSampleV3.json");
19
19
20 void associateActions(DataSourceItem &item, const QUuid &dataSourceUid)
20 void associateActions(DataSourceItem &item, const QUuid &dataSourceUid)
21 {
21 {
22 auto addLoadAction = [&item, dataSourceUid](const QString &label) {
22 auto addLoadAction = [&item, dataSourceUid](const QString &label) {
23 item.addAction(
23 item.addAction(
24 std::make_unique<DataSourceItemAction>(label, [dataSourceUid](DataSourceItem &item) {
24 std::make_unique<DataSourceItemAction>(label, [dataSourceUid](DataSourceItem &item) {
25 if (auto app = sqpApp) {
25 if (auto app = sqpApp) {
26 app->dataSourceController().loadProductItem(dataSourceUid, item);
26 app->dataSourceController().loadProductItem(dataSourceUid, item);
27 }
27 }
28 }));
28 }));
29 };
29 };
30
30
31 const auto itemType = item.type();
31 const auto itemType = item.type();
32 if (itemType == DataSourceItemType::PRODUCT || itemType == DataSourceItemType::COMPONENT) {
32 if (itemType == DataSourceItemType::PRODUCT || itemType == DataSourceItemType::COMPONENT) {
33 // Adds plugin name to item metadata
33 // Adds plugin name to item metadata
34 item.setData(DataSourceItem::PLUGIN_DATA_KEY, AmdaServer::instance().name());
34 item.setData(DataSourceItem::PLUGIN_DATA_KEY, AmdaServer::instance().name());
35
35
36 // Adds load action
36 // Adds load action
37 auto actionLabel = QObject::tr(
37 auto actionLabel = QObject::tr(
38 itemType == DataSourceItemType::PRODUCT ? "Load %1 product" : "Load %1 component");
38 itemType == DataSourceItemType::PRODUCT ? "Load %1 product" : "Load %1 component");
39 addLoadAction(actionLabel.arg(item.name()));
39 addLoadAction(actionLabel.arg(item.name()));
40 item.setData(DataSourceItem::ID_DATA_KEY, item.data(AMDA_XML_ID_KEY));
40 }
41 }
41
42
42 auto count = item.childCount();
43 auto count = item.childCount();
43 for (auto i = 0; i < count; ++i) {
44 for (auto i = 0; i < count; ++i) {
44 if (auto child = item.child(i)) {
45 if (auto child = item.child(i)) {
45 associateActions(*child, dataSourceUid);
46 associateActions(*child, dataSourceUid);
46 }
47 }
47 }
48 }
48 }
49 }
49
50
50 } // namespace
51 } // namespace
51
52
52 void AmdaPlugin::initialize()
53 void AmdaPlugin::initialize()
53 {
54 {
54 if (auto app = sqpApp) {
55 if (auto app = sqpApp) {
55 auto dataSourceName = AmdaServer::instance().name();
56 auto dataSourceName = AmdaServer::instance().name();
56
57
57 // Registers to the data source controller
58 // Registers to the data source controller
58 auto &dataSourceController = app->dataSourceController();
59 auto &dataSourceController = app->dataSourceController();
59 auto dataSourceUid = dataSourceController.registerDataSource(dataSourceName);
60 auto dataSourceUid = dataSourceController.registerDataSource(dataSourceName);
60
61
61 // Sets data source tree
62 // Sets data source tree
62 if (auto dataSourceItem = AmdaParser::readJson(JSON_FILE_PATH)) {
63 if (auto dataSourceItem = AmdaParser::readJson(JSON_FILE_PATH)) {
63 dataSourceItem->setData(DataSourceItem::NAME_DATA_KEY, dataSourceName);
64 dataSourceItem->setData(DataSourceItem::NAME_DATA_KEY, dataSourceName);
64
65
65 associateActions(*dataSourceItem, dataSourceUid);
66 associateActions(*dataSourceItem, dataSourceUid);
66 dataSourceController.setDataSourceItem(dataSourceUid, std::move(dataSourceItem));
67 dataSourceController.setDataSourceItem(dataSourceUid, std::move(dataSourceItem));
67 }
68 }
68 else {
69 else {
69 qCCritical(LOG_AmdaPlugin()) << tr("No data source item could be generated for AMDA");
70 qCCritical(LOG_AmdaPlugin()) << tr("No data source item could be generated for AMDA");
70 }
71 }
71
72
72 // Sets data provider
73 // Sets data provider
73 dataSourceController.setDataProvider(dataSourceUid, std::make_unique<AmdaProvider>());
74 dataSourceController.setDataProvider(dataSourceUid, std::make_unique<AmdaProvider>());
74 }
75 }
75 else {
76 else {
76 qCWarning(LOG_AmdaPlugin()) << tr("Can't access to SciQlop application");
77 qCWarning(LOG_AmdaPlugin()) << tr("Can't access to SciQlop application");
77 }
78 }
78 }
79 }
@@ -1,118 +1,119
1 #include "MockPlugin.h"
1 #include "MockPlugin.h"
2 #include "CosinusProvider.h"
2 #include "CosinusProvider.h"
3 #include "MockDefs.h"
3 #include "MockDefs.h"
4
4
5 #include <DataSource/DataSourceController.h>
5 #include <DataSource/DataSourceController.h>
6 #include <DataSource/DataSourceItem.h>
6 #include <DataSource/DataSourceItem.h>
7 #include <DataSource/DataSourceItemAction.h>
7 #include <DataSource/DataSourceItemAction.h>
8
8
9 #include <SqpApplication.h>
9 #include <SqpApplication.h>
10
10
11 Q_LOGGING_CATEGORY(LOG_MockPlugin, "MockPlugin")
11 Q_LOGGING_CATEGORY(LOG_MockPlugin, "MockPlugin")
12
12
13 namespace {
13 namespace {
14
14
15 /// Name of the data source
15 /// Name of the data source
16 const auto DATA_SOURCE_NAME = QStringLiteral("MMS");
16 const auto DATA_SOURCE_NAME = QStringLiteral("MMS");
17
17
18 /// Creates the data provider relative to the plugin
18 /// Creates the data provider relative to the plugin
19 std::unique_ptr<IDataProvider> createDataProvider() noexcept
19 std::unique_ptr<IDataProvider> createDataProvider() noexcept
20 {
20 {
21 return std::make_unique<CosinusProvider>();
21 return std::make_unique<CosinusProvider>();
22 }
22 }
23
23
24 std::unique_ptr<DataSourceItem> createProductItem(const QVariantHash &data,
24 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
28
29 // Adds plugin name to product metadata
29 // Adds plugin name to product metadata
30 result->setData(DataSourceItem::PLUGIN_DATA_KEY, DATA_SOURCE_NAME);
30 result->setData(DataSourceItem::PLUGIN_DATA_KEY, DATA_SOURCE_NAME);
31 result->setData(DataSourceItem::ID_DATA_KEY, data.value(DataSourceItem::NAME_DATA_KEY));
31
32
32 auto productName = data.value(DataSourceItem::NAME_DATA_KEY).toString();
33 auto productName = data.value(DataSourceItem::NAME_DATA_KEY).toString();
33
34
34 // Add action to load product from DataSourceController
35 // Add action to load product from DataSourceController
35 result->addAction(std::make_unique<DataSourceItemAction>(
36 result->addAction(std::make_unique<DataSourceItemAction>(
36 QObject::tr("Load %1 product").arg(productName),
37 QObject::tr("Load %1 product").arg(productName),
37 [productName, dataSourceUid](DataSourceItem &item) {
38 [productName, dataSourceUid](DataSourceItem &item) {
38 if (auto app = sqpApp) {
39 if (auto app = sqpApp) {
39 app->dataSourceController().loadProductItem(dataSourceUid, item);
40 app->dataSourceController().loadProductItem(dataSourceUid, item);
40 }
41 }
41 }));
42 }));
42
43
43 return result;
44 return result;
44 }
45 }
45
46
46 /// Creates the data source item relative to the plugin
47 /// Creates the data source item relative to the plugin
47 std::unique_ptr<DataSourceItem> createDataSourceItem(const QUuid &dataSourceUid) noexcept
48 std::unique_ptr<DataSourceItem> createDataSourceItem(const QUuid &dataSourceUid) noexcept
48 {
49 {
49 // Magnetic field products
50 // Magnetic field products
50 auto magneticFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE,
51 auto magneticFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE,
51 QStringLiteral("_Magnetic field"));
52 QStringLiteral("_Magnetic field"));
52 magneticFieldFolder->appendChild(
53 magneticFieldFolder->appendChild(
53 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 10 Hz")},
54 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 10 Hz")},
54 {COSINUS_TYPE_KEY, "scalar"},
55 {COSINUS_TYPE_KEY, "scalar"},
55 {COSINUS_FREQUENCY_KEY, 10.}},
56 {COSINUS_FREQUENCY_KEY, 10.}},
56 dataSourceUid));
57 dataSourceUid));
57 magneticFieldFolder->appendChild(
58 magneticFieldFolder->appendChild(
58 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 60 Hz")},
59 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 60 Hz")},
59 {COSINUS_TYPE_KEY, "scalar"},
60 {COSINUS_TYPE_KEY, "scalar"},
60 {COSINUS_FREQUENCY_KEY, 60.}},
61 {COSINUS_FREQUENCY_KEY, 60.}},
61 dataSourceUid));
62 dataSourceUid));
62 magneticFieldFolder->appendChild(
63 magneticFieldFolder->appendChild(
63 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 100 Hz")},
64 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Scalar 100 Hz")},
64 {COSINUS_TYPE_KEY, "scalar"},
65 {COSINUS_TYPE_KEY, "scalar"},
65 {COSINUS_FREQUENCY_KEY, 100.}},
66 {COSINUS_FREQUENCY_KEY, 100.}},
66 dataSourceUid));
67 dataSourceUid));
67 magneticFieldFolder->appendChild(
68 magneticFieldFolder->appendChild(
68 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Vector 10 Hz")},
69 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Vector 10 Hz")},
69 {COSINUS_TYPE_KEY, "vector"},
70 {COSINUS_TYPE_KEY, "vector"},
70 {COSINUS_FREQUENCY_KEY, 10.}},
71 {COSINUS_FREQUENCY_KEY, 10.}},
71 dataSourceUid));
72 dataSourceUid));
72 magneticFieldFolder->appendChild(
73 magneticFieldFolder->appendChild(
73 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Vector 60 Hz")},
74 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Vector 60 Hz")},
74 {COSINUS_TYPE_KEY, "vector"},
75 {COSINUS_TYPE_KEY, "vector"},
75 {COSINUS_FREQUENCY_KEY, 60.}},
76 {COSINUS_FREQUENCY_KEY, 60.}},
76 dataSourceUid));
77 dataSourceUid));
77 magneticFieldFolder->appendChild(
78 magneticFieldFolder->appendChild(
78 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Vector 100 Hz")},
79 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Vector 100 Hz")},
79 {COSINUS_TYPE_KEY, "vector"},
80 {COSINUS_TYPE_KEY, "vector"},
80 {COSINUS_FREQUENCY_KEY, 100.}},
81 {COSINUS_FREQUENCY_KEY, 100.}},
81 dataSourceUid));
82 dataSourceUid));
82 magneticFieldFolder->appendChild(
83 magneticFieldFolder->appendChild(
83 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Spectrogram 1 Hz")},
84 createProductItem({{DataSourceItem::NAME_DATA_KEY, QStringLiteral("Spectrogram 1 Hz")},
84 {COSINUS_TYPE_KEY, "spectrogram"},
85 {COSINUS_TYPE_KEY, "spectrogram"},
85 {COSINUS_FREQUENCY_KEY, 1.}},
86 {COSINUS_FREQUENCY_KEY, 1.}},
86 dataSourceUid));
87 dataSourceUid));
87
88
88 // Electric field products
89 // Electric field products
89 auto electricFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE,
90 auto electricFieldFolder = std::make_unique<DataSourceItem>(DataSourceItemType::NODE,
90 QStringLiteral("_Electric field"));
91 QStringLiteral("_Electric field"));
91
92
92 // Root
93 // Root
93 auto root = std::make_unique<DataSourceItem>(DataSourceItemType::NODE, DATA_SOURCE_NAME);
94 auto root = std::make_unique<DataSourceItem>(DataSourceItemType::NODE, DATA_SOURCE_NAME);
94 root->appendChild(std::move(magneticFieldFolder));
95 root->appendChild(std::move(magneticFieldFolder));
95 root->appendChild(std::move(electricFieldFolder));
96 root->appendChild(std::move(electricFieldFolder));
96
97
97 return root;
98 return root;
98 }
99 }
99
100
100 } // namespace
101 } // namespace
101
102
102 void MockPlugin::initialize()
103 void MockPlugin::initialize()
103 {
104 {
104 if (auto app = sqpApp) {
105 if (auto app = sqpApp) {
105 // Registers to the data source controller
106 // Registers to the data source controller
106 auto &dataSourceController = app->dataSourceController();
107 auto &dataSourceController = app->dataSourceController();
107 auto dataSourceUid = dataSourceController.registerDataSource(DATA_SOURCE_NAME);
108 auto dataSourceUid = dataSourceController.registerDataSource(DATA_SOURCE_NAME);
108
109
109 // Sets data source tree
110 // Sets data source tree
110 dataSourceController.setDataSourceItem(dataSourceUid, createDataSourceItem(dataSourceUid));
111 dataSourceController.setDataSourceItem(dataSourceUid, createDataSourceItem(dataSourceUid));
111
112
112 // Sets data provider
113 // Sets data provider
113 dataSourceController.setDataProvider(dataSourceUid, createDataProvider());
114 dataSourceController.setDataProvider(dataSourceUid, createDataProvider());
114 }
115 }
115 else {
116 else {
116 qCWarning(LOG_MockPlugin()) << tr("Can't access to SciQlop application");
117 qCWarning(LOG_MockPlugin()) << tr("Can't access to SciQlop application");
117 }
118 }
118 }
119 }
General Comments 3
Under Review
author

Auto status change to "Under Review"

Approved
author

Status change > Approved

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