From 2a4b97ba1a2fb4512ca7968162a9eb49287867b6 Mon Sep 17 00:00:00 2001 From: Rainer Kartmann <rainer.kartmann@kit.edu> Date: Fri, 9 Oct 2020 15:42:43 +0200 Subject: [PATCH] Refactor TreeWidgetBuilder into own file --- .../gui-plugins/ObjectPoseGui/CMakeLists.txt | 7 +- .../ObjectPoseGui/ObjectPoseGuiWidget.ui | 27 +- .../ObjectPoseGuiWidgetController.cpp | 256 +++------------- .../ObjectPoseGui/TreeWidgetBuilder.h | 287 ++++++++++++++++++ 4 files changed, 342 insertions(+), 235 deletions(-) create mode 100644 source/RobotAPI/gui-plugins/ObjectPoseGui/TreeWidgetBuilder.h diff --git a/source/RobotAPI/gui-plugins/ObjectPoseGui/CMakeLists.txt b/source/RobotAPI/gui-plugins/ObjectPoseGui/CMakeLists.txt index d4d29bd3e..e588dbbb7 100644 --- a/source/RobotAPI/gui-plugins/ObjectPoseGui/CMakeLists.txt +++ b/source/RobotAPI/gui-plugins/ObjectPoseGui/CMakeLists.txt @@ -8,12 +8,15 @@ armarx_set_target("ObjectPoseGuiPlugin") armarx_build_if(ArmarXGui_FOUND "ArmarXGui not available") set(SOURCES - ObjectPoseGuiPlugin.cpp ObjectPoseGuiWidgetController.cpp + ObjectPoseGuiPlugin.cpp + ObjectPoseGuiWidgetController.cpp ) # do not rename this variable, it is used in armarx_gui_library()... set(HEADERS - ObjectPoseGuiPlugin.h ObjectPoseGuiWidgetController.h + ObjectPoseGuiPlugin.h + ObjectPoseGuiWidgetController.h + TreeWidgetBuilder.h ) set(GUI_MOC_HDRS ${HEADERS}) diff --git a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidget.ui b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidget.ui index bc8167582..00677428b 100644 --- a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidget.ui +++ b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidget.ui @@ -143,9 +143,6 @@ <property name="columnCount"> <number>3</number> </property> - <attribute name="headerCascadingSectionResizes"> - <bool>true</bool> - </attribute> <attribute name="headerStretchLastSection"> <bool>true</bool> </attribute> @@ -166,7 +163,7 @@ </column> <item> <property name="text"> - <string>Dataset2</string> + <string>Dataset1</string> </property> <property name="text"> <string/> @@ -176,10 +173,10 @@ </property> <item> <property name="text"> - <string>ClassName3</string> + <string>ClassName1</string> </property> <property name="text"> - <string>Provider1</string> + <string>Provider2</string> </property> <property name="text"> <string/> @@ -187,16 +184,19 @@ </item> <item> <property name="text"> - <string>ClassName3</string> + <string>ClassName2</string> </property> <property name="text"> - <string>Provider2</string> + <string>Provider1</string> + </property> + <property name="text"> + <string/> </property> </item> </item> <item> <property name="text"> - <string>Dataset1</string> + <string>Dataset2</string> </property> <property name="text"> <string/> @@ -206,7 +206,7 @@ </property> <item> <property name="text"> - <string>ClassName2</string> + <string>ClassName3</string> </property> <property name="text"> <string>Provider1</string> @@ -217,14 +217,11 @@ </item> <item> <property name="text"> - <string>ClassName1</string> + <string>ClassName3</string> </property> <property name="text"> <string>Provider2</string> </property> - <property name="text"> - <string/> - </property> </item> </item> </widget> @@ -258,7 +255,7 @@ <double>0.250000000000000</double> </property> <property name="value"> - <double>5.000000000000000</double> + <double>30.000000000000000</double> </property> </widget> </item> diff --git a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp index 5742df60a..495ad1972 100644 --- a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp +++ b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp @@ -31,6 +31,8 @@ #include <RobotAPI/components/ObjectPoseObserver/ice_conversions.h> #include <RobotAPI/components/ObjectPoseObserver/ObjectPose.h> +#include "TreeWidgetBuilder.h" + namespace armarx { @@ -144,194 +146,6 @@ namespace armarx } - template <class ContainerT> - struct TreeVisitor - { - using ElementT = typename ContainerT::value_type; - - /// Return < 0 if `element < item`, 0 if `element == item`, and > 0 if `element > item`. - using CompareFn = std::function<int(const ElementT& element, QTreeWidgetItem* item)>; - using NameFn = std::function<std::string(const ElementT& element)>; - using MakeItemFn = std::function<QTreeWidgetItem*(const ElementT& element)>; - using UpdateItemFn = std::function<bool(const ElementT& element, QTreeWidgetItem* item)>; - - TreeVisitor() = default; - TreeVisitor(const ContainerT&) - {} - - static bool NoUpdate(const ElementT& element, QTreeWidgetItem* item) - { - (void) element, (void) item; - return true; - } - - void enableSort(QTreeWidget* tree) - { - tree->sortItems(0, Qt::SortOrder::AscendingOrder); - tree->isSortingEnabled(); - } - - template <typename ParentT> - void updateTree(ParentT* parent, const ContainerT& elements, - CompareFn compareFn, MakeItemFn makeItemFn, UpdateItemFn updateItemFn = NoUpdate) - { - int currentIndex = 0; - for (const auto& element : elements) - { - QTreeWidgetItem* item = nullptr; - if (currentIndex >= getItemCount(parent)) - { - // Add elements to the end of the list. - item = makeItemFn(element); - insertItem(parent, getItemCount(parent), item); - ++currentIndex; - } - else - { - QTreeWidgetItem* currentItem = getItem(parent, currentIndex); - while (currentItem != nullptr && compareFn(element, currentItem) > 0) - { - delete takeItem(parent, currentIndex); - currentItem = getItem(parent, currentIndex); - } - if (currentItem == nullptr || compareFn(element, currentItem) < 0) - { - // Insert new item before child. - item = makeItemFn(element); - insertItem(parent, currentIndex, item); - ++currentIndex; - } - else if (currentItem != nullptr && compareFn(element, currentItem) == 0) - { - // Already existing. - item = currentItem; - ++currentIndex; - } - } - ARMARX_CHECK_NOT_NULL(item); - if (!updateItemFn(element, item)) - { - break; - } - } - } - - template <typename ParentT> - void updateTree(ParentT* parent, const ContainerT& elements, - NameFn nameFn, MakeItemFn makeItemFn, UpdateItemFn updateItemFn = NoUpdate) - { - auto compare = [nameFn](const ElementT & element, QTreeWidgetItem * item) - { - std::string name = nameFn(element); - return name.compare(item->text(0).toStdString()); - }; - updateTree(parent, elements, compare, makeItemFn, updateItemFn); - } - - private: - - int getItemCount(QTreeWidget* tree) - { - return tree->topLevelItemCount(); - } - QTreeWidgetItem* getItem(QTreeWidget* tree, int index) - { - return tree->topLevelItem(index); - } - void insertItem(QTreeWidget* tree, int index, QTreeWidgetItem* item) - { - tree->insertTopLevelItem(index, item); - } - QTreeWidgetItem* takeItem(QTreeWidget* tree, int index) - { - return tree->takeTopLevelItem(index); - } - - int getItemCount(QTreeWidgetItem* parent) - { - return parent->childCount(); - } - QTreeWidgetItem* getItem(QTreeWidgetItem* parent, int index) - { - return parent->child(index); - } - QTreeWidgetItem* takeItem(QTreeWidgetItem* parent, int index) - { - return parent->takeChild(index); - } - void insertItem(QTreeWidgetItem* parent, int index, QTreeWidgetItem* item) - { - parent->insertChild(index, item); - } - }; - - template <class KeyT, class ValueT> - struct MapTreeVisitor : private TreeVisitor<std::map<KeyT, ValueT>> - { - using MapT = std::map<KeyT, ValueT>; - using Base = TreeVisitor<MapT>; - using ElementT = typename Base::ElementT; - - using NameFn = std::function<std::string(const KeyT& key, const ValueT& value)>; - - using MakeItemFn = std::function<QTreeWidgetItem*(const KeyT& key, const ValueT& value)>; - using UpdateItemFn = std::function<bool(const KeyT& key, const ValueT& value, QTreeWidgetItem* item)>; - - MapTreeVisitor() = default; - MapTreeVisitor(const MapT&) - {} - - static std::string KeyIsName(const KeyT& key, const ValueT& value) - { - (void) value; - if constexpr(std::is_same<KeyT, std::string>()) - { - return key; - } - else - { - std::stringstream ss; - ss << key; - return ss.str(); - } - } - - static bool NoUpdate(const KeyT& key, const ValueT& value, QTreeWidgetItem* item) - { - (void) key, (void) value, (void) item; - return true; - } - - void updateTree(QTreeWidget* tree, const MapT& elements, - MakeItemFn makeItemFn, UpdateItemFn updateItemFn = NoUpdate) - { - updateTree(tree, elements, KeyIsName, makeItemFn, updateItemFn); - } - - template <class ParentT> - void updateTree(ParentT* parent, const MapT& elements, - NameFn nameFn, MakeItemFn makeItemFn, UpdateItemFn updateItemFn = NoUpdate) - { - auto name = [nameFn](const ElementT & element) - { - const auto& [key, value] = element; - return nameFn(key, value); - }; - auto makeItem = [makeItemFn](const ElementT & element) - { - const auto& [key, value] = element; - return makeItemFn(key, value); - }; - auto updateItem = [updateItemFn](const ElementT & element, QTreeWidgetItem * item) - { - const auto& [key, value] = element; - return updateItemFn(key, value, item); - }; - - Base::updateTree(parent, elements, name, makeItem, updateItem); - } - }; - void ObjectPoseGuiWidgetController::updateObjectsTab() { @@ -358,25 +172,28 @@ namespace armarx start = IceUtil::Time::now(); QTreeWidget* tree = widget.objectsTree; - MapTreeVisitor visitor(objectPosesByProvider); - auto makeProviderItem = [](const std::string & provider, const objpose::ObjectPoseSeq&) + + MapTreeWidgetBuilder builder(objectPosesByProvider); + builder.setMakeItemFn([](const std::string & provider, const objpose::ObjectPoseSeq&) { QTreeWidgetItem* item = new QTreeWidgetItem({QString::fromStdString(provider)}); - item->setExpanded(true); return item; - }; - auto updateItem = [](const std::string&, const objpose::ObjectPoseSeq & objectPoses, QTreeWidgetItem * item) + }); + builder.setUpdateItemFn([](const std::string&, const objpose::ObjectPoseSeq & objectPoses, QTreeWidgetItem * item) { - auto name = [](const objpose::ObjectPose & pose) + bool expand = item->childCount() == 0; + + TreeWidgetBuilder builder(objectPoses); + builder.setNameFn([](const objpose::ObjectPose & pose) { return pose.objectID.str(); - }; - auto makeItem = [](const objpose::ObjectPose&) + }); + builder.setMakeItemFn([](const objpose::ObjectPose&) { QTreeWidgetItem* item = new QTreeWidgetItem(QStringList{}); return item; - }; - auto updateItem = [](const objpose::ObjectPose & pose, QTreeWidgetItem * item) + }); + builder.setUpdateItemFn([](const objpose::ObjectPose & pose, QTreeWidgetItem * item) { int col = 0; item->setText(col++, QString::fromStdString(pose.objectID.str())); @@ -398,13 +215,17 @@ namespace armarx item->setText(col++, QString::fromStdString(TimeUtil::toStringDateTime(pose.timestamp))); return true; - }; - TreeVisitor visitor(objectPoses); - visitor.updateTree(item, objectPoses, name, makeItem, updateItem); + }); + builder.updateTree(item, objectPoses); + + if (expand) + { + item->setExpanded(true); + } return true; - }; - visitor.updateTree(tree, objectPosesByProvider, makeProviderItem, updateItem); + }); + builder.updateTree(tree, objectPosesByProvider); ARMARX_INFO << "Gui update took " << (IceUtil::Time::now() - start).toMilliSecondsDouble() << " ms."; } @@ -438,17 +259,18 @@ namespace armarx QTreeWidget* tree = widget.requestTree; - MapTreeVisitor visitor(data); - auto makeDatasetItem = [](const std::string & dataset, const auto&) + MapTreeWidgetBuilder builder(data); + builder.setMakeItemFn([](const std::string & dataset, const auto&) { QTreeWidgetItem* item = new QTreeWidgetItem({QString::fromStdString(dataset)}); return item; - }; - auto updateItem = [tree](const std::string & dataset, const auto & datasetData, QTreeWidgetItem * datasetItem) + }); + builder.setUpdateItemFn([tree](const std::string & dataset, const auto & datasetData, QTreeWidgetItem * datasetItem) { (void) dataset; - auto compareFn = [](const std::pair<std::string, std::string>& lhs, QTreeWidgetItem * item) + TreeWidgetBuilder builder(datasetData); + builder.setCompareFn([](const std::pair<std::string, std::string>& lhs, QTreeWidgetItem * item) { auto rhs = std::make_pair(item->text(0).toStdString(), item->text(1).toStdString()); if (lhs < rhs) @@ -456,13 +278,13 @@ namespace armarx return -1; } return lhs == rhs ? 0 : 1; - }; - auto makeItemFn = [](const std::pair<std::string, std::string>& element) + }); + builder.setMakeItemFn([](const std::pair<std::string, std::string>& element) { QTreeWidgetItem* item = new QTreeWidgetItem({ QString::fromStdString(element.first), QString::fromStdString(element.second)}); return item; - }; - auto updateItemFn = [tree](const std::pair<std::string, std::string>& element, QTreeWidgetItem * item) + }); + builder.setUpdateItemFn([tree](const std::pair<std::string, std::string>& element, QTreeWidgetItem * item) { (void) element; if (!tree->itemWidget(item, 2)) @@ -471,14 +293,12 @@ namespace armarx tree->setItemWidget(item, 2, requestCheckBox); } return true; - }; - - TreeVisitor visitor(datasetData); - visitor.updateTree(datasetItem, datasetData, compareFn, makeItemFn, updateItemFn); + }); + builder.updateTree(datasetItem, datasetData); return true; - }; - visitor.updateTree(tree, data, makeDatasetItem, updateItem); + }); + builder.updateTree(tree, data); ARMARX_INFO << "Gui update took " << (IceUtil::Time::now() - start).toMilliSecondsDouble() << " ms."; } diff --git a/source/RobotAPI/gui-plugins/ObjectPoseGui/TreeWidgetBuilder.h b/source/RobotAPI/gui-plugins/ObjectPoseGui/TreeWidgetBuilder.h new file mode 100644 index 000000000..2b7cdf0d8 --- /dev/null +++ b/source/RobotAPI/gui-plugins/ObjectPoseGui/TreeWidgetBuilder.h @@ -0,0 +1,287 @@ +#pragma once + +#include <functional> +#include <map> +#include <sstream> + +#include <QTreeWidget> + +#include <ArmarXCore/core/exceptions/local/ExpressionException.h> + + +namespace armarx +{ + + /** + * A class to efficiently build and maintain sorted items of `QTreeWidget` + * or `QTreeWidgetItem` based on a sorted container matching the intended structure. + */ + template <class ContainerT> + struct TreeWidgetBuilder + { + using ElementT = typename ContainerT::value_type; + + /// Return < 0 if `element < item`, 0 if `element == item`, and > 0 if `element > item`. + using CompareFn = std::function<int(const ElementT& element, QTreeWidgetItem* item)>; + using NameFn = std::function<std::string(const ElementT& element)>; + using MakeItemFn = std::function<QTreeWidgetItem*(const ElementT& element)>; + using UpdateItemFn = std::function<bool(const ElementT& element, QTreeWidgetItem* item)>; + + + TreeWidgetBuilder() = default; + TreeWidgetBuilder(const ContainerT&) + {} + + TreeWidgetBuilder(CompareFn compareFn, MakeItemFn makeItemFn, UpdateItemFn updateItemFn = NoUpdate) : + compareFn(compareFn), makeItemFn(makeItemFn), updateItemFn(updateItemFn) + {} + TreeWidgetBuilder(NameFn nameFn, MakeItemFn makeItemFn, UpdateItemFn updateItemFn = NoUpdate) : + compareFn(MakeCompareNameFn(nameFn)), makeItemFn(makeItemFn), updateItemFn(updateItemFn) + {} + + + void setCompareFn(CompareFn compareFn) + { + this->compareFn = compareFn; + } + void setNameFn(NameFn nameFn) + { + compareFn = MakeCompareNameFn(nameFn); + } + void setMakeItemFn(MakeItemFn makeItemFn) + { + this->makeItemFn = makeItemFn; + } + void setUpdateItemFn(UpdateItemFn updateItemFn) + { + this->updateItemFn = updateItemFn; + } + + + template <class ParentT> + void updateTree(ParentT* parent, const ContainerT& elements); + + + /// No update function (default). + static bool NoUpdate(const ElementT& element, QTreeWidgetItem* item) + { + (void) element, (void) item; + return true; + } + + /// Use the name for comparison. + static CompareFn MakeCompareNameFn(NameFn nameFn) + { + return [nameFn](const ElementT & element, QTreeWidgetItem * item) + { + std::string name = nameFn(element); + return name.compare(item->text(0).toStdString()); + }; + } + + + private: + + CompareFn compareFn; + MakeItemFn makeItemFn; + UpdateItemFn updateItemFn; + + }; + + + /** + * A class to efficiently build and maintain sorted items of `QTreeWidget` + * or `QTreeWidgetItem` based on a map matching the intended structure. + */ + template <class KeyT, class ValueT> + struct MapTreeWidgetBuilder + { + using MapT = std::map<KeyT, ValueT>; + using Base = TreeWidgetBuilder<MapT>; + using ElementT = typename Base::ElementT; + + using NameFn = std::function<std::string(const KeyT& key, const ValueT& value)>; + + using MakeItemFn = std::function<QTreeWidgetItem*(const KeyT& key, const ValueT& value)>; + using UpdateItemFn = std::function<bool(const KeyT& key, const ValueT& value, QTreeWidgetItem* item)>; + + + MapTreeWidgetBuilder() + { + setNameFn(KeyAsName); + } + /// Allows declaring instance from container without explicit template arguments. + MapTreeWidgetBuilder(const MapT&) : MapTreeWidgetBuilder() + {} + + MapTreeWidgetBuilder(MakeItemFn makeItemFn, UpdateItemFn updateItemFn = NoUpdate) : MapTreeWidgetBuilder() + { + setMakeItemFn(makeItemFn); + setUpdateItemFn(updateItemFn); + } + MapTreeWidgetBuilder(NameFn nameFn, MakeItemFn makeItemFn, UpdateItemFn updateItemFn = NoUpdate) + { + setNameFn(nameFn); + setMakeItemFn(makeItemFn); + setUpdateItemFn(updateItemFn); + } + + void setNameFn(NameFn nameFn) + { + builder.setNameFn([nameFn](const ElementT & element) + { + const auto& [key, value] = element; + return nameFn(key, value); + }); + } + void setMakeItemFn(MakeItemFn makeItemFn) + { + builder.setMakeItemFn([makeItemFn](const ElementT & element) + { + const auto& [key, value] = element; + return makeItemFn(key, value); + }); + } + void setUpdateItemFn(UpdateItemFn updateItemFn) + { + builder.setUpdateItemFn([updateItemFn](const ElementT & element, QTreeWidgetItem * item) + { + const auto& [key, value] = element; + return updateItemFn(key, value, item); + }); + } + + + template <class ParentT> + void updateTree(ParentT* tree, const MapT& elements) + { + builder.updateTree(tree, elements); + } + + + /// A name function using the key as name. + static std::string KeyAsName(const KeyT& key, const ValueT& value) + { + (void) value; + if constexpr(std::is_same<KeyT, std::string>()) + { + return key; + } + else + { + std::stringstream ss; + ss << key; + return ss.str(); + } + } + + /// No update function (default). + static bool NoUpdate(const KeyT& key, const ValueT& value, QTreeWidgetItem* item) + { + (void) key, (void) value, (void) item; + return true; + } + + + private: + + TreeWidgetBuilder<MapT> builder; + + }; + + + + namespace detail + { + template <class ParentT> struct ParentAPI; + + template <> struct ParentAPI<QTreeWidget> + { + static int getItemCount(QTreeWidget* tree) + { + return tree->topLevelItemCount(); + } + static QTreeWidgetItem* getItem(QTreeWidget* tree, int index) + { + return tree->topLevelItem(index); + } + static void insertItem(QTreeWidget* tree, int index, QTreeWidgetItem* item) + { + tree->insertTopLevelItem(index, item); + } + static QTreeWidgetItem* takeItem(QTreeWidget* tree, int index) + { + return tree->takeTopLevelItem(index); + } + }; + + template <> struct ParentAPI<QTreeWidgetItem> + { + static int getItemCount(QTreeWidgetItem* parent) + { + return parent->childCount(); + } + static QTreeWidgetItem* getItem(QTreeWidgetItem* parent, int index) + { + return parent->child(index); + } + static QTreeWidgetItem* takeItem(QTreeWidgetItem* parent, int index) + { + return parent->takeChild(index); + } + static void insertItem(QTreeWidgetItem* parent, int index, QTreeWidgetItem* item) + { + parent->insertChild(index, item); + } + }; + } + + + template <class ContainerT> + template <typename ParentT> + void TreeWidgetBuilder<ContainerT>::updateTree(ParentT* parent, const ContainerT& elements) + { + using api = detail::ParentAPI<ParentT>; + + int currentIndex = 0; + for (const auto& element : elements) + { + QTreeWidgetItem* item = nullptr; + if (currentIndex >= api::getItemCount(parent)) + { + // Add elements to the end of the list. + item = makeItemFn(element); + api::insertItem(parent, api::getItemCount(parent), item); + ++currentIndex; + } + else + { + QTreeWidgetItem* currentItem = api::getItem(parent, currentIndex); + while (currentItem != nullptr && compareFn(element, currentItem) > 0) + { + delete api::takeItem(parent, currentIndex); + currentItem = api::getItem(parent, currentIndex); + } + if (currentItem == nullptr || compareFn(element, currentItem) < 0) + { + // Insert new item before child. + item = makeItemFn(element); + api::insertItem(parent, currentIndex, item); + ++currentIndex; + } + else if (currentItem != nullptr && compareFn(element, currentItem) == 0) + { + // Already existing. + item = currentItem; + ++currentIndex; + } + } + ARMARX_CHECK_NOT_NULL(item); + if (!updateItemFn(element, item)) + { + break; + } + } + } + +} -- GitLab