From a949d016326d9d196de05808dd27907fb0168ffe 2017-11-29 08:28:58 From: Alexandre Leroux Date: 2017-11-29 08:28:58 Subject: [PATCH] Merge branch 'feature/MergeDataSourceItem' into develop --- diff --git a/app/ui/MainWindow.ui b/app/ui/MainWindow.ui index 44aecb5..79fbee1 100644 --- a/app/ui/MainWindow.ui +++ b/app/ui/MainWindow.ui @@ -112,7 +112,13 @@ - + + + + Name + + + diff --git a/core/include/DataSource/DataSourceItem.h b/core/include/DataSource/DataSourceItem.h index 3b03955..9b82471 100644 --- a/core/include/DataSource/DataSourceItem.h +++ b/core/include/DataSource/DataSourceItem.h @@ -25,10 +25,14 @@ class SCIQLOP_CORE_EXPORT DataSourceItem { public: /// Key associated with the name of the item static const QString NAME_DATA_KEY; + /// Key associated with the plugin of the item + static const QString PLUGIN_DATA_KEY; explicit DataSourceItem(DataSourceItemType type, const QString &name); explicit DataSourceItem(DataSourceItemType type, QVariantHash data = {}); + std::unique_ptr clone() const; + /// @return the actions of the item as a vector QVector actions() const noexcept; @@ -64,6 +68,46 @@ public: /// Gets all data QVariantHash data() const noexcept; + /** + * Merge in the item the source item passed as parameter. + * + * The merge is done by adding as child of the item the complete tree represented by the source + * item. If a part of the tree already exists in the item (based on the name of the nodes), it + * is merged by completing the existing tree by items "leaves" (products, components or nodes + * with no child). + * + * For example, with item representing the tree: + * R (root node) + * - N1 (node) + * -- N11 (node) + * --- P1 (product) + * --- P2 (product) + * - N2 (node) + * + * and the source item representing the tree: + * N1 (root node) + * - N11 (node) + * -- P3 (product) + * - N12 (node) + * + * The leaves of the source item to merge into the item are N1/N11/P3 and N1/N12 => we therefore + * have the following merge result: + * R + * - N1 + * -- N11 + * --- P1 + * --- P2 + * --- P3 (added leaf) + * -- N12 (added leaf) + * + * @param item the source item + * @remarks No control is performed on products or components that are merged into the same tree + * part (two products or components may have the same name) + * @remarks the merge is made by copy (source item is not changed and still exists after the + * operation) + */ + void merge(const DataSourceItem &item); + bool isRoot() const noexcept; QString name() const noexcept; diff --git a/core/include/DataSource/DataSourceItemAction.h b/core/include/DataSource/DataSourceItemAction.h index 72f2a88..9bab4e3 100644 --- a/core/include/DataSource/DataSourceItemAction.h +++ b/core/include/DataSource/DataSourceItemAction.h @@ -35,6 +35,8 @@ public: */ explicit DataSourceItemAction(const QString &name, ExecuteFunction fun); + std::unique_ptr clone() const; + QString name() const noexcept; /// Sets the data source item concerned by the action diff --git a/core/include/DataSource/DataSourceItemMergeHelper.h b/core/include/DataSource/DataSourceItemMergeHelper.h new file mode 100644 index 0000000..722da2c --- /dev/null +++ b/core/include/DataSource/DataSourceItemMergeHelper.h @@ -0,0 +1,15 @@ +#ifndef SCIQLOP_DATASOURCEITEMMERGEHELPER_H +#define SCIQLOP_DATASOURCEITEMMERGEHELPER_H + +class DataSourceItem; + +/** + * @brief The DataSourceItemMergeHelper struct is used to merge two data source items + * @sa DataSourceItem::merge() + */ +struct DataSourceItemMergeHelper { + /// Merges source item into dest item + static void merge(const DataSourceItem &source, DataSourceItem &dest); +}; + +#endif // SCIQLOP_DATASOURCEITEMMERGEHELPER_H diff --git a/core/meson.build b/core/meson.build index 11a1fb6..864624b 100644 --- a/core/meson.build +++ b/core/meson.build @@ -31,6 +31,7 @@ core_sources = [ 'src/DataSource/DataSourceController.cpp', 'src/DataSource/DataSourceItem.cpp', 'src/DataSource/DataSourceItemAction.cpp', + 'src/DataSource/DataSourceItemMergeHelper.cpp', 'src/Network/NetworkController.cpp', 'src/Plugin/PluginManager.cpp', 'src/Settings/SqpSettingsDefs.cpp', diff --git a/core/src/DataSource/DataSourceController.cpp b/core/src/DataSource/DataSourceController.cpp index 81adfb3..a7bc3d2 100644 --- a/core/src/DataSource/DataSourceController.cpp +++ b/core/src/DataSource/DataSourceController.cpp @@ -12,28 +12,6 @@ Q_LOGGING_CATEGORY(LOG_DataSourceController, "DataSourceController") -namespace { - -/** - * Builds the metadata of the variable that will be generated from the loading of an item - * @param dataSourceItem the data source item from which to generate the metadata - * @return the metadata of the variable - */ -QVariantHash variableMetadata(const DataSourceItem &dataSourceItem) -{ - // Variable metadata contains... - - // ... all metadata of the item - auto result = dataSourceItem.data(); - - // ... and the name of the plugin, recovered from root item - result.insert(QStringLiteral("plugin"), dataSourceItem.rootItem().name()); - - return result; -} - -} // namespace - class DataSourceController::DataSourceControllerPrivate { public: QMutex m_WorkingMutex; @@ -131,8 +109,7 @@ void DataSourceController::loadProductItem(const QUuid &dataSourceUid, auto it = impl->m_DataProviders.find(dataSourceUid); auto dataProvider = (it != impl->m_DataProviders.end()) ? it->second : nullptr; - emit variableCreationRequested(productItem.name(), variableMetadata(productItem), - dataProvider); + emit variableCreationRequested(productItem.name(), productItem.data(), dataProvider); } else { qCWarning(LOG_DataSourceController()) << tr("Can't load an item that is not a product"); diff --git a/core/src/DataSource/DataSourceItem.cpp b/core/src/DataSource/DataSourceItem.cpp index 35972b7..39d6599 100644 --- a/core/src/DataSource/DataSourceItem.cpp +++ b/core/src/DataSource/DataSourceItem.cpp @@ -1,9 +1,11 @@ #include #include +#include #include const QString DataSourceItem::NAME_DATA_KEY = QStringLiteral("name"); +const QString DataSourceItem::PLUGIN_DATA_KEY = QStringLiteral("plugin"); struct DataSourceItem::DataSourceItemPrivate { explicit DataSourceItemPrivate(DataSourceItemType type, QVariantHash data) @@ -28,6 +30,23 @@ DataSourceItem::DataSourceItem(DataSourceItemType type, QVariantHash data) { } +std::unique_ptr DataSourceItem::clone() const +{ + auto result = std::make_unique(impl->m_Type, impl->m_Data); + + // Clones children + for (const auto &child : impl->m_Children) { + result->appendChild(std::move(child->clone())); + } + + // Clones actions + for (const auto &action : impl->m_Actions) { + result->addAction(std::move(action->clone())); + } + + return result; +} + QVector DataSourceItem::actions() const noexcept { auto result = QVector{}; @@ -75,6 +94,11 @@ QVariantHash DataSourceItem::data() const noexcept return impl->m_Data; } +void DataSourceItem::merge(const DataSourceItem &item) +{ + DataSourceItemMergeHelper::merge(item, *this); +} + bool DataSourceItem::isRoot() const noexcept { return impl->m_Parent == nullptr; @@ -146,7 +170,7 @@ bool DataSourceItem::operator==(const DataSourceItem &other) if (std::tie(impl->m_Type, impl->m_Data) == std::tie(other.impl->m_Type, other.impl->m_Data)) { // Compares contents of items' children return std::equal(std::cbegin(impl->m_Children), std::cend(impl->m_Children), - std::cbegin(other.impl->m_Children), + std::cbegin(other.impl->m_Children), std::cend(other.impl->m_Children), [](const auto &itemChild, const auto &otherChild) { return *itemChild == *otherChild; }); diff --git a/core/src/DataSource/DataSourceItemAction.cpp b/core/src/DataSource/DataSourceItemAction.cpp index 1677091..10b233a 100644 --- a/core/src/DataSource/DataSourceItemAction.cpp +++ b/core/src/DataSource/DataSourceItemAction.cpp @@ -22,6 +22,11 @@ DataSourceItemAction::DataSourceItemAction(const QString &name, ExecuteFunction { } +std::unique_ptr DataSourceItemAction::clone() const +{ + return std::make_unique(impl->m_Name, impl->m_Fun); +} + QString DataSourceItemAction::name() const noexcept { return impl->m_Name; diff --git a/core/src/DataSource/DataSourceItemMergeHelper.cpp b/core/src/DataSource/DataSourceItemMergeHelper.cpp new file mode 100644 index 0000000..91e60af --- /dev/null +++ b/core/src/DataSource/DataSourceItemMergeHelper.cpp @@ -0,0 +1,55 @@ +#include "DataSource/DataSourceItemMergeHelper.h" + +#include + +namespace { + +/** + * Finds in a tree an item similar to the item passed in parameter + * @param item the item for which to find a similar item + * @param root the root item of the tree + * @return the similar item if found, nullptr otherwise + */ +DataSourceItem *findSimilarItem(const DataSourceItem &item, const DataSourceItem &root) +{ + // An item is considered similar to the another item if: + // - the items are both nodes AND + // - the names of the items are identical + + if (item.type() != DataSourceItemType::NODE) { + return nullptr; + } + + DataSourceItem *result{nullptr}; + bool found{false}; + for (auto i = 0, count = root.childCount(); i < count && !found; ++i) { + auto child = root.child(i); + + found = child->type() == DataSourceItemType::NODE + && QString::compare(child->name(), item.name(), Qt::CaseInsensitive) == 0; + if (found) { + result = child; + } + } + + return result; +} + +} // namespace + +void DataSourceItemMergeHelper::merge(const DataSourceItem &source, DataSourceItem &dest) +{ + // Checks if the source item can be merged into the destination item (i.e. there is a child item + // similar to the source item) + if (auto subItem = findSimilarItem(source, dest)) { + // If there is an item similar to the source item, applies the merge recursively + for (auto i = 0, count = source.childCount(); i < count; ++i) { + merge(*source.child(i), *subItem); + } + } + else { + // If no item is similar to the source item, the item is copied as the child of the + // destination item + dest.appendChild(source.clone()); + } +} diff --git a/core/tests/DataSource/DataSourceItemBuilder.cpp b/core/tests/DataSource/DataSourceItemBuilder.cpp new file mode 100644 index 0000000..c3ff319 --- /dev/null +++ b/core/tests/DataSource/DataSourceItemBuilder.cpp @@ -0,0 +1,73 @@ +#include "DataSourceItemBuilder.h" + +DataSourceItemBuilder &DataSourceItemBuilder::root(const QString &name) +{ + m_Root = std::make_shared(DataSourceItemType::NODE, name); + m_Items.push(m_Root.get()); + return *this; +} + +DataSourceItemBuilder &DataSourceItemBuilder::root(QVariantHash data) +{ + m_Root = std::make_shared(DataSourceItemType::NODE, data); + m_Items.push(m_Root.get()); + return *this; +} + +DataSourceItemBuilder &DataSourceItemBuilder::node(const QString &name) +{ + return append(DataSourceItemType::NODE, name); +} + +DataSourceItemBuilder &DataSourceItemBuilder::node(QVariantHash data) +{ + return append(DataSourceItemType::NODE, std::move(data)); +} + +DataSourceItemBuilder &DataSourceItemBuilder::product(const QString &name) +{ + return append(DataSourceItemType::PRODUCT, name); +} + +DataSourceItemBuilder &DataSourceItemBuilder::product(QVariantHash data) +{ + return append(DataSourceItemType::PRODUCT, std::move(data)); +} + +DataSourceItemBuilder &DataSourceItemBuilder::component(const QString &name) +{ + return append(DataSourceItemType::COMPONENT, name); +} + +DataSourceItemBuilder &DataSourceItemBuilder::component(QVariantHash data) +{ + return append(DataSourceItemType::COMPONENT, std::move(data)); +} + +DataSourceItemBuilder &DataSourceItemBuilder::end() +{ + m_Items.pop(); + return *this; +} + +std::shared_ptr DataSourceItemBuilder::build() +{ + return m_Root; +} + +DataSourceItemBuilder &DataSourceItemBuilder::append(DataSourceItemType type, const QString &name) +{ + append(type, QVariantHash{{DataSourceItem::NAME_DATA_KEY, name}}); + return *this; +} + +DataSourceItemBuilder &DataSourceItemBuilder::append(DataSourceItemType type, QVariantHash data) +{ + auto parentItem = m_Items.top(); + + auto insertIndex = parentItem->childCount(); + parentItem->appendChild(std::make_unique(type, std::move(data))); + + m_Items.push(parentItem->child(insertIndex)); + return *this; +} diff --git a/core/tests/DataSource/DataSourceItemBuilder.h b/core/tests/DataSource/DataSourceItemBuilder.h new file mode 100644 index 0000000..ce675a4 --- /dev/null +++ b/core/tests/DataSource/DataSourceItemBuilder.h @@ -0,0 +1,45 @@ +#ifndef SCIQLOP_DATASOURCEITEMBUILDER_H +#define SCIQLOP_DATASOURCEITEMBUILDER_H + +#include + +#include +#include + +/** + * @brief The DataSourceItemBuilder class aims to facilitate the creation of a DataSourceItem for unit tests + * @sa DataSourceItem + */ +class DataSourceItemBuilder { +public: + /// Inits root item + DataSourceItemBuilder & root(const QString &name); + DataSourceItemBuilder & root(QVariantHash data); + + /// Adds node into the current item + DataSourceItemBuilder & node(const QString &name); + DataSourceItemBuilder & node(QVariantHash data); + + /// Adds product into the current item + DataSourceItemBuilder & product(const QString &name); + DataSourceItemBuilder & product(QVariantHash data); + + /// Adds component into the current item + DataSourceItemBuilder & component(const QString &name); + DataSourceItemBuilder & component(QVariantHash data); + + /// Closes the build of the current item + DataSourceItemBuilder& end(); + + /// Creates the DataSourceItem + std::shared_ptr build(); + +private: + DataSourceItemBuilder& append(DataSourceItemType type, const QString &name); + DataSourceItemBuilder& append(DataSourceItemType type, QVariantHash data); + + std::shared_ptr m_Root{nullptr}; + std::stack m_Items; +}; + +#endif // SCIQLOP_DATASOURCEITEMBUILDER_H diff --git a/core/tests/DataSource/TestDataSourceItem.cpp b/core/tests/DataSource/TestDataSourceItem.cpp new file mode 100644 index 0000000..dd998f0 --- /dev/null +++ b/core/tests/DataSource/TestDataSourceItem.cpp @@ -0,0 +1,207 @@ +#include + +#include "DataSourceItemBuilder.h" + +#include +#include + +#include + +namespace { + +void printItem(std::ostream &out, const DataSourceItem &item, int level = 0) +{ + for (auto i = 0; i < level; ++i) { + out << " "; + } + + out << item.name().toStdString() << "\n"; + + for (auto i = 0, count = item.childCount(); i < count; ++i) { + printItem(out, *item.child(i), level + 1); + } +} + +std::ostream &operator<<(std::ostream &out, const DataSourceItem &item) +{ + printItem(out, item, 0); + return out; +} + +} // namespace + +Q_DECLARE_METATYPE(std::shared_ptr) + +class TestDataSourceItem : public QObject { + Q_OBJECT +private slots: + void testMerge_data(); + void testMerge(); +}; + +void TestDataSourceItem::testMerge_data() +{ + QTest::addColumn >("source"); + QTest::addColumn >("dest"); + QTest::addColumn >("expectedResult"); + + QTest::newRow("merge (basic case)") << DataSourceItemBuilder{} + .root("A2") + .node("- B2") + .product("-- P2") + .end() // P2 + .end() // B2 + .end() // A2 + .build() + << DataSourceItemBuilder{} + .root("A1") + .node("- B1") + .product("-- P1") + .end() // P1 + .end() // B1 + .end() // A1 + .build() + << DataSourceItemBuilder{} + .root("A1") + .node("- B1") + .product("-- P1") + .end() // P1 + .end() // B1 + .node("- B2") + .product("-- P2") + .end() // P2 + .end() // B2 + .end() // A1 + .build(); + + QTest::newRow("merge (some of the source and destination trees are identical)") + << DataSourceItemBuilder{} + .root("A2") + .node("- B1") + .node("-- C1") + .product("--- P2") + .end() // P2 + .end() // C1 + .node("-- C2") + .end() // C2 + .end() // B1 + .end() // A2 + .build() + << DataSourceItemBuilder{} + .root("A1") + .node("- B1") + .node("-- C1") + .product("--- P1") + .end() // P1 + .end() // C1 + .end() // B1 + .end() // A1 + .build() + << DataSourceItemBuilder{} + .root("A1") + .node("- B1") + .node("-- C1") + .product("--- P1") + .end() // P1 + .product("--- P2") + .end() // P2 + .end() // C1 + .node("-- C2") + .end() // C2 + .end() // B1 + .end() // A1 + .build(); + + QTest::newRow("merge (products with the same name and tree are kept)") + << DataSourceItemBuilder{} + .root("A2") + .node("- B1") + .node("-- C1") + .product({{"name", "--- P1"}, {"from", "source"}}) + .end() // P1 + .end() // C1 + .end() // B1 + .end() // A2 + .build() + << DataSourceItemBuilder{} + .root("A1") + .node("- B1") + .node("-- C1") + .product({{"name", "--- P1"}, {"from", "dest"}}) + .end() // P1 + .end() // C1 + .end() // B1 + .end() // A1 + .build() + << DataSourceItemBuilder{} + .root("A1") + .node("- B1") + .node("-- C1") + .product({{"name", "--- P1"}, {"from", "dest"}}) + .end() // P1 (dest) + .product({{"name", "--- P1"}, {"from", "source"}}) + .end() // P1 (source) + .end() // C1 + .end() // B1 + .end() // A1 + .build(); + + QTest::newRow("merge (for same nodes, metadata of dest node are kept)") + << DataSourceItemBuilder{} + .root("A2") + .node("- B1") + .node({{"name", "-- C1"}, {"from", "source"}}) + .product("--- P2") + .end() // P1 + .end() // C1 + .end() // B1 + .end() // A2 + .build() + << DataSourceItemBuilder{} + .root("A1") + .node("- B1") + .node({{"name", "-- C1"}, {"from", "dest"}}) + .product("--- P1") + .end() // P1 + .end() // C1 + .end() // B1 + .end() // A1 + .build() + << DataSourceItemBuilder{} + .root("A1") + .node("- B1") + .node({{"name", "-- C1"}, {"from", "dest"}}) + .product("--- P1") + .end() // P1 + .product("--- P2") + .end() // P2 + .end() // C1 (dest) + .end() // B1 + .end() // A1 + .build(); +} + +void TestDataSourceItem::testMerge() +{ + QFETCH(std::shared_ptr, source); + QFETCH(std::shared_ptr, dest); + QFETCH(std::shared_ptr, expectedResult); + + // Uncomment to print trees + // std::cout << "source: \n" << *source << "\n"; + // std::cout << "dest: \n" << *dest << "\n"; + + // Merges source in dest (not taking source root) + for (auto i = 0, count = source->childCount(); i < count; ++i) { + dest->merge(*source->child(i)); + } + + // Uncomment to print trees + // std::cout << "dest after merge: \n" << *dest << "\n"; + + // Checks merge result + QVERIFY(*dest == *expectedResult); +} + +QTEST_MAIN(TestDataSourceItem) +#include "TestDataSourceItem.moc" diff --git a/core/tests/meson.build b/core/tests/meson.build index 8a23801..ec2a714 100644 --- a/core/tests/meson.build +++ b/core/tests/meson.build @@ -10,6 +10,7 @@ tests = [ [['Data/TestTwoDimArrayData.cpp'],'test_2d','Two Dim Array test'], [['Data/TestDataSeriesUtils.cpp'],'test_dataseries_util','Data series utils test'], [['DataSource/TestDataSourceController.cpp'],'test_data_source','DataSourceController test'], + [['DataSource/TestDataSourceItem.cpp'],'test_data_source_item','DataSourceItem test'], [['Variable/TestVariableCacheController.cpp'],'test_variable_cache','VariableCacheController test'], [['Variable/TestVariable.cpp'],'test_variable','Variable test'], [['Variable/TestVariableSync.cpp'],'test_variable_sync','Variable synchronization test'] @@ -19,7 +20,9 @@ amdatest_sources = [ 'Data/DataSeriesBuilders.h', 'Data/DataSeriesBuilders.cpp', 'Data/DataSeriesTestsUtils.h', - 'Data/DataSeriesTestsUtils.cpp' + 'Data/DataSeriesTestsUtils.cpp', + 'DataSource/DataSourceItemBuilder.h', + 'DataSource/DataSourceItemBuilder.cpp' ] foreach unit_test : tests diff --git a/gui/include/DataSource/DataSourceWidget.h b/gui/include/DataSource/DataSourceWidget.h index 615adfd..388dbd8 100644 --- a/gui/include/DataSource/DataSourceWidget.h +++ b/gui/include/DataSource/DataSourceWidget.h @@ -3,6 +3,8 @@ #include +#include + namespace Ui { class DataSourceWidget; } // Ui @@ -29,7 +31,10 @@ public slots: void addDataSource(DataSourceItem *dataSource) noexcept; private: + void updateTreeWidget() noexcept; + Ui::DataSourceWidget *ui; + std::unique_ptr m_Root; private slots: /// Slot called when the filtering text has changed diff --git a/gui/src/DataSource/DataSourceTreeWidgetItem.cpp b/gui/src/DataSource/DataSourceTreeWidgetItem.cpp index 67bbeb1..d02d738 100644 --- a/gui/src/DataSource/DataSourceTreeWidgetItem.cpp +++ b/gui/src/DataSource/DataSourceTreeWidgetItem.cpp @@ -11,6 +11,41 @@ namespace { // Column indexes const auto NAME_COLUMN = 0; +/** + * Generates the full name of an item. + * + * The full name of an item is its name possibly suffixed by the name of its plugin, in case there + * are items of the same name in its relatives + * @param item the item for which to generate the complete name + * @return the complete name of the item + */ +QString completeName(const DataSourceItem &item) +{ + auto name = item.name(); + + if (item.type() == DataSourceItemType::NODE) { + return name; + } + + auto parentItem = item.parentItem(); + if (!parentItem) { + return name; + } + + // Finds in item's relatives items that have the same name + bool foundSameName = false; + for (auto i = 0, count = parentItem->childCount(); i < count && !foundSameName; ++i) { + auto child = parentItem->child(i); + foundSameName = child != &item + && QString::compare(child->name(), item.name(), Qt::CaseInsensitive) == 0; + } + + // If the name of the item is not unique, it is completed by the plugin suffix + return foundSameName + ? QString{"%1 (%2)"}.arg(name, item.data(DataSourceItem::PLUGIN_DATA_KEY).toString()) + : name; +} + QIcon itemIcon(const DataSourceItem *dataSource) { if (dataSource) { @@ -92,10 +127,15 @@ QString itemTooltip(const DataSourceItem *dataSource) noexcept } // namespace struct DataSourceTreeWidgetItem::DataSourceTreeWidgetItemPrivate { - explicit DataSourceTreeWidgetItemPrivate(const DataSourceItem *data) : m_Data{data} {} + explicit DataSourceTreeWidgetItemPrivate(const DataSourceItem *data) + : m_Data{data}, m_Name{completeName(*m_Data)} + { + } /// Model used to retrieve data source information const DataSourceItem *m_Data; + /// Name displayed + QString m_Name; /// Actions associated to the item. The parent of the item (QTreeWidget) takes the ownership of /// the actions QList m_Actions; @@ -151,7 +191,7 @@ QVariant DataSourceTreeWidgetItem::data(int column, int role) const if (impl->m_Data) { switch (column) { case NAME_COLUMN: - return impl->m_Data->name(); + return impl->m_Name; default: // No action break; diff --git a/gui/src/DataSource/DataSourceWidget.cpp b/gui/src/DataSource/DataSourceWidget.cpp index 454f76b..7934df4 100644 --- a/gui/src/DataSource/DataSourceWidget.cpp +++ b/gui/src/DataSource/DataSourceWidget.cpp @@ -36,7 +36,11 @@ DataSourceTreeWidgetItem *createTreeWidgetItem(DataSourceItem *dataSource) } // namespace -DataSourceWidget::DataSourceWidget(QWidget *parent) : QWidget{parent}, ui{new Ui::DataSourceWidget} +DataSourceWidget::DataSourceWidget(QWidget *parent) + : QWidget{parent}, + ui{new Ui::DataSourceWidget}, + m_Root{ + std::make_unique(DataSourceItemType::NODE, QStringLiteral("Sources"))} { ui->setupUi(this); @@ -51,6 +55,9 @@ DataSourceWidget::DataSourceWidget(QWidget *parent) : QWidget{parent}, ui{new Ui // Connection to filter tree connect(ui->filterLineEdit, &QLineEdit::textChanged, this, &DataSourceWidget::filterChanged); + + // First init + updateTreeWidget(); } DataSourceWidget::~DataSourceWidget() noexcept @@ -60,13 +67,29 @@ DataSourceWidget::~DataSourceWidget() noexcept void DataSourceWidget::addDataSource(DataSourceItem *dataSource) noexcept { - // Creates the item associated to the source and adds it to the tree widget. The tree widget - // takes the ownership of the item + // Merges the data source (without taking its root) if (dataSource) { - ui->treeWidget->addTopLevelItem(createTreeWidgetItem(dataSource)); + for (auto i = 0, count = dataSource->childCount(); i < count; ++i) { + m_Root->merge(*dataSource->child(i)); + } + + updateTreeWidget(); } } +void DataSourceWidget::updateTreeWidget() noexcept +{ + ui->treeWidget->clear(); + + auto rootItem = createTreeWidgetItem(m_Root.get()); + ui->treeWidget->addTopLevelItem(rootItem); + rootItem->setExpanded(true); + + // Sorts tree + ui->treeWidget->setSortingEnabled(true); + ui->treeWidget->sortByColumn(0, Qt::AscendingOrder); +} + void DataSourceWidget::filterChanged(const QString &text) noexcept { auto validateItem = [&text](const DataSourceTreeWidgetItem &item) { diff --git a/plugins/amda/src/AmdaPlugin.cpp b/plugins/amda/src/AmdaPlugin.cpp index 5ce9064..0a033a9 100644 --- a/plugins/amda/src/AmdaPlugin.cpp +++ b/plugins/amda/src/AmdaPlugin.cpp @@ -31,11 +31,14 @@ void associateActions(DataSourceItem &item, const QUuid &dataSourceUid) }; const auto itemType = item.type(); - if (itemType == DataSourceItemType::PRODUCT) { - addLoadAction(QObject::tr("Load %1 product").arg(item.name())); - } - else if (itemType == DataSourceItemType::COMPONENT) { - addLoadAction(QObject::tr("Load %1 component").arg(item.name())); + if (itemType == DataSourceItemType::PRODUCT || itemType == DataSourceItemType::COMPONENT) { + // Adds plugin name to item metadata + item.setData(DataSourceItem::PLUGIN_DATA_KEY, DATA_SOURCE_NAME); + + // Adds load action + auto actionLabel = QObject::tr( + itemType == DataSourceItemType::PRODUCT ? "Load %1 product" : "Load %1 component"); + addLoadAction(actionLabel.arg(item.name())); } auto count = item.childCount(); diff --git a/plugins/mockplugin/src/MockPlugin.cpp b/plugins/mockplugin/src/MockPlugin.cpp index 734200b..1cbf77b 100644 --- a/plugins/mockplugin/src/MockPlugin.cpp +++ b/plugins/mockplugin/src/MockPlugin.cpp @@ -25,6 +25,10 @@ std::unique_ptr createProductItem(const QVariantHash &data, const QUuid &dataSourceUid) { auto result = std::make_unique(DataSourceItemType::PRODUCT, data); + + // Adds plugin name to product metadata + result->setData(DataSourceItem::PLUGIN_DATA_KEY, DATA_SOURCE_NAME); + auto productName = data.value(DataSourceItem::NAME_DATA_KEY).toString(); // Add action to load product from DataSourceController