diff --git a/source/RobotAPI/gui-plugins/ArMemMemoryViewer/ArMemMemoryViewerWidgetController.cpp b/source/RobotAPI/gui-plugins/ArMemMemoryViewer/ArMemMemoryViewerWidgetController.cpp index ff461269fd3c68bf57c6c69d77116c62dc807d5c..8d7c5a48cb0bb387f21b4a9495bbc73f3e9f1c9b 100644 --- a/source/RobotAPI/gui-plugins/ArMemMemoryViewer/ArMemMemoryViewerWidgetController.cpp +++ b/source/RobotAPI/gui-plugins/ArMemMemoryViewer/ArMemMemoryViewerWidgetController.cpp @@ -22,44 +22,42 @@ #include "ArMemMemoryViewerWidgetController.h" -#include <RobotAPI/libraries/armem_gui/gui_utils.h> - #include <string> +#include <RobotAPI/libraries/armem_gui/gui_utils.h> namespace armarx { - QString ArMemMemoryViewerWidgetController::GetWidgetName() + QString + ArMemMemoryViewerWidgetController::GetWidgetName() { return "ArMem.MemoryViewer"; } - - QIcon ArMemMemoryViewerWidgetController::GetWidgetIcon() + QIcon + ArMemMemoryViewerWidgetController::GetWidgetIcon() { return QIcon(":icons/memory-128.png"); } - ArMemMemoryViewerWidgetController::ArMemMemoryViewerWidgetController() { widget.setupUi(getWidget()); viewer = std::make_unique<MemoryViewer>( - widget.updateWidgetLayout, + widget.updateWidgetLayout, - widget.memoryGroupBox, - widget.treesLayout, + widget.memoryGroupBox, + widget.treesLayout, - widget.instanceGroupBox, - widget.treesLayout, + widget.instanceGroupBox, + widget.treesLayout, - widget.diskControlWidgetLayout, + widget.diskControlWidgetLayout, - widget.statusLabel - ); + widget.statusLabel); viewer->setLogTag("ArMem Memory Viewer"); armarx::gui::useSplitter(widget.treesLayout); @@ -71,33 +69,38 @@ namespace armarx { } - - void ArMemMemoryViewerWidgetController::onInitComponent() + void + ArMemMemoryViewerWidgetController::onInitComponent() { emit lifecycleServer.initialized(this); } - void ArMemMemoryViewerWidgetController::onConnectComponent() + void + ArMemMemoryViewerWidgetController::onConnectComponent() { emit lifecycleServer.connected(this); } - void ArMemMemoryViewerWidgetController::onDisconnectComponent() + void + ArMemMemoryViewerWidgetController::onDisconnectComponent() { emit lifecycleServer.disconnected(this); } - - void ArMemMemoryViewerWidgetController::loadSettings(QSettings* settings) + void + ArMemMemoryViewerWidgetController::loadSettings(QSettings* settings) { viewer->loadSettings(settings); } - void ArMemMemoryViewerWidgetController::saveSettings(QSettings* settings) + + void + ArMemMemoryViewerWidgetController::saveSettings(QSettings* settings) { viewer->saveSettings(settings); } - QPointer<QDialog> ArMemMemoryViewerWidgetController::getConfigDialog(QWidget* parent) + QPointer<QDialog> + ArMemMemoryViewerWidgetController::getConfigDialog(QWidget* parent) { if (!configDialog) { @@ -107,11 +110,12 @@ namespace armarx return qobject_cast<QDialog*>(configDialog); } - void ArMemMemoryViewerWidgetController::configured() + void + ArMemMemoryViewerWidgetController::configured() { if (configDialog) { viewer->readConfigDialog(configDialog.data()); } } -} +} // namespace armarx diff --git a/source/RobotAPI/gui-plugins/SkillManagerPlugin/CMakeLists.txt b/source/RobotAPI/gui-plugins/SkillManagerPlugin/CMakeLists.txt index 5df70b65810beeead4599151d25b15960a26e615..fae8ace92bdcf18f9e15018a3d38bf8ec025c141 100644 --- a/source/RobotAPI/gui-plugins/SkillManagerPlugin/CMakeLists.txt +++ b/source/RobotAPI/gui-plugins/SkillManagerPlugin/CMakeLists.txt @@ -28,6 +28,7 @@ set(SOURCES ColorPalettes.cpp SkillManagerMonitorWidgetController.cpp PeriodicUpdateWidget.cpp + #SkillExecutionTreeWidget.cpp ) set(HEADERS @@ -55,6 +56,7 @@ set(HEADERS ColorPalettes.h SkillManagerMonitorWidgetController.h PeriodicUpdateWidget.h + #SkillExecutionTreeWidget.h ) set(GUI_UIS @@ -70,6 +72,7 @@ set(COMPONENT_LIBS SkillsMemory aronjsonconverter SimpleConfigDialog + skills_gui ) if(ArmarXGui_FOUND) diff --git a/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidget.ui b/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidget.ui index 82569fa85833ef3eb89476da914b7ba02606864b..88daea56ca801bdcd88785d54cb748c97b5e8ebc 100644 --- a/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidget.ui +++ b/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidget.ui @@ -19,7 +19,7 @@ <property name="windowTitle"> <string>SkillManagerMonitorWidget</string> </property> - <layout class="QGridLayout" name="gridLayout_3" rowstretch="0,0,0,0"> + <layout class="QGridLayout" name="gridLayout_3" rowstretch="0,0,0,0,0,0"> <item row="3" column="0" colspan="2"> <widget class="QSplitter" name="splitter_2"> <property name="enabled"> @@ -97,44 +97,7 @@ <property name="title"> <string>Manager</string> </property> - <layout class="QGridLayout" name="gridLayout"> - <item row="5" column="0" colspan="3"> - <widget class="QTreeWidget" name="treeWidgetSkills"> - <column> - <property name="text"> - <string>Skill</string> - </property> - </column> - <column> - <property name="text"> - <string>HasInputType</string> - </property> - </column> - <column> - <property name="text"> - <string>HasOutputType</string> - </property> - </column> - </widget> - </item> - <item row="4" column="1"> - <widget class="QPushButton" name="pushButtonSearch"> - <property name="text"> - <string>Search</string> - </property> - </widget> - </item> - <item row="4" column="0"> - <widget class="QLineEdit" name="lineEditSearch"> - <property name="text"> - <string/> - </property> - <property name="placeholderText"> - <string>Search...</string> - </property> - </widget> - </item> - </layout> + <layout class="QGridLayout" name="gridLayout"/> </widget> <widget class="QGroupBox" name="groupBoxSkillDetails"> <property name="enabled"> @@ -149,140 +112,7 @@ <property name="title"> <string>Skill Details</string> </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="QSplitter" name="splitter_2"> - <property name="enabled"> - <bool>true</bool> - </property> - <property name="sizePolicy"> - <sizepolicy hsizetype="Preferred" vsizetype="Preferred"> - <horstretch>0</horstretch> - <verstretch>0</verstretch> - </sizepolicy> - </property> - <property name="orientation"> - <enum>Qt::Vertical</enum> - </property> - <property name="childrenCollapsible"> - <bool>false</bool> - </property> - <widget class="QWidget" name="groupBoxSkillDetailsTop" native="true"> - <layout class="QVBoxLayout" name="verticalLayout_2"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <layout class="QGridLayout" name="gridLayout_5"> - <item row="0" column="0"> - <widget class="QPushButton" name="pushButtonPaste"> - <property name="text"> - <string>Set args from clipboard</string> - </property> - </widget> - </item> - <item row="0" column="3"> - <widget class="QPushButton" name="pushButtonReset"> - <property name="text"> - <string>Reset args to profile</string> - </property> - </widget> - </item> - <item row="1" column="0" colspan="4"> - <widget class="QComboBox" name="comboBoxProfiles"> - <item> - <property name="text"> - <string><No Profile selected. Using root></string> - </property> - </item> - </widget> - </item> - <item row="0" column="1"> - <widget class="QPushButton" name="pushButtonCopy"> - <property name="text"> - <string>Copy args to clipboard</string> - </property> - </widget> - </item> - <item row="0" column="2"> - <widget class="QLabel" name="label_2"> - <property name="text"> - <string/> - </property> - </widget> - </item> - </layout> - </item> - <item> - <widget class="QWidget" name="skillDescription" native="true"/> - </item> - </layout> - </widget> - <widget class="QWidget" name="groupBoxSkillDetailsBottom" native="true"> - <layout class="QVBoxLayout" name="verticalLayout_3"> - <property name="leftMargin"> - <number>0</number> - </property> - <property name="topMargin"> - <number>0</number> - </property> - <property name="rightMargin"> - <number>0</number> - </property> - <property name="bottomMargin"> - <number>0</number> - </property> - <item> - <widget class="QTreeWidget" name="treeWidgetSkillDetails"> - <property name="contextMenuPolicy"> - <enum>Qt::CustomContextMenu</enum> - </property> - <property name="editTriggers"> - <set>QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed</set> - </property> - <column> - <property name="text"> - <string>Key</string> - </property> - </column> - <column> - <property name="text"> - <string>Value</string> - </property> - </column> - <column> - <property name="text"> - <string>Type</string> - </property> - </column> - <column> - <property name="text"> - <string>defaultValue (hidden in GUI)</string> - </property> - </column> - </widget> - </item> - <item> - <widget class="QPushButton" name="pushButtonExecuteSkill"> - <property name="text"> - <string>Request Execution</string> - </property> - </widget> - </item> - </layout> - </widget> - </widget> - </item> - </layout> + <layout class="QVBoxLayout" name="verticalLayout"/> </widget> </widget> </widget> @@ -331,6 +161,13 @@ </item> </layout> </item> + <item row="5" column="0" colspan="2"> + <widget class="QLabel" name="connectionStatusLabel"> + <property name="text"> + <string>(hidden in GUI)</string> + </property> + </widget> + </item> </layout> </widget> <resources/> diff --git a/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.cpp b/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.cpp index bbeaa5a4d9a889808db465419f732e5903031a31..245ea08034c8c5aa912447fce57fdbe7d7efb58f 100644 --- a/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.cpp +++ b/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.cpp @@ -22,6 +22,7 @@ #include "SkillManagerMonitorWidgetController.h" +#include <memory> #include <optional> #include <regex> #include <string> @@ -31,7 +32,6 @@ #include <RobotAPI/libraries/skills/core/Skill.h> #include "aronTreeWidget/visitors/AronTreeWidgetConverter.h" -#include "aronTreeWidget/visitors/AronTreeWidgetCreator.h" #include "aronTreeWidget/visitors/AronTreeWidgetModalCreator.h" // modals @@ -51,7 +51,6 @@ #include "aronTreeWidget/Data.h" #include "aronTreeWidget/modal/text/AronTreeWidgetTextInputModalController.h" -#include "aronTreeWidget/visitors/AronTreeWidgetConverter.h" #include "aronTreeWidget/visitors/AronTreeWidgetCreator.h" #include "aronTreeWidget/visitors/AronTreeWidgetModalCreator.h" #include "aronTreeWidget/widgets/SkillDescriptionWidget.h" @@ -59,8 +58,6 @@ //configSk namespace armarx { - const skills::SkillID SkillManagerMonitorWidgetController::SelectedSkill::UNK_SKILL_ID = - skills::SkillID{.providerId = ::std::nullopt, .skillName = skills::SkillID::UNKNOWN}; QPointer<QDialog> SkillManagerMonitorWidgetController::getConfigDialog(QWidget* parent) @@ -68,7 +65,7 @@ namespace armarx if (!dialog) { dialog = new SimpleConfigDialog(parent); - dialog->addProxyFinder<skills::dti::SkillMemoryInterfacePrx>( + dialog->addProxyFinder<skills::manager::dti::SkillManagerInterfacePrx>( "SkillMemory", "", "SkillMem*"); } return qobject_cast<SimpleConfigDialog*>(dialog); @@ -96,728 +93,81 @@ namespace armarx // Others namespace armarx { - void - SkillManagerMonitorWidgetController::prepareAndRunMenu(const QPoint& pos) - { - QMenu* menu = new QMenu(); - - // Stop skill - QAction* stopSkillAction = new QAction("Stop execution", this); - skills::SkillStatus currentStatus = - skillStatusUpdates.at(selectedSkill.skillExecutionId).status; - stopSkillAction->setDisabled(currentStatus == skills::SkillStatus::Aborted || - currentStatus == skills::SkillStatus::Failed || - currentStatus == skills::SkillStatus::Succeeded); - - QAction* rerunSkillAction = new QAction("Re-run with similar params", this); - menu->addAction(stopSkillAction); - menu->addAction(rerunSkillAction); - connect(stopSkillAction, - &QAction::triggered, - this, - &SkillManagerMonitorWidgetController::stopSkill); - connect(rerunSkillAction, - &QAction::triggered, - this, - &SkillManagerMonitorWidgetController::rerunSkillWithSimilarParams); - - // Temporarily disable rerun-skill-Action - rerunSkillAction->setDisabled(true); - - // open menu - menu->popup(widget.treeWidgetSkillExecutions->viewport()->mapToGlobal(pos)); - } - - SkillExecutionInfoTreeWidgetItem* - SkillExecutionInfoTreeWidgetItem::SearchRecursiveForMatch( - SkillExecutionInfoTreeWidgetItem* haystack, - const skills::SkillExecutionID& needle) - { - if (!haystack) - { - return nullptr; - } - - if (needle == haystack->executionId) - { - return haystack; - } - for (int i = 0; i < haystack->childCount(); ++i) - { - auto el = static_cast<SkillExecutionInfoTreeWidgetItem*>(haystack->child(i)); - return SkillExecutionInfoTreeWidgetItem::SearchRecursiveForMatch(el, needle); - } - return nullptr; - } - SkillManagerMonitorWidgetController::SkillManagerMonitorWidgetController() { - widget.setupUi(getWidget()); - this->updateWidget = new PeriodicUpdateWidget(2.0, 60); - widget.updateWidgetLayout->insertWidget(0, updateWidget); - - this->stopAllButton = new QPushButton("Stop all executions"); - widget.stopAllLayout->addWidget(stopAllButton); - - - skillDescriptionWidget = new SkillDescriptionWidget(); - widget.skillDescription->parentWidget()->layout()->replaceWidget(widget.skillDescription, - skillDescriptionWidget); - widget.skillDescription = skillDescriptionWidget; - - - connect(this->stopAllButton, - &QPushButton::clicked, - this, - &SkillManagerMonitorWidgetController::stopAllExecutions); - connect(widget.treeWidgetSkillExecutions, - &QTreeWidget::customContextMenuRequested, - this, - &SkillManagerMonitorWidgetController::prepareAndRunMenu); - - connect(updateWidget, - &armarx::PeriodicUpdateWidget::update, - this, - &SkillManagerMonitorWidgetController::refreshSkillsAndExecutions); - - connect(widget.pushButtonCopy, - &QPushButton::clicked, - this, - &SkillManagerMonitorWidgetController::copyCurrentConfig); - connect(widget.pushButtonPaste, - &QPushButton::clicked, - this, - &SkillManagerMonitorWidgetController::pasteCurrentConfig); - - connect(widget.pushButtonExecuteSkill, - &QPushButton::clicked, - this, - &SkillManagerMonitorWidgetController::executeSelectedSkill); - - connect(widget.treeWidgetSkills, - &QTreeWidget::currentItemChanged, - this, - &SkillManagerMonitorWidgetController::skillSelectionChanged); - - connect(widget.treeWidgetSkillExecutions, - &QTreeWidget::currentItemChanged, - this, - &SkillManagerMonitorWidgetController::skillExecutionSelectionChanged); - - /*connect(widget.pushButtonRefreshNow, - &QPushButton::clicked, - this, - &SkillManagerMonitorWidgetController::refreshSkills);*/ - connect(widget.pushButtonSearch, - &QPushButton::clicked, - this, - &SkillManagerMonitorWidgetController::searchSkills); - // alternatively run search when pressing enter - connect(widget.lineEditSearch, - &QLineEdit::returnPressed, - this, - &SkillManagerMonitorWidgetController::searchSkills); - } - - SkillManagerMonitorWidgetController::~SkillManagerMonitorWidgetController() - { - } - - void - SkillManagerMonitorWidgetController::onInitComponent() - { - usingProxy(observerName); - } - - void - SkillManagerMonitorWidgetController::onConnectComponent() - { - widget.groupBoxSkills->setTitle(QString::fromStdString(observerName)); - widget.treeWidgetSkillDetails->setEditTriggers( - QAbstractItemView::EditTrigger::NoEditTriggers); - widget.treeWidgetSkillDetails->setColumnHidden(3, true); - - getProxy(memory, observerName, 1000); - this->updateWidget->startTimerIfEnabled(); - connected = true; - } - - void - SkillManagerMonitorWidgetController::onDisconnectComponent() - { - connected = false; - memory = nullptr; - - // reset all - skills.clear(); - widget.treeWidgetSkills->clear(); - widget.treeWidgetSkillDetails->clear(); - skillsArgumentsTreeWidgetItem = nullptr; - selectedSkill.skillId.providerId->providerName = ""; - selectedSkill.skillId.skillName = ""; - this->updateWidget->stopTimer(); - } - void - SkillManagerMonitorWidgetController::searchSkills() - { - this->currentSkillSearch = widget.lineEditSearch->text(); - } - - void - SkillManagerMonitorWidgetController::refreshSkillsAndExecutions() - { - refreshSkills(); - refreshExecutions(); - } - - void - SkillManagerMonitorWidgetController::matchSkillUpdateToSearch( - std::map<skills::manager::dto::SkillID, skills::manager::dto::SkillDescription>& update) - { - /* - if (this->currentSkillSearch.isEmpty()) - { - return; - } - - // TODO: whitespace to wildcard - - std::string search = this->currentSkillSearch.toLower().toStdString(); - std::transform(search.begin(), search.end(), search.begin(), - [](unsigned char c){ return std::tolower(c); }); - - for (auto it = update.begin(); it != update.end();) - { - if (simox::alg::to_lower(skills::SkillID::FromIce(it->first).skillName) - .find(this->currentSkillSearch.toLower().toStdString())) - { - it = update.erase(it); - } - else - { - ++it; - } - } -*/ - } - - void - SkillManagerMonitorWidgetController::refreshSkills() - { - if (!memory) - { - // check if null - return; - } - - /* CHECK OWN SKILLS LIST */ - // remove non-existing ones - try - { - std::scoped_lock l(updateMutex); - - auto managerSkills = memory->getSkillDescriptions(); - this->matchSkillUpdateToSearch(managerSkills); - - // completely recreate internal skills map - skills.clear(); - for (const auto& [sid, desc] : managerSkills) - { - auto description = skills::SkillDescription::FromIce(desc); - auto skillId = skills::SkillID::FromIce(sid); - auto providerId = skillId.providerId.value_or( - skills::ProviderID{.providerName = "UNKNOWN PROVIDER NAME"}); - - ARMARX_CHECK(skillId.isFullySpecified()); - - auto& providedSkillsMap = skills[providerId]; // create new if not existent - providedSkillsMap.insert({skillId, description}); - } - - // update tree view. Remove non-existing elements - int i = 0; - while (i < widget.treeWidgetSkills->topLevelItemCount()) - { - auto* providerItem = widget.treeWidgetSkills->topLevelItem(i); - auto providerName = providerItem->text(0).toStdString(); - skills::ProviderID providerId{.providerName = providerName}; - - if (skills.find(providerId) == skills.end()) - { - providerItem = nullptr; // reset - auto remove = widget.treeWidgetSkills->takeTopLevelItem(i); - delete remove; - continue; - } - - ++i; - auto& providedSkills = skills.at(providerId); - - int j = 0; - while (j < providerItem->childCount()) - { - auto* skillItem = providerItem->child(j); - auto skillName = skillItem->text(0).toStdString(); - - skills::SkillID skillId{.providerId = - skills::ProviderID{.providerName = providerName}, - .skillName = skillName}; - - if (providedSkills.find(skillId) == providedSkills.end()) - { - skillItem = nullptr; - auto remove = providerItem->takeChild(j); - delete remove; - continue; - } - - ++j; - } - } - - // update tree view. Add new elements - for (const auto& [providerId, providedSkills] : skills) - { - QTreeWidgetItem* providerItem = nullptr; - for (int i = 0; i < widget.treeWidgetSkills->topLevelItemCount(); ++i) - { - auto el = widget.treeWidgetSkills->topLevelItem(i); - auto providerName = el->text(0).toStdString(); - skills::ProviderID elProviderId{.providerName = providerName}; - - if (providerId == elProviderId) - { - providerItem = el; - break; - } - } - - if (!providerItem) - { - providerItem = new QTreeWidgetItem(widget.treeWidgetSkills); - providerItem->setText(0, QString::fromStdString(providerId.providerName)); - } - - for (const auto& [skillId, skill] : providedSkills) - { - QTreeWidgetItem* skillItem = nullptr; - for (int i = 0; i < providerItem->childCount(); ++i) - { - auto el = providerItem->child(i); - auto skillName = el->text(0).toStdString(); - skills::SkillID elSkillId{providerId, skillName}; - - if (skillId == elSkillId) - { - skillItem = el; - break; - } - } - - if (!skillItem) - { - skillItem = new SkillInfoTreeWidgetItem(skill, providerItem); - skillItem->setText(0, QString::fromStdString(skillId.skillName)); - } - } - } - } - catch (...) - { - // perhaps the manager died during the method? - } - } - - void - SkillManagerMonitorWidgetController::refreshExecutions() - { - static std::map<skills::SkillStatus, std::string> ExecutionStatus2String = { - // Main states - {skills::SkillStatus::Constructing, "Constructing"}, - {skills::SkillStatus::Initializing, "Initializing"}, - {skills::SkillStatus::Preparing, "Preparing"}, - {skills::SkillStatus::Running, "Running"}, - - // Terminating - {skills::SkillStatus::Aborted, "Aborted"}, - {skills::SkillStatus::Failed, "Failed"}, - {skills::SkillStatus::Succeeded, "Succeeded"}}; - - if (!memory) - { - // check if null - return; - } - - try - { - std::scoped_lock l(updateMutex); - - auto currentManagerStatuses = memory->getSkillExecutionStatuses(); - - for (const auto& [k, v] : currentManagerStatuses) - { - auto executionId = skills::SkillExecutionID::FromIce(k); - auto statusUpdate = skills::SkillStatusUpdate::FromIce(v); - - // update maps - skillStatusUpdates[executionId] = statusUpdate; - //skillExecutionParams.insert_or_assign(executionId, - // statusUpdate.usedParameterization); - - SkillExecutionInfoTreeWidgetItem* found = nullptr; - for (int i = 0; i < widget.treeWidgetSkillExecutions->topLevelItemCount(); ++i) - { - auto c = static_cast<SkillExecutionInfoTreeWidgetItem*>( - widget.treeWidgetSkillExecutions->topLevelItem(i)); - - found = - SkillExecutionInfoTreeWidgetItem::SearchRecursiveForMatch(c, executionId); - - if (found) - { - for (std::pair<skills::SkillStatus, std::string> i : ExecutionStatus2String) - { - if (i.first == statusUpdate.status) - { - found->setText(3, QString::fromStdString(i.second)); - } - } - - break; - } - } - - if (!found) - { - // TODO: Sort to executor! - auto item = new SkillExecutionInfoTreeWidgetItem(executionId); - - item->setText(0, - QString::fromStdString( - executionId.executionStartedTime.toDateTimeString())); - item->setText(1, QString::fromStdString(executionId.executorName)); - item->setText(2, QString::fromStdString(executionId.skillId.toString())); - for (std::pair<skills::SkillStatus, std::string> i : ExecutionStatus2String) - { - if (i.first == statusUpdate.status) - { - item->setText(3, QString::fromStdString(i.second)); - } - } - - widget.treeWidgetSkillExecutions->insertTopLevelItem(0, item); - } - } - } - catch (...) - { - // perhaps the manager died during the method? - } - } - - void - SkillManagerMonitorWidgetController::executeSelectedSkill() - { - auto data = getConfigAsAron(); - executeSkillWithParams(selectedSkill.skillId, data); - } - - void - SkillManagerMonitorWidgetController::rerunSkillWithSimilarParams() - { - // TODO: disabled until skillparameterization is replaced - //skills::SkillParameterization selectedExecutionParams = - //skillExecutionParams.at(selectedSkill.skillExecutionId); - //executeSkillWithParams(selectedSkill.skillExecutionId.skillId, - // selectedExecutionParams.parameterization); - - executeSelectedSkill(); - } - - void - SkillManagerMonitorWidgetController::executeSkillWithParams(skills::SkillID skillId, - aron::data::DictPtr params) - { - std::scoped_lock l(updateMutex); - - auto providerId = skillId.providerId; - if (!providerId.has_value()) - { - return; - } - const auto& skillDescriptions = skills.at(providerId.value()); - if (skillDescriptions.find(skillId) == skillDescriptions.end()) - { - return; - } - - char hostname[HOST_NAME_MAX]; - gethostname(hostname, HOST_NAME_MAX); - - skills::SkillExecutionRequest req{ - .skillId = selectedSkill.skillId, - .executorName = "Skills.Manager GUI (hostname: " + std::string(hostname) + ")", - .parameters = params}; - - ARMARX_CHECK(selectedSkill.skillId.isFullySpecified()); // sanity check - ARMARX_IMPORTANT << "Executing skill from GUI: " << selectedSkill.skillId << "."; - // Note that we execute the skill in a seperate thread so that the GUI thread does not freeze. - memory->begin_executeSkill(req.toManagerIce()); - } - - void - SkillManagerMonitorWidgetController::stopAllExecutions() - { - QTreeWidget const* tree = widget.treeWidgetSkillExecutions; + widget.setupUi(getWidget()); - int const max_retries = 3; - int left_retries = max_retries; - bool retry = false; + // setup memory + this->mem_wrapper = std::make_shared<skills::gui::SkillManagerWrapper>(); - do - { - retry = false; - - for (ssize_t i = 0; i < tree->topLevelItemCount(); ++i) - { - SkillExecutionInfoTreeWidgetItem const* item = - dynamic_cast<SkillExecutionInfoTreeWidgetItem*>( - widget.treeWidgetSkillExecutions->topLevelItem(i)); - - if (not item) - { - // At this point, dynamic-casting failed and we don't know anything about the - // type. - ARMARX_ERROR << "Cannot stop unknown skill."; - retry = true; - continue; - } - - try - { - ARMARX_INFO << "Aborting skill '" << item->executionId.skillId.skillName - << "'..."; - memory->abortSkillAsync(item->executionId.toManagerIce()); - } - catch (Ice::Exception const& e) - { - retry = true; - ARMARX_ERROR << "Unhandeled Ice exception while aborting skill '" - << item->executionId.skillId.skillName << "'."; - } - catch (...) - { - retry = true; - ARMARX_ERROR << "Unhandled error while aborting skill '" - << item->executionId.skillId.skillName << "'."; - } - } - - if (retry) - { - left_retries -= 1; - - if (left_retries > 0) - { - ARMARX_WARNING << "There where errors aborting skills. Retrying..."; - } - else - { - ARMARX_ERROR << "Couldn't abort all skills after " << max_retries - << " tries. Giving up."; - retry = false; - } - } - } while (retry); - } + viewer = std::make_unique<armarx::skills::gui::SkillMemoryGUI>( + widget.treeWidgetSkillExecutions, + widget.groupBoxSkillExecutions->layout(), - void - SkillManagerMonitorWidgetController::stopSkill() - { - std::scoped_lock l(updateMutex); + widget.groupBoxSkills, + widget.splitter, - /* - if (selectedSkill.skillExecutionId.skillId.fullySpecified()) - { - ARMARX_INFO << "The user requested to stop a skill, which was not fully specified!"; - return; - } + widget.groupBoxSkillDetails, + widget.splitter, - const auto& skillDescriptions = skills.at(*selectedSkill.skillId.providerId); - if (!skillDescriptions.count(selectedSkill.skillId.skillName)) - { - return; - } -*/ + widget.updateWidgetLayout, - ARMARX_INFO << "Stopping skill from GUI: " << selectedSkill.skillExecutionId.toString(); + widget.stopAllLayout, - memory->abortSkillAsync(selectedSkill.skillExecutionId.toManagerIce()); - } + widget.connectionStatusLabel, - void - SkillManagerMonitorWidgetController::skillExecutionSelectionChanged(QTreeWidgetItem* current, - QTreeWidgetItem*) - { - std::scoped_lock l(updateMutex); - widget.groupBoxSkillExecutions->setEnabled(false); - - if (!current) - { - // gui has died? - return; - } + this->mem_wrapper); - auto c = static_cast<SkillExecutionInfoTreeWidgetItem*>(current); - selectedSkill.skillExecutionId = c->executionId; - widget.groupBoxSkillExecutions->setEnabled(true); + connectSignals(); } void - SkillManagerMonitorWidgetController::skillSelectionChanged(QTreeWidgetItem* current, - QTreeWidgetItem*) + SkillManagerMonitorWidgetController::connectSignals() { - std::scoped_lock l(updateMutex); - widget.groupBoxSkillDetails->setEnabled(false); - - if (!current) - { - // gui has died? - return; - } - - if (!current->parent()) - { - // no parent available. Perhaps provider clicked? Reset selected skill. - selectedSkill.skillId = SelectedSkill::UNK_SKILL_ID; - return; - } - - auto c = static_cast<SkillInfoTreeWidgetItem*>(current); - auto skillDescription = c->skillDescription; - - - if (selectedSkill.skillId == skillDescription.skillId) - { - // no change - return; - } - - selectedSkill.skillId = skillDescription.skillId; - - // setup groupBox - widget.groupBoxSkillDetails->setTitle( - QString::fromStdString(selectedSkill.skillId.toString())); - widget.groupBoxSkillDetails->setEnabled(true); - - // setup table view - widget.treeWidgetSkillDetails->clear(); - aronTreeWidgetController = nullptr; - skillsArgumentsTreeWidgetItem = nullptr; - - // We assert that the skill exists - ARMARX_CHECK(skills.count(*selectedSkill.skillId.providerId) > 0); - ARMARX_CHECK(skills.at(*selectedSkill.skillId.providerId).count(selectedSkill.skillId) > 0); - auto skillDesc = skills.at(*selectedSkill.skillId.providerId).at(selectedSkill.skillId); - - { - auto it = new QTreeWidgetItem(widget.treeWidgetSkillDetails, - {QString::fromStdString("Name"), - QString::fromStdString(skillDesc.skillId.skillName)}); - widget.treeWidgetSkillDetails->addTopLevelItem(it); - } - - { - auto it = new QTreeWidgetItem(widget.treeWidgetSkillDetails, - {QString::fromStdString("Description"), - QString::fromStdString(skillDesc.description)}); - widget.treeWidgetSkillDetails->addTopLevelItem(it); - } - - { - auto it = new QTreeWidgetItem( - widget.treeWidgetSkillDetails, - {QString::fromStdString("Timeout"), - QString::fromStdString(std::to_string(skillDesc.timeout.toMilliSeconds())) + - " ms"}); - widget.treeWidgetSkillDetails->addTopLevelItem(it); - } - - // select root profile - widget.comboBoxProfiles->setCurrentIndex(0); - - // remove any profile - while (widget.comboBoxProfiles->count() > 1) - { - widget.comboBoxProfiles->removeItem(1); - } - - // add new profiles for this skill - // TODO: Where stored? + // disconnect + connect(this, + &SkillManagerMonitorWidgetController::disconnectGui, + this->mem_wrapper.get(), + &skills::gui::SkillManagerWrapper::disconnectMemory); + connect(this, + &SkillManagerMonitorWidgetController::disconnectGui, + this->viewer.get(), + &skills::gui::SkillMemoryGUI::disconnectGui); - skillsArgumentsTreeWidgetItem = new QTreeWidgetItem(widget.treeWidgetSkillDetails, - {QString::fromStdString("Arguments")}); - auto aron_args = skillDesc.parametersType; - auto default_args_of_profile = skillDesc.rootProfileDefaults; - - aronTreeWidgetController = - std::make_shared<AronTreeWidgetController>(widget.treeWidgetSkillDetails, - skillsArgumentsTreeWidgetItem, - aron_args, - default_args_of_profile); - - // automatically expand args - skillsArgumentsTreeWidgetItem->setExpanded(true); + // connect + connect(this, + &SkillManagerMonitorWidgetController::connectGui, + viewer.get(), + &skills::gui::SkillMemoryGUI::connectGui); } - aron::data::DictPtr - SkillManagerMonitorWidgetController::getConfigAsAron() const + SkillManagerMonitorWidgetController::~SkillManagerMonitorWidgetController() { - // create argument aron (if there is an accepted type set) - if (aronTreeWidgetController) - { - return aronTreeWidgetController->convertToAron(); - } - return nullptr; } void - SkillManagerMonitorWidgetController::copyCurrentConfig() + SkillManagerMonitorWidgetController::onInitComponent() { - auto data = getConfigAsAron(); - if (!data) - { - return; - } - - auto json = aron::data::converter::AronNlohmannJSONConverter::ConvertToNlohmannJSON(data); - QClipboard* clipboard = QApplication::clipboard(); - clipboard->setText(QString::fromStdString(json.dump(2))); + usingProxy(observerName); } void - SkillManagerMonitorWidgetController::pasteCurrentConfig() + SkillManagerMonitorWidgetController::onConnectComponent() { - QClipboard* clipboard = QApplication::clipboard(); - std::string s = clipboard->text().toStdString(); - nlohmann::json json = nlohmann::json::parse(s); - auto data = - aron::data::converter::AronNlohmannJSONConverter::ConvertFromNlohmannJSONObject(json); - - if (!aronTreeWidgetController) - { - return; - } - aronTreeWidgetController->setFromAron(data); + getProxy(this->memory, observerName, 1000); + connected = true; + mem_wrapper->connectMemory(this->memory); + emit connectGui(observerName); } void - SkillManagerMonitorWidgetController::resetCurrentConfig() + SkillManagerMonitorWidgetController::onDisconnectComponent() { - // TODO + connected = false; + memory = nullptr; + emit disconnectGui(); } } // namespace armarx diff --git a/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.h b/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.h index 786f13d869aee2c6c80d7de9058e840ef0305db1..ca80a304b536833542ad35957423e6dbdc029267 100644 --- a/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.h +++ b/source/RobotAPI/gui-plugins/SkillManagerPlugin/SkillManagerMonitorWidgetController.h @@ -33,7 +33,6 @@ #include <ArmarXGui/libraries/ArmarXGuiBase/ArmarXGuiPlugin.h> #include <ArmarXGui/libraries/SimpleConfigDialog/SimpleConfigDialog.h> -#include "RobotAPI/gui-plugins/SkillManagerPlugin/PeriodicUpdateWidget.h" #include <RobotAPI/gui-plugins/SkillManagerPlugin/ui_SkillManagerMonitorWidget.h> #include <RobotAPI/interface/skills/SkillMemoryInterface.h> #include <RobotAPI/libraries/aron/core/data/variant/All.h> @@ -42,57 +41,10 @@ #include <RobotAPI/libraries/skills/core/ProviderID.h> #include <RobotAPI/libraries/skills/core/SkillDescription.h> #include <RobotAPI/libraries/skills/core/SkillStatusUpdate.h> - -#include "aronTreeWidget/AronTreeWidgetController.h" +#include <RobotAPI/libraries/skills_gui/SkillMemoryGui.h> namespace armarx { - class SkillDescriptionWidget; - - class SkillInfoTreeWidgetItem : public QTreeWidgetItem - { - public: - SkillInfoTreeWidgetItem(const skills::SkillDescription& desc, QTreeWidgetItem* parent) : - QTreeWidgetItem(parent), skillDescription(desc) - { - } - - SkillInfoTreeWidgetItem(const skills::SkillDescription& desc, QTreeWidget* parent) : - QTreeWidgetItem(parent), skillDescription(desc) - { - } - - skills::SkillDescription skillDescription; - }; - - class SkillExecutionInfoTreeWidgetItem : public QTreeWidgetItem - { - //Q_OBJECT - public: - SkillExecutionInfoTreeWidgetItem(const skills::SkillExecutionID& id, - QTreeWidgetItem* parent) : - QTreeWidgetItem(parent), executionId(id) - { - } - - // After constructing an item, it must be manually inserted into the tree! - SkillExecutionInfoTreeWidgetItem(const skills::SkillExecutionID& id) : executionId(id) - { - } - - // When using this constructor, the new item will be appended to the bottom of the tree! - SkillExecutionInfoTreeWidgetItem(const skills::SkillExecutionID& id, QTreeWidget* parent) : - QTreeWidgetItem(parent), executionId(id) - { - } - - static SkillExecutionInfoTreeWidgetItem* - SearchRecursiveForMatch(SkillExecutionInfoTreeWidgetItem* el, - const skills::SkillExecutionID& needle); - - skills::SkillExecutionID executionId; - }; - class ARMARXCOMPONENT_IMPORT_EXPORT SkillManagerMonitorWidgetController : public armarx::ArmarXComponentWidgetControllerTemplate<SkillManagerMonitorWidgetController> { @@ -123,84 +75,20 @@ namespace armarx void onConnectComponent() override; void onDisconnectComponent(); - private slots: - void skillSelectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); - void skillExecutionSelectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); - - void stopAllExecutions(); - void stopSkill(); // TODO: rename - void executeSelectedSkill(); - - void refreshSkills(); - void refreshExecutions(); - void refreshSkillsAndExecutions(); - - void copyCurrentConfig(); - void pasteCurrentConfig(); - void resetCurrentConfig(); - - void prepareAndRunMenu(const QPoint& pos); - void rerunSkillWithSimilarParams(); - - void searchSkills(); - - - private: - aron::data::DictPtr getConfigAsAron() const; + signals: + void disconnectGui(); + void connectGui(std::string observerName); private: - /** - * Widget Form - */ + armarx::skills::manager::dti::SkillManagerInterfacePrx memory; Ui::SkillManagerMonitorWidget widget; + std::unique_ptr<armarx::skills::gui::SkillMemoryGUI> viewer = nullptr; QPointer<SimpleConfigDialog> dialog; - QString currentSkillSearch; std::string observerName = "SkillManager"; - skills::dti::SkillMemoryInterfacePrx memory = nullptr; - - // Data taken from observer (snapshot of it) - mutable std::mutex updateMutex; - std::map<skills::ProviderID, std::map<skills::SkillID, skills::SkillDescription>> skills = - {}; - std::map<skills::SkillExecutionID, skills::SkillStatusUpdate> skillStatusUpdates = {}; - - // store copies (!) of skill descriptions - //std::map<skills::SkillExecutionID, skills::SkillParameterization> skillExecutionParams = {}; - - // User Input - struct SelectedSkill - { - static const skills::SkillID UNK_SKILL_ID; - - skills::SkillID skillId; - skills::SkillExecutionID skillExecutionId; - - // make default constructable - SelectedSkill() : - skillId(UNK_SKILL_ID), - skillExecutionId{.skillId = UNK_SKILL_ID, - .executorName = skills::SkillExecutionID::UNKNOWN, - .executionStartedTime = armarx::core::time::DateTime::Invalid()} - { - } - } - - selectedSkill; - - void executeSkillWithParams(skills::SkillID skillId, aron::data::DictPtr params); - void matchSkillUpdateToSearch(std::map<skills::manager::dto::SkillID, - skills::manager::dto::SkillDescription>& update); - - - PeriodicUpdateWidget* updateWidget = - nullptr; // TODO: this should not be held by the controller! - QPushButton* stopAllButton = nullptr; // also this. Oh well... + std::shared_ptr<skills::gui::SkillManagerWrapper> mem_wrapper = nullptr; - // Helper to get the treeWidgetItem easily - QTreeWidgetItem* skillsArgumentsTreeWidgetItem = nullptr; - AronTreeWidgetControllerPtr aronTreeWidgetController = nullptr; - SkillDescriptionWidget* skillDescriptionWidget = nullptr; + void connectSignals(); // connected flag std::atomic_bool connected = false; diff --git a/source/RobotAPI/libraries/CMakeLists.txt b/source/RobotAPI/libraries/CMakeLists.txt index 4d481f8619df7851446f95712d8dce766513c943..aab6379138035bbabb26599742ec74372779fd66 100644 --- a/source/RobotAPI/libraries/CMakeLists.txt +++ b/source/RobotAPI/libraries/CMakeLists.txt @@ -41,3 +41,5 @@ add_subdirectory(GraspingUtility) add_subdirectory(obstacle_avoidance) add_subdirectory(robot_name_service) + +add_subdirectory(skills_gui) diff --git a/source/RobotAPI/libraries/skills/core/ProviderID.cpp b/source/RobotAPI/libraries/skills/core/ProviderID.cpp index 53d6aad6807a7002a80a097d94b1e1fb5ec9f864..c8d6708f1ce7157256b0eacd0889c836f1dc9137 100644 --- a/source/RobotAPI/libraries/skills/core/ProviderID.cpp +++ b/source/RobotAPI/libraries/skills/core/ProviderID.cpp @@ -1,7 +1,5 @@ #include "ProviderID.h" -#include "SkillID.h" - namespace armarx { namespace skills diff --git a/source/RobotAPI/libraries/skills/core/SkillDescription.h b/source/RobotAPI/libraries/skills/core/SkillDescription.h index 4bc8a3cd8fe92f7ebdf13c32b1e812be8596fbbd..784afd9321f4d677109c1f43672f0bdff4846db8 100644 --- a/source/RobotAPI/libraries/skills/core/SkillDescription.h +++ b/source/RobotAPI/libraries/skills/core/SkillDescription.h @@ -30,6 +30,16 @@ namespace armarx static SkillDescription FromIce(const provider::dto::SkillDescription& i, const std::optional<ProviderID>& = std::nullopt); static SkillDescription FromIce(const manager::dto::SkillDescription& i); + + bool + operator==(const SkillDescription& other) const + { + return this->skillId == other.skillId && this->description == other.description && + this->rootProfileDefaults == other.rootProfileDefaults && + this->timeout == other.timeout && + this->parametersType == other.parametersType && + this->resultType == other.resultType; + } }; template <class T> diff --git a/source/RobotAPI/libraries/skills/core/SkillExecutionID.h b/source/RobotAPI/libraries/skills/core/SkillExecutionID.h index 53fc20a1013f5924c77159580899ae824bd1ad86..39f3bc008324d046486d4eb1cd486ef87a1860f8 100644 --- a/source/RobotAPI/libraries/skills/core/SkillExecutionID.h +++ b/source/RobotAPI/libraries/skills/core/SkillExecutionID.h @@ -36,13 +36,13 @@ namespace armarx bool operator<(const SkillExecutionID& other) const { - return this->toString() < other.toString(); + return this->executionStartedTime < other.executionStartedTime; } bool operator<=(const SkillExecutionID& other) const { - return this->toString() <= other.toString(); + return this->executionStartedTime <= other.executionStartedTime; } skills::manager::dto::SkillExecutionID toManagerIce() const; diff --git a/source/RobotAPI/libraries/skills_gui/CMakeLists.txt b/source/RobotAPI/libraries/skills_gui/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..a1e53c1a2d96b3a6e50d4720efbd9be38d6760de --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/CMakeLists.txt @@ -0,0 +1,121 @@ +set(LIB_NAME skills_gui) + +armarx_component_set_name("${LIB_NAME}") +armarx_set_target("Library: ${LIB_NAME}") + +set(LIBRARIES + ArmarXCore + ArmarXCoreInterfaces + ArmarXGuiBase + RobotAPISkillsCore + RobotAPISkillsManager + RobotAPIInterfaces + aron +) + +set(COMPONENT_LIBS + RobotAPIInterfaces + aron + RobotAPISkills + RobotAPISkillsManager + RobotAPISkillsMemory + SkillsMemory + aronjsonconverter + SimpleConfigDialog +) + +set(SOURCES + aron_tree_widget/visitors/AronTreeWidgetCreator.cpp + aron_tree_widget/visitors/AronTreeWidgetConverter.cpp + aron_tree_widget/visitors/AronTreeWidgetSetter.cpp + aron_tree_widget/visitors/AronTreeWidgetModalCreator.cpp + aron_tree_widget/visitors/AronTreeWidgetContextMenu.cpp + aron_tree_widget/widgets/CustomWidget.cpp + aron_tree_widget/widgets/EditMatrixWidget.cpp + aron_tree_widget/widgets/IntEnumWidget.cpp + aron_tree_widget/widgets/QuaternionWidget.cpp + aron_tree_widget/widgets/SkillDescriptionWidget.cpp + aron_tree_widget/Data.cpp + aron_tree_widget/ListDictHelper.cpp + aron_tree_widget/AronTreeWidgetItem.cpp + aron_tree_widget/AronTreeWidgetController.cpp + aron_tree_widget/modal/text/AronTreeWidgetTextInputModalController.cpp + aron_tree_widget/modal/dict/AronTreeWidgetDictInputModalController.cpp + aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModalController.cpp + aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModalController.cpp + aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModalController.cpp + aron_tree_widget/modal/AronTreeWidgetModal.cpp + aron_tree_widget/ColorPalettes.cpp + + executions/SkillExecutionTreeWidget.cpp + executions/SkillExecutionTreeWidgetItem.cpp + + memory/SkillManagerWrapper.cpp + memory/MemoryCommunicatorBase.cpp + + skills/SkillTreeWidget.cpp + skills/SkillGroupBox.cpp + skills/SkillTreeWidgetItem.cpp + + skill_details/SkillDetailsGroupBox.cpp + skill_details/ProfileMenuWidget.cpp + skill_details/SkillDetailsTreeWidget.cpp + + SkillMemoryGui.cpp + gui_utils.cpp + StatusLabel.cpp +) +set(HEADERS + aron_tree_widget/visitors/AronTreeWidgetCreator.h + aron_tree_widget/visitors/AronTreeWidgetConverter.h + aron_tree_widget/visitors/AronTreeWidgetSetter.h + aron_tree_widget/visitors/AronTreeWidgetModalCreator.h + aron_tree_widget/visitors/AronTreeWidgetContextMenu.h + aron_tree_widget/widgets/NDArrayHelper.h + aron_tree_widget/widgets/EditMatrixWidget.h + aron_tree_widget/widgets/CustomWidget.h + aron_tree_widget/widgets/IntEnumWidget.h + aron_tree_widget/widgets/QuaternionWidget.h + aron_tree_widget/widgets/SkillDescriptionWidget.h + aron_tree_widget/Data.h + aron_tree_widget/ListDictHelper.h + aron_tree_widget/AronTreeWidgetItem.h + aron_tree_widget/AronTreeWidgetController.h + aron_tree_widget/modal/AronTreeWidgetModal.h + aron_tree_widget/modal/text/AronTreeWidgetTextInputModalController.h + aron_tree_widget/modal/dict/AronTreeWidgetDictInputModalController.h + aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModalController.h + aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModalController.h + aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModalController.h + aron_tree_widget/ColorPalettes.h + + executions/SkillExecutionTreeWidget.h + executions/SkillExecutionTreeWidgetItem.h + + memory/SkillManagerWrapper.h + memory/MemoryCommunicatorBase.h + + skills/SkillTreeWidget.h + skills/SkillGroupBox.h + skills/SkillTreeWidgetItem + + skill_details/SkillDetailsGroupBox.h + skill_details/ProfileMenuWidget.h + skill_details/SkillDetailsTreeWidget.h + + SkillMemoryGui.h + gui_utils.h + StatusLabel.h +) + +armarx_gui_library("${LIB_NAME}" "${SOURCES}" "${GUI_MOC_HDRS}" "${GUI_UIS}" "" "${LIBRARIES}") + +set(GUI_UIS + aronTreeWidget/modal/text/AronTreeWidgetTextInputModal.ui +) + +add_library(${PROJECT_NAME}::skills_gui ALIAS skills_gui) + + +# add unit tests +#add_subdirectory(test) diff --git a/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.cpp b/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4d74cf11e2b5c208a30eb553c986a9636dabd562 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.cpp @@ -0,0 +1,137 @@ +#include "SkillMemoryGui.h" + +#include "gui_utils.h" + +namespace armarx::skills::gui +{ + SkillMemoryGUI::SkillMemoryGUI(QTreeWidget* _skillExecutionTreeWidget, + QLayout* _skillExecutionTreeWidgetParentLayout, + QGroupBox* _skillGroupBox, + QSplitter* _skillGroupBoxParentLayout, + QGroupBox* _skillDetailGroupBox, + QSplitter* _skillDetailGroupBoxParentLayout, + QHBoxLayout* _updateWidgetLayout, + + QLayout* stopAllLayout, + + QWidget* _connectionStatusLabel, + + std::shared_ptr<SkillManagerWrapper> _memory) + { + Logging::setTag("SkillMemoryGui"); + + // parameter check + ARMARX_CHECK(_skillExecutionTreeWidgetParentLayout); + ARMARX_CHECK(_skillGroupBoxParentLayout); + ARMARX_CHECK(_skillDetailGroupBoxParentLayout); + ARMARX_CHECK(_memory); + + ARMARX_CHECK(_skillExecutionTreeWidget); + ARMARX_CHECK(_skillGroupBox); + ARMARX_CHECK(_skillDetailGroupBox); + ARMARX_CHECK(_updateWidgetLayout); + ARMARX_CHECK(_connectionStatusLabel); + ARMARX_CHECK(_memory); + + // setup memory + this->memory = _memory; + + // Update widget + this->updateWidgetLayout = _updateWidgetLayout; + this->updateWidget = new PeriodicUpdateWidget(); + this->updateWidgetLayout->insertWidget(0, updateWidget); + + + // replace skillExecutionTreeWidget + this->skillExecutionTreeWidget = new SkillExecutionTreeWidget(memory); + armarx::gui::replaceWidget(_skillExecutionTreeWidget, + this->skillExecutionTreeWidget, + _skillExecutionTreeWidgetParentLayout); + + // replace skillGroupBox + this->skillGroupBox = new SkillGroupBox(memory); + + // replace skillDetailGroupBox + this->skillDetailGroupBox = new SkillDetailGroupBox(memory); + + armarx::gui::clearSplitter(_skillGroupBoxParentLayout); + _skillGroupBoxParentLayout->insertWidget(0, this->skillDetailGroupBox); + _skillGroupBoxParentLayout->insertWidget(0, this->skillGroupBox); + + // setup stop all button + stopAllButton = new QPushButton(QString::fromStdString(STOP_ALL_BUTTON_TEXT)); + stopAllLayout->addWidget(stopAllButton); + + this->connectionStatusLabel = new StatusLabel(); + armarx::gui::replaceWidget(_connectionStatusLabel, + this->connectionStatusLabel, + _connectionStatusLabel->parentWidget()->layout()); + + setupUi(); + } + + void + SkillMemoryGUI::setupUi() + { + stopAllButton->setStyleSheet("background-color: red"); + connectSignals(); + } + + void + SkillMemoryGUI::connectSignals() + { + connect(skillGroupBox, + &SkillGroupBox::updateSkillDetails, + skillDetailGroupBox, + &SkillDetailGroupBox::updateSkillDetails); + + // disconnect gui + connect(this, &SkillMemoryGUI::disconnectGui, skillGroupBox, &SkillGroupBox::disconnectGui); + connect(this, + &SkillMemoryGUI::disconnectGui, + skillDetailGroupBox, + &SkillDetailGroupBox::disconnectGui); + connect(this, + &SkillMemoryGUI::disconnectGui, + skillExecutionTreeWidget, + &SkillExecutionTreeWidget::disconnectGui); + connect( + this, &SkillMemoryGUI::disconnectGui, updateWidget, &PeriodicUpdateWidget::stopTimer); + + // connect gui + connect(this, + &SkillMemoryGUI::connectGui, + updateWidget, + &PeriodicUpdateWidget::startTimerIfEnabled); + connect(this, &SkillMemoryGUI::connectGui, skillGroupBox, &SkillGroupBox::connectGui); + + // update cascade + connect(this, &SkillMemoryGUI::updateGui, skillGroupBox, &SkillGroupBox::updateGui); + + // we don't want to update the skill details periodically, update can be triggered manually + /* + connect( + this, &SkillMemoryGUI::updateGui, skillDetailGroupBox, &SkillDetailGroupBox::updateGui); + */ + connect(this, + &SkillMemoryGUI::updateGui, + skillExecutionTreeWidget, + &SkillExecutionTreeWidget::updateExecutions); + + // timer -> update + connect( + this->updateWidget, &PeriodicUpdateWidget::update, this, &SkillMemoryGUI::updateGui); + + // stop all + connect(stopAllButton, + &QPushButton::clicked, + memory.get(), + &SkillManagerWrapper::stopAllExecutions); + + // status label + connect(memory.get(), + &SkillManagerWrapper::connectionUpdate, + connectionStatusLabel, + &StatusLabel::handleMessage); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.h b/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.h new file mode 100644 index 0000000000000000000000000000000000000000..64361f811ef2251ad679fad0c2efa1395a5aa563 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/SkillMemoryGui.h @@ -0,0 +1,80 @@ +#ifndef SKILLMEMORYGUI_H +#define SKILLMEMORYGUI_H + +#include <QHBoxLayout> +#include <QLabel> +#include <QSplitter> +#include <QTreeWidget> +#include <QWidget> + +#include <ArmarXCore/core/logging/Logging.h> + +#include <ArmarXGui/libraries/ArmarXGuiBase/widgets/PeriodicUpdateWidget.h> + +#include "RobotAPI/libraries/skills_gui/StatusLabel.h" + +#include "./aron_tree_widget/widgets/SkillDescriptionWidget.h" +#include "./executions/SkillExecutionTreeWidget.h" +#include "./memory/SkillManagerWrapper.h" +#include "./skill_details/SkillDetailsGroupBox.h" +#include "./skills/SkillGroupBox.h" + +namespace armarx::skills::gui +{ + class SkillMemoryGUI : public QObject, public armarx::Logging + { + Q_OBJECT + + public: + static const constexpr char* STOP_ALL_BUTTON_TEXT = "Stop all executions"; + SkillMemoryGUI(QTreeWidget* _skillExecutionTreeWidget, + QLayout* _skillExecutionTreeWidgetParentLayout, + QGroupBox* _skillGroupBox, + QSplitter* _skillGroupBoxParentLayout, + QGroupBox* _skillDetailGroupBox, + QSplitter* _skillDetailGroupBoxParentLayout, + QHBoxLayout* _updateWidgetLayout, + + QLayout* stopAllLayout, + + QWidget* connectionStatusLabel, + + std::shared_ptr<SkillManagerWrapper> _memory); + + signals: + /** + * @brief Notify affected widgets to clear all saved state. + */ + void disconnectGui(); + + /** + * @brief Notify widgets of new connection. + * @param observerName the observer name. + */ + void connectGui(std::string observerName); + + /** + * @brief Notify widgets to update themselves + */ + void updateGui(); + + private: + void setupUi(); + void connectSignals(); + + SkillExecutionTreeWidget* skillExecutionTreeWidget = nullptr; + SkillGroupBox* skillGroupBox = nullptr; + SkillDetailGroupBox* skillDetailGroupBox = nullptr; + QBoxLayout* updateWidgetLayout = nullptr; + + std::shared_ptr<SkillManagerWrapper> memory = nullptr; + + PeriodicUpdateWidget* updateWidget = nullptr; + + QPushButton* stopAllButton = nullptr; + + StatusLabel* connectionStatusLabel = nullptr; + }; +} // namespace armarx::skills::gui + +#endif // SKILLMEMORYGUI_H diff --git a/source/RobotAPI/libraries/skills_gui/StatusLabel.cpp b/source/RobotAPI/libraries/skills_gui/StatusLabel.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6b65f166aa9677f0eed55b95436a1a76777471f3 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/StatusLabel.cpp @@ -0,0 +1,52 @@ +#include "StatusLabel.h" + +#include <QHBoxLayout> + +namespace armarx::skills::gui +{ + + StatusLabel::StatusLabel() + { + this->label = new QLabel(""); + this->resetButton = new QPushButton(""); + this->setupUi(); + } + + void + StatusLabel::handleMessage(const std::string& message, std::string const& error) + { + this->label->setText(QString::fromStdString(message)); + this->resetButton->setHidden(false); + label->setToolTip(QString::fromStdString(error)); + } + + void + StatusLabel::resetLabel() + { + this->label->setText(QString::fromStdString("")); + this->resetButton->setHidden(true); + } + + void + StatusLabel::setupUi() + { + QHBoxLayout* layout = new QHBoxLayout(); + layout->addWidget(resetButton); + layout->addWidget(label); + this->setLayout(layout); + layout->setStretch(1, 2); + label->setStyleSheet("QLabel { color : red; }"); + this->resetButton->setHidden(true); + + label->setMinimumHeight(35); + label->setMaximumHeight(35); + + QPixmap pixmap(":/icons/delete.ico"); + QIcon ButtonIcon(pixmap); + resetButton->setIcon(ButtonIcon); + resetButton->setIconSize(pixmap.rect().size() / 2); + + connect(this->resetButton, &QPushButton::clicked, this, &StatusLabel::resetLabel); + } + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/StatusLabel.h b/source/RobotAPI/libraries/skills_gui/StatusLabel.h new file mode 100644 index 0000000000000000000000000000000000000000..b8e0f91e425e3715d5d5f617f877cfeeceee9a39 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/StatusLabel.h @@ -0,0 +1,35 @@ +#pragma once + +#include <QLabel> +#include <QPushButton> + +namespace armarx::skills::gui +{ + class StatusLabel : public QWidget + { + public: + /** + * @brief Constructor for StatusLabel + */ + StatusLabel(); + + public slots: + /** + * @brief Display a message to indicate an update. + */ + void handleMessage(std::string const& message, std::string const& error); + + private slots: + /** + * @brief Reset the label to default state. + */ + void resetLabel(); + + private: + void setupUi(); + + // contents + QLabel* label = nullptr; + QPushButton* resetButton = nullptr; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetController.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..79d606639c320ffeba73918967d9a832de446e68 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetController.cpp @@ -0,0 +1,163 @@ +#include "AronTreeWidgetController.h" + +#include "RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetModalCreator.h" + +#include "visitors/AronTreeWidgetContextMenu.h" +#include "visitors/AronTreeWidgetConverter.h" +#include "visitors/AronTreeWidgetSetter.h" + +namespace armarx::skills::gui +{ + AronTreeWidgetController::AronTreeWidgetController(QTreeWidget* tree, + QTreeWidgetItem* parent, + const aron::type::ObjectPtr& type, + const aron::data::DictPtr& data) : + parent(parent), tree(tree), type(type) + { + connect(tree, + SIGNAL(customContextMenuRequested(const QPoint&)), + this, + SLOT(ShowContextMenu(const QPoint&))); + connect(tree, + &QTreeWidget::itemDoubleClicked, + this, + &AronTreeWidgetController::onTreeWidgetItemDoubleClicked); + + if (type) // if there is a type set, we create a tree widget from the type + { + AronTreeWidgetCreatorVisitor v(parent); + v.setTopLevelWidget(tree); + aron::type::visit(v, type); + + if (data) // check if there is a default argument set. Prefill the GUI with it + { + setFromAron(data); + } + } + // there is no type but a default configuration. Prefill the GUI with the default arguments + else if (data) + { + // TODO: There is no visitor for that (yet)... + // create type from data, ... + } + else + { + new QTreeWidgetItem(parent, {QString::fromStdString("No args")}); + } + + // connect change handling after args init + connect(tree, + &QTreeWidget::itemChanged, + this, + &AronTreeWidgetController::onTreeWidgetItemChanged); + } + + aron::data::DictPtr + AronTreeWidgetController::convertToAron() const + { + if (parent && type) + { + AronTreeWidgetConverterVisitor v(parent, 0); + aron::type::visit(v, type); + + auto aron_args = aron::data::Dict::DynamicCastAndCheck(v.createdAron); + return aron_args; + } + return nullptr; + } + + void + AronTreeWidgetController::setFromAron(const aron::data::DictPtr& data) + { + if (parent) + { + AronTreeWidgetSetterVisitor v(parent, 0); + aron::data::visit(v, data); + } + } + + void + AronTreeWidgetController::ShowContextMenu(const QPoint& pos) + { + tree->blockSignals(true); + + auto idx = tree->indexAt(pos); + AronTreeWidgetItem* clickedItem = AronTreeWidgetItem::DynamicCast(tree->itemAt(pos)); + if (clickedItem) + { + AronTreeWidgetContextMenuVisitor visitor(clickedItem, pos, tree, idx.row()); + + aron::type::visit(visitor, clickedItem->aronType); + visitor.showMenuAndExecute(); + } + + tree->blockSignals(false); + } + + void + AronTreeWidgetController::onTreeWidgetItemDoubleClicked(QTreeWidgetItem* item, int column) + { + if (!item) + { + return; + } + tree->blockSignals(true); + + auto* aronItem = AronTreeWidgetItem::DynamicCast(item); + if (column == 1 && aronItem) + { + + + std::string name = + aronItem->text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_NAME).toStdString(); + // depending on aron type, create extra gui element. + armarx::skills::gui::AronTreeWidgetModalCreatorVisitor v(name, aronItem, tree); + aron::type::visit(v, aronItem->aronType); + auto modal = v.createdModal; + + // if no modal is created, we instead use the edit field directly + if (modal) + { + modal->exec(); + } + else if (aronItem->col1Editable) + { + item->treeWidget()->editItem(item, column); + } + } + else if (column == 0 && aronItem && aronItem->col0Editable) + { + item->treeWidget()->editItem(item, column); + } + + tree->blockSignals(false); + } + + void + AronTreeWidgetController::onTreeWidgetItemChanged(QTreeWidgetItem* item, int column) + { + tree->blockSignals(true); + + auto* aronElem = AronTreeWidgetItem::DynamicCast(item); + if (aronElem) + { + aronElem->onUserChange(column); + } + // start conversion for entire tree -- this also sets the highlighting + if (parent->childCount() == 1) + { + auto* aronTreeRoot = AronTreeWidgetItem::DynamicCast(parent->child(0)); + aronTreeRoot->resetError(); + if (aronTreeRoot) + { + AronTreeWidgetConverterVisitor v(parent, 0); + aron::type::visit(v, type); + aronTreeRoot->setValueErrorState(v.hasDirectError(), v.onlyChildFailedConversion()); + } + } + // else perhaps the GUI was stopped or died. + + tree->blockSignals(false); + } + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetController.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetController.h new file mode 100644 index 0000000000000000000000000000000000000000..2b6608fe17e09acddbe5cbde6192ce9625f0de8c --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetController.h @@ -0,0 +1,52 @@ +#pragma once + +#include <stack> + +#include <QTreeWidget> + +#include <ArmarXCore/core/system/ImportExportComponent.h> + +#include <ArmarXGui/libraries/ArmarXGuiBase/ArmarXComponentWidgetController.h> +#include <ArmarXGui/libraries/ArmarXGuiBase/ArmarXGuiPlugin.h> + +#include "AronTreeWidgetItem.h" +#include "Data.h" +#include "visitors/AronTreeWidgetCreator.h" + +namespace armarx::skills::gui +{ + // Main controller for any AronTreeWidget GUI. It attaches itself to the parent and needs the active widget. + // It's responsible to handle all signals for non-widget fields and click events. + class AronTreeWidgetController : public QObject + { + Q_OBJECT + public: + AronTreeWidgetController(QTreeWidget* tree, + QTreeWidgetItem* parent, + const aron::type::ObjectPtr& type, + const aron::data::DictPtr& data = nullptr); + + aron::data::DictPtr convertToAron() const; + void setFromAron(const aron::data::DictPtr&); + + private: + QTreeWidgetItem* parent; + QTreeWidget* tree; + + aron::type::ObjectPtr type; + + private slots: + // allows most primitive fields to be directly editable in the tree + // only String will open up a new widget + void onTreeWidgetItemDoubleClicked(QTreeWidgetItem* item, int column); + // check the new user input. Maybe undo if the field must not be edited. + // Also highlight if the input cannot be parsed. (Or the errors are now fixed) + void onTreeWidgetItemChanged(QTreeWidgetItem* item, int column); + + public slots: + // hook for items to show a context menu (add / delete element) + void ShowContextMenu(const QPoint&); + }; + + using AronTreeWidgetControllerPtr = std::shared_ptr<AronTreeWidgetController>; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetItem.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetItem.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d76cee191fda8cb927548a795b53291875ae14e6 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetItem.cpp @@ -0,0 +1,200 @@ +#include "AronTreeWidgetItem.h" + +#include <QAction> +#include <QMenu> + +#include <RobotAPI/libraries/aron/core/type/variant/All.h> + +#include "ColorPalettes.h" +#include "visitors/AronTreeWidgetConverter.h" +#include "widgets/CustomWidget.h" + +namespace armarx::skills::gui +{ + AronTreeWidgetItem* + AronTreeWidgetItem::DynamicCast(QTreeWidgetItem* i) + { + return dynamic_cast<AronTreeWidgetItem*>(i); + } + + AronTreeWidgetItem* + AronTreeWidgetItem::DynamicCastAndCheck(QTreeWidgetItem* i) + { + if (!i) + { + return nullptr; + } + auto c = DynamicCast(i); + ARMARX_CHECK_NOT_NULL(c); + return c; + } + + bool + AronTreeWidgetItem::isValueErrorneous() + { + return itemValueError; + } + + void + AronTreeWidgetItem::setValueErrorState(bool isErrorSource, bool isTransitiveError) + { + itemValueError |= isErrorSource; + transitiveValueError |= isTransitiveError; + + + if (CustomWidget::DynamicCast(treeWidget()->itemWidget(this, 1))) + { + // The widgets handle errors themselves + return; + } + auto palette = gui_color_palette::getNormalPalette(); + if (itemValueError) + { + palette = gui_color_palette::getErrorPalette(); + } + else if (transitiveValueError) + { + palette = gui_color_palette::getIndirectErrorPalette(); + } + + QTreeWidgetItem::setBackground(1, QBrush(palette.color(QPalette::Base))); + } + + void + AronTreeWidgetItem::setKeyErrorState(bool hasError) + { + ARMARX_CHECK(col0Editable); //only editable keys should call this function! + auto palette = + hasError ? gui_color_palette::getErrorPalette() : gui_color_palette::getNormalPalette(); + + keyValueError = hasError; + + QTreeWidgetItem::setBackground(1, QBrush(palette.color(QPalette::Base))); + } + + void + AronTreeWidgetItem::resetError() + { + keyValueError = false; + itemValueError = false; + transitiveValueError = false; + // also reset children + for (int i = 0; i < childCount(); ++i) + { + auto* arChild = AronTreeWidgetItem::DynamicCastAndCheck(QTreeWidgetItem::child(i)); + arChild->resetError(); + } + } + + void + AronTreeWidgetItem::checkKeyValidityOfChildren() + { + ARMARX_CHECK(aronType->getDescriptor() == aron::type::Descriptor::DICT); + // return if check failed + if (aronType->getDescriptor() != aron::type::Descriptor::DICT) + { + return; + } + // iterate through children; memorize keys + std::map<QString, std::vector<int>> found_keys; + auto numChildren = childCount(); + for (int i = 0; i < numChildren; ++i) + { + auto* casted = AronTreeWidgetItem::DynamicCastAndCheck(child(i)); + if (!casted) + { + // soft error here, we already report it above. - Definetly programming error + continue; + } + auto& vec = found_keys[casted->text(0)]; + vec.push_back(i); + } + // highlight keys that conflict + // memorize children that are not ok + std::set<int> errorneous_indices; + for (auto [key, vals] : found_keys) + { + if (vals.size() > 1) + { + for (int i : vals) + { + auto* casted = AronTreeWidgetItem::DynamicCastAndCheck(child(i)); + if (!casted) + { + // soft error here, we already report it above. - Definetly programming error + continue; + } + casted->setKeyErrorState(true); + errorneous_indices.emplace(i); + } + } + } + // clear potential error state of other elements + for (int i = 0; i < numChildren; ++i) + { + if (errorneous_indices.find(i) != errorneous_indices.end()) + { + continue; + } + auto* casted = AronTreeWidgetItem::DynamicCastAndCheck(child(i)); + if (!casted) + { + // soft error here, we already report it above. - Definetly programming error + continue; + } + casted->setKeyErrorState(false); + } + } + + void + AronTreeWidgetItem::onUserChange(int changedColumn) + { + QTreeWidgetItem* qParent = QTreeWidgetItem::parent(); + ARMARX_CHECK(qParent); + AronTreeWidgetItem* aronParent = DynamicCast(qParent); + if (changedColumn == 0) + { + if (col0Editable) + { + // Topmost should always be an Object and col0 is not editable in the child then... + // -> aronParent should never be nullptr + ARMARX_CHECK(aronParent); + // check if the element is child of a dict. If so, validate uniqueness of keys + if (aronParent->aronType->getDescriptor() == aron::type::Descriptor::DICT) + { + aronParent->checkKeyValidityOfChildren(); + } + } + else + { + // maybe while editing keys, we try to edit a key that should not change by tabbing. + // Catch that here and undo the edit + preventIllegalKeyChange(); + } + } + } + + void + AronTreeWidgetItem::preventIllegalKeyChange() + { + if (!col0Editable) + { + setText(0, unchangeableKey); + } + } + + AronTreeWidgetItem::AronTreeWidgetItem(bool editKey, + bool editVal, + QString key, + aron::type::VariantPtr type) : + aronType(type), col0Editable(editKey), col1Editable(editVal) + { + this->setText(0, key); + // add hook to check for edited keys for children of dictionaries + if (!editKey) + { + unchangeableKey = std::move(key); + } + } + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetItem.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetItem.h new file mode 100644 index 0000000000000000000000000000000000000000..e1bf488e9b4bbec1cc04674e312bcabd783dabdf --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetItem.h @@ -0,0 +1,65 @@ +#pragma once + +#include <QTreeWidget> + +#include <ArmarXCore/core/system/ImportExportComponent.h> + +#include <ArmarXGui/libraries/ArmarXGuiBase/ArmarXComponentWidgetController.h> +#include <ArmarXGui/libraries/ArmarXGuiBase/ArmarXGuiPlugin.h> + +#include <RobotAPI/libraries/aron/core/type/variant/Variant.h> + +#include "Data.h" + +namespace armarx::skills::gui +{ + // Internal derived QtTreeWidgetItem class. Contains additional information to compare its data agains aron types. + // It contains 3 columns: Key ; Value ; Type + // The key can only be edited if the parent object is a dict + class AronTreeWidgetItem : public QObject, public QTreeWidgetItem + { + Q_OBJECT + public: + AronTreeWidgetItem(bool editKey, bool editVal, QString key, aron::type::VariantPtr type); + + using QTreeWidgetItem::QTreeWidgetItem; + + static AronTreeWidgetItem* DynamicCast(QTreeWidgetItem*); + static AronTreeWidgetItem* DynamicCastAndCheck(QTreeWidgetItem*); + + aron::type::VariantPtr aronType; + // if editing the first column should be allowed + const bool col0Editable = false; + // if editing the second column should be allowed + const bool col1Editable = false; + + bool isValueErrorneous(); + // marks the gui in a specific color. Also stores if there is currently an error. + // the only way to reset the color back to normal is with resetError() (error is sticky) + void setValueErrorState(bool isErrorSource, bool isTransitiveError); + void setKeyErrorState(bool hasError); + // reset error storage that influences coloring for this and all children + void resetError(); + + // Checks if the children of a dict are unique + // should not be called on other types! (does nothing then) + void checkKeyValidityOfChildren(); + + // main logic on changes. Gets called from the onTreeWidgetItemChanged() slot in AronTreeWidgetController. + // (This class cannot directly consume this signal, at least I did not find a nice way...) + void onUserChange(int changedColumn); + + private: + // because the editable keyword counts for all columns, it is possible to TAB into uneditable keys. + // We do not want those to change, so just change them back once they finished editing. + void preventIllegalKeyChange(); + + bool itemValueError = false; + bool transitiveValueError = false; + bool keyValueError = false; + // hacky storage of previous key value for items that usually should not be editable. + // Problem: Dict-keys are editable, one can tab from that to any other value field and edit it. + // Fix: Once the change is commited, the AronTreeWidgetController notices the change and checks if it was allowed. + QString unchangeableKey = ""; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ColorPalettes.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ColorPalettes.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ad2e711d96b2cc5cb5a139ac8805bf590fd0591f --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ColorPalettes.cpp @@ -0,0 +1,33 @@ +#include "ColorPalettes.h" + +namespace armarx::skills::gui::gui_color_palette +{ + QPalette + getErrorPalette() + { + QPalette errorPalette; + errorPalette.setColor(QPalette::Base, Qt::red); + return errorPalette; + } + + QPalette + getNormalPalette() + { + + QPalette normalPalette; + + normalPalette.setColor(QPalette::Base, Qt::white); + return normalPalette; + } + + QPalette + getIndirectErrorPalette() + { + QPalette indirectErr; + static QColor orange = QColor::fromRgb(255, 165, 0); + // orange color + indirectErr.setColor(QPalette::Base, orange); + return indirectErr; + } + +} // namespace armarx::skills::gui::gui_color_palette diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ColorPalettes.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ColorPalettes.h new file mode 100644 index 0000000000000000000000000000000000000000..79386a831608667395a2405f45d79d751d926b32 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ColorPalettes.h @@ -0,0 +1,11 @@ +#pragma once +#include <QPalette> + +namespace armarx::skills::gui::gui_color_palette +{ + QPalette getNormalPalette(); + + QPalette getErrorPalette(); + + QPalette getIndirectErrorPalette(); +} // namespace armarx::skills::gui::gui_color_palette diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/Data.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/Data.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f668ffd67c7aa5ccdb889f4e7794a0e08f919d9a --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/Data.cpp @@ -0,0 +1,6 @@ +#include "Data.h" + +namespace armarx::skills::gui +{ + +} diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/Data.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/Data.h new file mode 100644 index 0000000000000000000000000000000000000000..57fadd8343a1b4deab96c56c193851f37ed1d2be --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/Data.h @@ -0,0 +1,13 @@ +#pragma once + +#include <string> + +namespace armarx::skills::gui::aron_tree_widget::constantes +{ + const int TREE_WIDGET_ITEM_NAME = 0; + const int TREE_WIDGET_ITEM_VALUE = 1; + const int TREE_WIDGET_ITEM_TYPE = 2; + + const std::string ITEM_EMPTY_MESSAGE = "(double click to edit)"; + const std::string NEW_ITEM_DEFAULT_MESSAGE = "(Please set via main GUI (not in modal))"; +} // namespace armarx::skills::gui::aron_tree_widget::constantes diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ListDictHelper.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ListDictHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d8c763e7b227d90fd101ecce44ff7d79ed905fac --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ListDictHelper.cpp @@ -0,0 +1,28 @@ +#include "ListDictHelper.h" + +namespace armarx::skills::gui::misc +{ + QString + generateNumElementsText(int num) + { + QString numElemsText = "<"; + if (num == 0) + { + numElemsText.append("no"); + } + else + { + numElemsText.append(QString::number(num)); + } + if (num > 1 || num == 0) + { + numElemsText.append(" elements>"); + } + else + { + numElemsText.append(" element>"); + } + return numElemsText; + } + +} // namespace armarx::skills::gui::misc diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ListDictHelper.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ListDictHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..553eca828bb37b5acec0b1bee96cf8c7a5addcbd --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/ListDictHelper.h @@ -0,0 +1,7 @@ +#include <QString> + +namespace armarx::skills::gui::misc +{ + // helper that generates a string on how many items are available + QString generateNumElementsText(int num); +} // namespace armarx::skills::gui::misc diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/AronTreeWidgetModal.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/AronTreeWidgetModal.cpp new file mode 100644 index 0000000000000000000000000000000000000000..83189ef6d168fd33dd8e5745c9b4cb2f908bdf9a --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/AronTreeWidgetModal.cpp @@ -0,0 +1,22 @@ +#include "AronTreeWidgetModal.h" + +namespace armarx::skills::gui +{ + AronTreeWidgetModal::AronTreeWidgetModal(const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent) : + QDialog(parent), item(item), label(label), parent(parent) + { + init.aronType = item->aronType; + init.setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_NAME, + item->text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_NAME)); + init.setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE, + item->text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE)); + init.setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_TYPE, + item->text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_TYPE)); + for (int i = 0; i < item->childCount(); ++i) + { + init.addChild(item->child(i)->clone()); + } + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/AronTreeWidgetModal.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/AronTreeWidgetModal.h new file mode 100644 index 0000000000000000000000000000000000000000..ba1b39e5b7ef2fc4a09f0f6208bb931e743b7e6c --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/AronTreeWidgetModal.h @@ -0,0 +1,65 @@ +#pragma once + +#include <stack> + +#include <QDialog> +#include <QTreeWidget> + +#include <ArmarXCore/core/system/ImportExportComponent.h> + +#include <ArmarXGui/libraries/ArmarXGuiBase/ArmarXComponentWidgetController.h> +#include <ArmarXGui/libraries/ArmarXGuiBase/ArmarXGuiPlugin.h> + +#include "../AronTreeWidgetItem.h" +#include "../Data.h" + +namespace armarx::skills::gui +{ + class AronTreeWidgetModal : public QDialog + { + Q_OBJECT + + public: + AronTreeWidgetModal(const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent); + + protected slots: + + virtual void + reset() + { + item->aronType = init.aronType; + item->setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_NAME, + init.text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_NAME)); + item->setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE, + init.text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE)); + item->setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_TYPE, + init.text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_TYPE)); + for (int i = 0; i < item->childCount(); ++i) + { + item->removeChild(item->child(i)); + } + for (int i = 0; i < init.childCount(); ++i) + { + item->addChild(init.child(i)->clone()); + } + } + + virtual void + submit() + { + accept(); + } + + protected: + AronTreeWidgetItem init; + AronTreeWidgetItem* item; + + private: + std::string label; + QTreeWidget* parent; + }; + + using AronTreeWidgetModalControllerPtr = std::shared_ptr<AronTreeWidgetModal>; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModal.ui b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModal.ui new file mode 100644 index 0000000000000000000000000000000000000000..69f4a5c99b277ba4808ecc45081df86e0ab55ef0 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModal.ui @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AronTreeWidgetBoolInputModalWidget</class> + <widget class="QWidget" name="AronTreeWidgetBoolInputModalWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1015</width> + <height>498</height> + </rect> + </property> + <property name="windowTitle"> + <string>SkillManagerMonitorWidget</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QGroupBox" name="groupBoxInput"> + <property name="title"> + <string>InputField</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QPushButton" name="pushButtonReset"> + <property name="text"> + <string>Reset</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="pushButtonSubmit"> + <property name="text"> + <string>Submit</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QTextEdit" name="textEditInput"/> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModalController.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModalController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5ae3401afed2a7d933e473600273309ef6e3f4b2 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModalController.cpp @@ -0,0 +1,34 @@ +#include "AronTreeWidgetBoolInputModalController.h" + +namespace armarx::skills::gui +{ + AronTreeWidgetBoolInputModalController::AronTreeWidgetBoolInputModalController( + const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent) : + AronTreeWidgetModal(label, item, parent) + { + widget.setupUi(this); + + // TODO + } + + void + AronTreeWidgetBoolInputModalController::submit() + { + item->setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE, + widget.textEditInput->toPlainText()); + + AronTreeWidgetModal::submit(); + } + + void + AronTreeWidgetBoolInputModalController::reset() + { + AronTreeWidgetModal::reset(); + + // reset to initial value + widget.textEditInput->setPlainText( + init.text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE)); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModalController.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModalController.h new file mode 100644 index 0000000000000000000000000000000000000000..cbac189299a1fa4d9e5f705c159f0f87fdf90a9d --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/bool/AronTreeWidgetBoolInputModalController.h @@ -0,0 +1,27 @@ +#pragma once + +#include <QDialog> + +#include "RobotAPI/libraries/skills_gui/bool/ui_AronTreeWidgetBoolInputModal.h" + +#include "../AronTreeWidgetModal.h" + +namespace armarx::skills::gui +{ + class AronTreeWidgetBoolInputModalController : public AronTreeWidgetModal + { + + public: + AronTreeWidgetBoolInputModalController(const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent); + + private slots: + + void submit() final; + void reset() final; + + private: + Ui::AronTreeWidgetBoolInputModalWidget widget; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/dict/AronTreeWidgetDictInputModal.ui b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/dict/AronTreeWidgetDictInputModal.ui new file mode 100644 index 0000000000000000000000000000000000000000..b9b800ed26a2b2170b8ab06633e8e795b1957be4 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/dict/AronTreeWidgetDictInputModal.ui @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AronTreeWidgetDictInputModalWidget</class> + <widget class="QWidget" name="AronTreeWidgetDictInputModalWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1015</width> + <height>498</height> + </rect> + </property> + <property name="windowTitle"> + <string>SkillManagerMonitorWidget</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QGroupBox" name="groupBoxInput"> + <property name="title"> + <string>InputField</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0" colspan="5"> + <widget class="QTreeWidget" name="treeWidgetDict"> + <column> + <property name="text"> + <string>Key</string> + </property> + </column> + <column> + <property name="text"> + <string>Value</string> + </property> + </column> + <column> + <property name="text"> + <string>Type</string> + </property> + </column> + </widget> + </item> + <item row="1" column="3" colspan="2"> + <widget class="QPushButton" name="pushButtonAddElement"> + <property name="text"> + <string>+ Add element</string> + </property> + </widget> + </item> + <item row="1" column="0" colspan="3"> + <widget class="QLineEdit" name="lineEditKey"> + <property name="maximumSize"> + <size> + <width>880</width> + <height>16777215</height> + </size> + </property> + <property name="text"> + <string>Enter Key</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QPushButton" name="pushButtonReset"> + <property name="text"> + <string>Reset</string> + </property> + </widget> + </item> + <item row="2" column="1" colspan="4"> + <widget class="QPushButton" name="pushButtonSubmit"> + <property name="text"> + <string>Submit</string> + </property> + </widget> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/dict/AronTreeWidgetDictInputModalController.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/dict/AronTreeWidgetDictInputModalController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..57d43b6d40917ec1b6a54c6c1eacd560aed72876 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/dict/AronTreeWidgetDictInputModalController.cpp @@ -0,0 +1,89 @@ +#include "AronTreeWidgetDictInputModalController.h" + +#include <RobotAPI/libraries/aron/core/type/variant/All.h> + +#include "../../visitors/AronTreeWidgetCreator.h" + +namespace armarx::skills::gui +{ + AronTreeWidgetDictInputModalController::AronTreeWidgetDictInputModalController( + const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent) : + AronTreeWidgetModal(label, item, parent) + { + widget.setupUi(this); + + // set header + widget.groupBoxInput->setTitle(QString::fromStdString(label)); + reset(); + + // connect signals + connect(widget.pushButtonAddElement, + &QPushButton::clicked, + this, + &AronTreeWidgetDictInputModalController::addEmptyElement); + + connect(widget.pushButtonReset, + &QPushButton::clicked, + this, + &AronTreeWidgetDictInputModalController::reset); + connect(widget.pushButtonSubmit, + &QPushButton::clicked, + this, + &AronTreeWidgetDictInputModalController::submit); + } + + void + AronTreeWidgetDictInputModalController::submit() + { + /* + for (const auto& added : addedItems) + { + item->addChild(added->copy()); + } +*/ + + AronTreeWidgetModal::submit(); + } + + void + AronTreeWidgetDictInputModalController::reset() + { + AronTreeWidgetModal::reset(); + + // reset to initial value + widget.treeWidgetDict->clear(); + for (int i = 0; i < init.childCount(); ++i) + { + auto el = init.child(i); + widget.treeWidgetDict->addTopLevelItem(el->clone()); + } + } + + void + AronTreeWidgetDictInputModalController::addEmptyElement() + { + QString s = widget.lineEditKey->text(); + widget.lineEditKey->setText("Enter Key"); + + if (widget.treeWidgetDict->findItems(s, Qt::MatchFlag::MatchExactly, 0).empty()) + { + auto t = item->aronType; + auto d = aron::type::Dict::DynamicCastAndCheck(t); + auto ac = d->getAcceptedType(); + + AronTreeWidgetCreatorVisitor v(nullptr); + v.setTopLevelWidget(widget.treeWidgetDict); + aron::type::visit(v, ac); + + if (v.createdQWidgetItem) + { + v.createdQWidgetItem->setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_NAME, + s); + addedItems.push_back(v.createdQWidgetItem); + widget.treeWidgetDict->addTopLevelItem(v.createdQWidgetItem); + } + } + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/dict/AronTreeWidgetDictInputModalController.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/dict/AronTreeWidgetDictInputModalController.h new file mode 100644 index 0000000000000000000000000000000000000000..f2e992b19eb4d68df7a86c98c538acebc89b8464 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/dict/AronTreeWidgetDictInputModalController.h @@ -0,0 +1,37 @@ +#pragma once + +#include <stack> + +#include <QDialog> + +#include <ArmarXCore/core/system/ImportExportComponent.h> + +#include <ArmarXGui/libraries/ArmarXGuiBase/ArmarXComponentWidgetController.h> +#include <ArmarXGui/libraries/ArmarXGuiBase/ArmarXGuiPlugin.h> +#include <ArmarXGui/libraries/SimpleConfigDialog/SimpleConfigDialog.h> + +#include "RobotAPI/libraries/skills_gui/dict/ui_AronTreeWidgetDictInputModal.h" + +#include "../AronTreeWidgetModal.h" + +namespace armarx::skills::gui +{ + class AronTreeWidgetDictInputModalController : public AronTreeWidgetModal + { + public: + AronTreeWidgetDictInputModalController(const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent); + + private slots: + + void submit() final; + void reset() final; + + void addEmptyElement(); + + private: + std::vector<AronTreeWidgetItem*> addedItems; + Ui::AronTreeWidgetDictInputModalWidget widget; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModal.ui b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModal.ui new file mode 100644 index 0000000000000000000000000000000000000000..ba3734066df16e227142f6636d7cb6e39c415646 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModal.ui @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AronTreeWidgetFloatInputModalWidget</class> + <widget class="QWidget" name="AronTreeWidgetFloatInputModalWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1015</width> + <height>498</height> + </rect> + </property> + <property name="windowTitle"> + <string>SkillManagerMonitorWidget</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QGroupBox" name="groupBoxInput"> + <property name="title"> + <string>InputField</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QPushButton" name="pushButtonReset"> + <property name="text"> + <string>Reset</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="pushButtonSubmit"> + <property name="text"> + <string>Submit</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QTextEdit" name="textEditInput"/> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModalController.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModalController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..79ce1bdb67c81465e9631348f50a4689de21a093 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModalController.cpp @@ -0,0 +1,34 @@ +#include "AronTreeWidgetFloatInputModalController.h" + +namespace armarx::skills::gui +{ + AronTreeWidgetFloatInputModalController::AronTreeWidgetFloatInputModalController( + const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent) : + AronTreeWidgetModal(label, item, parent) + { + widget.setupUi(this); + + // TODO + } + + void + AronTreeWidgetFloatInputModalController::submit() + { + item->setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE, + widget.textEditInput->toPlainText()); + + AronTreeWidgetModal::submit(); + } + + void + AronTreeWidgetFloatInputModalController::reset() + { + AronTreeWidgetModal::reset(); + + // reset to initial value + widget.textEditInput->setPlainText( + init.text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE)); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModalController.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModalController.h new file mode 100644 index 0000000000000000000000000000000000000000..a68834c9a75becaf7eb7075e7947485ae7523dee --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/float_double/AronTreeWidgetFloatInputModalController.h @@ -0,0 +1,27 @@ +#pragma once + +#include <QDialog> + +#include "RobotAPI/libraries/skills_gui/float_double/ui_AronTreeWidgetFloatInputModal.h" + +#include "../AronTreeWidgetModal.h" + +namespace armarx::skills::gui +{ + class AronTreeWidgetFloatInputModalController : public AronTreeWidgetModal + { + + public: + AronTreeWidgetFloatInputModalController(const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent); + + private slots: + + void submit() final; + void reset() final; + + private: + Ui::AronTreeWidgetFloatInputModalWidget widget; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModal.ui b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModal.ui new file mode 100644 index 0000000000000000000000000000000000000000..538a78e766e997ad9811ff11b6036f088de332e2 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModal.ui @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AronTreeWidgetIntInputModalWidget</class> + <widget class="QWidget" name="AronTreeWidgetIntInputModalWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1015</width> + <height>498</height> + </rect> + </property> + <property name="windowTitle"> + <string>SkillManagerMonitorWidget</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QGroupBox" name="groupBoxInput"> + <property name="title"> + <string>InputField</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QPushButton" name="pushButtonReset"> + <property name="text"> + <string>Reset</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="pushButtonSubmit"> + <property name="text"> + <string>Submit</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QTextEdit" name="textEditInput"/> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModalController.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModalController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..72f849c2745c63b41ea0c054dbf70f2f282ba48a --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModalController.cpp @@ -0,0 +1,34 @@ +#include "AronTreeWidgetIntInputModalController.h" + +namespace armarx::skills::gui +{ + AronTreeWidgetIntInputModalController::AronTreeWidgetIntInputModalController( + const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent) : + AronTreeWidgetModal(label, item, parent) + { + widget.setupUi(this); + + // TODO + } + + void + AronTreeWidgetIntInputModalController::submit() + { + item->setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE, + widget.textEditInput->toPlainText()); + + AronTreeWidgetModal::submit(); + } + + void + AronTreeWidgetIntInputModalController::reset() + { + AronTreeWidgetModal::reset(); + + // reset to initial value + widget.textEditInput->setPlainText( + init.text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE)); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModalController.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModalController.h new file mode 100644 index 0000000000000000000000000000000000000000..15b3382a4af234d0c435d25787d3f7b3aff822e1 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/int_long/AronTreeWidgetIntInputModalController.h @@ -0,0 +1,27 @@ +#pragma once + +#include <QDialog> + +#include "RobotAPI/libraries/skills_gui/int_long/ui_AronTreeWidgetIntInputModal.h" + +#include "../AronTreeWidgetModal.h" + +namespace armarx::skills::gui +{ + class AronTreeWidgetIntInputModalController : public AronTreeWidgetModal + { + + public: + AronTreeWidgetIntInputModalController(const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent); + + private slots: + + void submit() final; + void reset() final; + + private: + Ui::AronTreeWidgetIntInputModalWidget widget; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/text/AronTreeWidgetTextInputModal.ui b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/text/AronTreeWidgetTextInputModal.ui new file mode 100644 index 0000000000000000000000000000000000000000..ed40962f96639ff34eed30b85331d7774b2eef9d --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/text/AronTreeWidgetTextInputModal.ui @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>AronTreeWidgetTextInputModalWidget</class> + <widget class="QWidget" name="AronTreeWidgetTextInputModalWidget"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>1015</width> + <height>498</height> + </rect> + </property> + <property name="windowTitle"> + <string>SkillManagerMonitorWidget</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QSplitter" name="splitter"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <widget class="QGroupBox" name="groupBoxInput"> + <property name="title"> + <string>InputField</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="1" column="0"> + <widget class="QPushButton" name="pushButtonReset"> + <property name="text"> + <string>Reset</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QPushButton" name="pushButtonSubmit"> + <property name="text"> + <string>Submit</string> + </property> + </widget> + </item> + <item row="0" column="0" colspan="2"> + <widget class="QTextEdit" name="textEditInput"/> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/text/AronTreeWidgetTextInputModalController.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/text/AronTreeWidgetTextInputModalController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cfce5ccafb7be7b9b14f76cb99f383fefc452eb2 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/text/AronTreeWidgetTextInputModalController.cpp @@ -0,0 +1,46 @@ +#include "AronTreeWidgetTextInputModalController.h" + +namespace armarx::skills::gui +{ + AronTreeWidgetTextInputModalController::AronTreeWidgetTextInputModalController( + const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent) : + AronTreeWidgetModal(label, item, parent) + { + widget.setupUi(this); + + // set header + widget.groupBoxInput->setTitle(QString::fromStdString(label)); + reset(); + + // connect signals + connect(widget.pushButtonReset, + &QPushButton::clicked, + this, + &AronTreeWidgetTextInputModalController::reset); + connect(widget.pushButtonSubmit, + &QPushButton::clicked, + this, + &AronTreeWidgetTextInputModalController::submit); + } + + void + AronTreeWidgetTextInputModalController::submit() + { + item->setText(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE, + widget.textEditInput->toPlainText()); + + AronTreeWidgetModal::submit(); + } + + void + AronTreeWidgetTextInputModalController::reset() + { + AronTreeWidgetModal::reset(); + + // reset to initial value + widget.textEditInput->setPlainText( + init.text(aron_tree_widget::constantes::TREE_WIDGET_ITEM_VALUE)); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/text/AronTreeWidgetTextInputModalController.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/text/AronTreeWidgetTextInputModalController.h new file mode 100644 index 0000000000000000000000000000000000000000..1089a595ec1a9f935785b71519bb821bbddc648f --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/modal/text/AronTreeWidgetTextInputModalController.h @@ -0,0 +1,27 @@ +#pragma once + +#include <QDialog> + +#include "RobotAPI/libraries/skills_gui/text/ui_AronTreeWidgetTextInputModal.h" + +#include "../AronTreeWidgetModal.h" + +namespace armarx::skills::gui +{ + class AronTreeWidgetTextInputModalController : public AronTreeWidgetModal + { + + public: + AronTreeWidgetTextInputModalController(const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent); + + private slots: + + void submit() final; + void reset() final; + + private: + Ui::AronTreeWidgetTextInputModalWidget widget; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetContextMenu.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetContextMenu.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b02b713032456e3b5a7d37dd3a4f7be294236b87 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetContextMenu.cpp @@ -0,0 +1,242 @@ +#include "AronTreeWidgetContextMenu.h" + +#include <QMenu> +#include <QPoint> +#include <QTreeWidget> +#include <QTreeWidgetItem> + +#include "../AronTreeWidgetItem.h" +#include "../ListDictHelper.h" +#include "AronTreeWidgetCreator.h" + +namespace armarx::skills::gui +{ + AronTreeWidgetContextMenuVisitor::AronTreeWidgetContextMenuVisitor( + AronTreeWidgetItem* i, + const QPoint& pos, + QTreeWidget* contextMenuParent, + int x) : + parentItem(i), contextMenuParent(contextMenuParent), pos(pos), index(x) + { + } + + void + AronTreeWidgetContextMenuVisitor::addDeleteAction() + { + auto* castedParent = AronTreeWidgetItem::DynamicCast(parentItem->QTreeWidgetItem::parent()); + if (!castedParent) + { + // must be top level element + return; + } + auto aronType = castedParent->aronType->getDescriptor(); + if (aron::type::Descriptor::DICT == aronType || aron::type::Descriptor::LIST == aronType) + { + QMenu contextMenu("Context menu", contextMenuParent); + actions.emplace_back("remove element", contextMenuParent); + action_callbacks.push_back([this]() mutable { this->executeDelete(); }); + } + } + + void + AronTreeWidgetContextMenuVisitor::executeDelete() + { + auto* containerPtr = parentItem->QTreeWidgetItem::parent(); + containerPtr->removeChild(parentItem); + auto* castedContainer = AronTreeWidgetItem::DynamicCast(containerPtr); + + // if the parent item is a List, we need to redo the numbers + if (castedContainer && + castedContainer->aronType->getDescriptor() == aron::type::Descriptor::LIST) + { + // start renumbering from the removed child onwards + for (int i = index; i < castedContainer->childCount(); ++i) + { + std::string numberString = std::to_string(i); + castedContainer->child(i)->setText(0, numberString.c_str()); + } + // This displays the number of children also when the list is collapsed + QString numElemsText = misc::generateNumElementsText(castedContainer->childCount()); + containerPtr->setText(1, numElemsText); + // set italic + auto currFont = castedContainer->font(1); + currFont.setItalic(true); + castedContainer->setFont(1, currFont); + } + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::ObjectPtr&) + { + addDeleteAction(); + } + + // lol + void + AronTreeWidgetContextMenuVisitor::addAddAction() + { + actions.emplace_back("Add element", contextMenuParent); + action_callbacks.push_back([this]() mutable { this->executeAddElement(); }); + } + + void + AronTreeWidgetContextMenuVisitor::executeAddElement() + { + AronTreeWidgetCreatorVisitor creator(parentItem); + aron::type::visit(creator, parentItem->aronType->getChildren()[0]); + + if (!creator.createdQWidgetItem) + { + throw std::runtime_error("Creation of TreeElementChild failed unexpectedly"); + } + // if it is a list, we update the number of children at the top + auto* castedContainer = AronTreeWidgetItem::DynamicCast(parentItem); + + // if the parent item is a List, we need to redo the numbers + if (castedContainer && + castedContainer->aronType->getDescriptor() == aron::type::Descriptor::LIST) + { + // This displays the number of children also when the list is collapsed + auto numElemsText = misc::generateNumElementsText(castedContainer->childCount()); + castedContainer->setText(1, numElemsText); + // set italic + auto currFont = castedContainer->font(1); + currFont.setItalic(true); + castedContainer->setFont(1, currFont); + } + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::DictPtr&) + { + addAddAction(); + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::PairPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::TuplePtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::ListPtr&) + { + addAddAction(); + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::NDArrayPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::MatrixPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::QuaternionPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::ImagePtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::PointCloudPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::IntEnumPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::IntPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::LongPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::FloatPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::DoublePtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::BoolPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitAronVariant(const aron::type::StringPtr&) + { + addDeleteAction(); + } + + void + AronTreeWidgetContextMenuVisitor::visitUnknown(Input&) + { + ARMARX_WARNING << "Tried to open Context menu on unknown aron type"; + } + + void + AronTreeWidgetContextMenuVisitor::showMenuAndExecute() + { + QMenu menu("Context Menu", contextMenuParent); + for (auto& el : actions) + { + menu.addAction(&el); + } + auto* chosenAction = menu.exec(contextMenuParent->mapToGlobal(pos)); + + if (!chosenAction) + { + return; + } + + // not elegant, but is a small loop anyway + auto it = actions.begin(); + size_t count = 0; + while (it != actions.end()) + { + if (chosenAction == &*it) + { + action_callbacks[count](); + break; + } + ++it; + ++count; + } + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetContextMenu.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetContextMenu.h new file mode 100644 index 0000000000000000000000000000000000000000..d83339329e6f69aa9769a2dc5b138fa0d0144ab6 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetContextMenu.h @@ -0,0 +1,67 @@ +#pragma once + + +#include <QAction> + +#include <RobotAPI/libraries/aron/core/data/variant/All.h> +#include <RobotAPI/libraries/aron/core/type/variant/All.h> +#include <RobotAPI/libraries/aron/core/type/visitor/variant/VariantVisitor.h> + +class QTreeWidget; +class QPoint; + +namespace armarx::skills::gui +{ + + class AronTreeWidgetItem; + + // Visitor on aron types. It creates a context menu dependent on the type of the AronTreeWidgetItem. + // Its only used for Lists and Dicts. + class AronTreeWidgetContextMenuVisitor : public armarx::aron::type::ConstVariantVisitor + { + + AronTreeWidgetItem* parentItem; + QTreeWidget* contextMenuParent; + const QPoint& pos; + int index; + + public: + AronTreeWidgetContextMenuVisitor() = delete; + AronTreeWidgetContextMenuVisitor(AronTreeWidgetItem* i, + const QPoint& pos, + QTreeWidget* contextMenuParent, + int x); + + void visitAronVariant(const aron::type::ObjectPtr&) final; + void visitAronVariant(const aron::type::DictPtr&) final; + void visitAronVariant(const aron::type::PairPtr&) final; + void visitAronVariant(const aron::type::TuplePtr&) final; + void visitAronVariant(const aron::type::ListPtr&) final; + void visitAronVariant(const aron::type::NDArrayPtr&) final; + void visitAronVariant(const aron::type::MatrixPtr&) final; + void visitAronVariant(const aron::type::QuaternionPtr&) final; + void visitAronVariant(const aron::type::ImagePtr&) final; + void visitAronVariant(const aron::type::PointCloudPtr&) final; + void visitAronVariant(const aron::type::IntEnumPtr&) final; + void visitAronVariant(const aron::type::IntPtr&) final; + void visitAronVariant(const aron::type::LongPtr&) final; + void visitAronVariant(const aron::type::FloatPtr&) final; + void visitAronVariant(const aron::type::DoublePtr&) final; + void visitAronVariant(const aron::type::BoolPtr&) final; + void visitAronVariant(const aron::type::StringPtr&) final; + void visitUnknown(Input&) final; + + void showMenuAndExecute(); + + private: + std::list<QAction> actions; + std::vector<std::function<void()>> action_callbacks; + + // Creates a remove option if the element is a direct child of a list or dict + void addDeleteAction(); + void executeDelete(); + + void addAddAction(); + void executeAddElement(); + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetConverter.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetConverter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..63fc8b0de060b4f79de3a06cb168e8ede1e4efc2 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetConverter.cpp @@ -0,0 +1,636 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * \package RobotAPI::gui-plugins::SkillManagerMonitorWidgetController + * \author Raphael Grimm ( raphael dot grimm at kit dot edu ) + * \date 2020 + * \copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#include <string> + +// base class +#include "AronTreeWidgetConverter.h" + +// armarx +#include <SimoxUtility/algorithm/string.h> + +#include <ArmarXCore/core/logging/Logging.h> + +#include "RobotAPI/libraries/aron/core/data/variant/All.h" + +// qt +#include <QTreeWidgetItem> + +#include "../widgets/EditMatrixWidget.h" +#include "../widgets/IntEnumWidget.h" +#include "../widgets/QuaternionWidget.h" + +namespace armarx::skills::gui +{ + bool + AronTreeWidgetConverterVisitor::isConversionSuccessful() + { + return !isDirectError && !hasTransitiveError; + } + + bool + AronTreeWidgetConverterVisitor::onlyChildFailedConversion() + { + return hasTransitiveError; + } + + bool + AronTreeWidgetConverterVisitor::hasDirectError() const + { + return isDirectError; + } + + void + AronTreeWidgetConverterVisitor::handleErrors(AronTreeWidgetConverterVisitor childV, + bool ownFault) + { + ARMARX_TRACE; + isDirectError |= ownFault; + hasTransitiveError |= childV.isDirectError || childV.hasTransitiveError; + + auto* aronItem = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + ARMARX_CHECK(aronItem); + aronItem->setValueErrorState(isDirectError, hasTransitiveError); + } + + void + AronTreeWidgetConverterVisitor::handleErrors(bool ownFault) + { + ARMARX_TRACE; + isDirectError = ownFault; + auto* aronItem = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + ARMARX_CHECK(aronItem); + aronItem->setValueErrorState(isDirectError, false); + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::ObjectPtr& i) + { + ARMARX_TRACE; + auto createdAronDict = std::make_shared<aron::data::Dict>(i->getPath()); + createdAron = createdAronDict; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + unsigned int x = 0; + for (const auto& [key, value] : i->getMemberTypes()) + { + ARMARX_TRACE; + AronTreeWidgetConverterVisitor v(el, x++); + aron::type::visit(v, value); + + handleErrors(v); + if (v.isConversionSuccessful()) + { + createdAronDict->addElement(key, v.createdAron); + } + } + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::DictPtr& i) + { + ARMARX_TRACE; + auto createdAronDict = std::make_shared<aron::data::Dict>(i->getPath()); + createdAron = createdAronDict; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + for (int x = 0; x < el->childCount(); ++x) + { + auto it = el->child(x); + AronTreeWidgetConverterVisitor v(el, x); + aron::type::visit(v, i->getAcceptedType()); + auto key = it->text(0).toStdString(); + // TODO: handle key errors more elegantly / separately, fine for now + handleErrors(v, createdAronDict->hasElement(key)); + if (v.createdAron && v.isConversionSuccessful() && !createdAronDict->hasElement(key)) + { + createdAronDict->addElement(key, v.createdAron); + } + } + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::ListPtr& i) + { + ARMARX_TRACE; + auto createdAronList = std::make_shared<aron::data::List>(i->getPath()); + createdAron = createdAronList; + auto* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + auto childrenTypes = i->getChildren(); + ARMARX_CHECK(childrenTypes.size() == 1); + for (int j = 0; j < el->childCount(); ++j) + { + AronTreeWidgetConverterVisitor convVisitor(el, j); + aron::type::visit(convVisitor, childrenTypes[0]); + handleErrors(convVisitor); + + if (convVisitor.createdAron && convVisitor.isConversionSuccessful()) + { + createdAronList->addElement(convVisitor.createdAron); + } + } + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::PairPtr& i) + { + ARMARX_TRACE; + auto createdAronPair = std::make_shared<aron::data::List>(i->getPath()); + createdAron = createdAronPair; + auto* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + for (int j = 0; j < 2; ++j) + { + AronTreeWidgetConverterVisitor convVisitor(el, j); + handleErrors(convVisitor); + if (convVisitor.createdAron && convVisitor.isConversionSuccessful()) + { + createdAronPair->addElement(convVisitor.createdAron); + } + } + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::TuplePtr& i) + { + ARMARX_TRACE; + auto createdAronList = std::make_shared<aron::data::List>(i->getPath()); + createdAron = createdAronList; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + for (int x = 0; x < el->childCount(); ++x) + { + auto* it = el->child(x); + AronTreeWidgetConverterVisitor v(it, x); + aron::type::visit(v, i->getAcceptedType(x)); + handleErrors(v); + + if (v.createdAron) + { + createdAronList->addElement(v.createdAron); + } + } + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::NDArrayPtr& i) + { + ARMARX_TRACE; + ARMARX_ERROR << "Currently do not support supplying raw NDArrays!"; + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::MatrixPtr& i) + { + ARMARX_TRACE; + auto createdMatrix = std::make_shared<aron::data::NDArray>(i->getPath()); + int dataSize = 0; + switch (i->getElementType()) + { + case armarx::aron::type::matrix::INT16: + dataSize = 2; + break; + case armarx::aron::type::matrix::INT32: + case armarx::aron::type::matrix::FLOAT32: + dataSize = 4; + break; + case armarx::aron::type::matrix::FLOAT64: + case armarx::aron::type::matrix::INT64: + dataSize = 8; + break; + }; + + // UGLY HACK: FIX ME!!! + switch (i->getElementType()) + { + case armarx::aron::type::matrix::INT16: + createdMatrix->setType("short"); + break; + case armarx::aron::type::matrix::INT32: + createdMatrix->setType("int"); + break; + case armarx::aron::type::matrix::FLOAT32: + createdMatrix->setType("float"); + break; + case armarx::aron::type::matrix::FLOAT64: + createdMatrix->setType("double"); + break; + case armarx::aron::type::matrix::INT64: + createdMatrix->setType("long"); + break; + }; + + createdMatrix->setShape({i->getRows(), i->getCols(), dataSize}); + int totalByteSize = i->getRows() * i->getCols() * dataSize; + createdAron = createdMatrix; + auto* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + + auto* rootWidget = el->treeWidget(); + ARMARX_CHECK(rootWidget); + auto* widget = rootWidget->itemWidget(el, 1); + auto* matrixWidget = EditMatrixWidget::DynamicCastAndCheck(widget); + + handleErrors(matrixWidget->hasParseErrors()); + if (matrixWidget->hasParseErrors()) + { + return; + } + // write to aron data + std::vector<unsigned char> elems; + elems.reserve(totalByteSize); + // CAUTION: Raw data has column based storage + for (size_t col = 0; col < (size_t)i->getCols(); ++col) + { + for (size_t row = 0; row < (size_t)i->getRows(); ++row) + { + // gets us directly the byte wise format + auto parsed = matrixWidget->parseElement(row, col); + // append vector to vector + elems.insert(elems.end(), parsed.begin(), parsed.end()); + } + } + createdMatrix->setData(totalByteSize, elems.data()); + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::QuaternionPtr& i) + { + ARMARX_TRACE; + auto createdQuat = std::make_shared<aron::data::NDArray>(i->getPath()); + createdAron = createdQuat; + int dataSize = i->getElementType() == aron::type::quaternion::ElementType::FLOAT32 ? 4 : 8; + createdQuat->setShape({1, 4, dataSize}); + createdQuat->setType(i->getFullName()); + auto* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + auto* itemWidget = el->treeWidget()->itemWidget(el, 1); + auto* quatWidget = QuaternionWidget::DynamicCastAndCheck(itemWidget); + + // error handling + handleErrors(quatWidget->hasParseErrors()); + if (quatWidget->hasParseErrors()) + { + return; + } + + // write to aron data + auto serialized = quatWidget->parseAllToNDArray(); + if ((int)serialized.size() != dataSize * 4) + { + ARMARX_ERROR + << "serialized quaternions did not return byte sequence of correct length!"; + } + createdQuat->setData(serialized.size(), serialized.data()); + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::ImagePtr& i) + { + ARMARX_TRACE; + // TODO + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::PointCloudPtr& i) + { + ARMARX_TRACE; + // TODO + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::IntEnumPtr& i) + { + ARMARX_TRACE; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + auto* genericWidget = el->treeWidget()->itemWidget(el, 1); + auto* intEnumWidget = IntEnumWidget::DynamicCastAndCheck(genericWidget); + if (!intEnumWidget) + { + // already reporting error; continue here + return; + } + bool success; + std::tie(success, createdAron) = intEnumWidget->parseToAron(); + + handleErrors(!success); + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::IntPtr& i) + { + ARMARX_TRACE; + auto createdAronInt = std::make_shared<aron::data::Int>(i->getPath()); + createdAron = createdAronInt; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + std::string str = el->text(1).toStdString(); + if (str.empty()) + { + createdAronInt->setValue(0); + return; + } + try + { + int val = simox::alg::to_<int>(str); + createdAronInt->setValue(val); + } + catch (const simox::error::SimoxError& err) + { + handleErrors(); + ARMARX_VERBOSE << "Conversion from String to Int failed. Error:\"" << err.what() + << "\""; + return; + } + handleErrors(false); + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::LongPtr& i) + { + ARMARX_TRACE; + auto createdAronLong = std::make_shared<aron::data::Long>(i->getPath()); + createdAron = createdAronLong; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + std::string str = el->text(1).toStdString(); + if (str.empty()) + { + //TODO: similar behaviour for rest? + str = el->text(3).toStdString(); + } + try + { + createdAronLong->setValue(simox::alg::to_<long>(str)); + } + catch (const simox::error::SimoxError& err) + { + handleErrors(); + ARMARX_VERBOSE << "Conversion from String to Long failed. Error:\"" << err.what() + << "\""; + return; + } + handleErrors(false); + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::FloatPtr& i) + { + ARMARX_TRACE; + auto createdAronFloat = std::make_shared<aron::data::Float>(i->getPath()); + createdAron = createdAronFloat; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + std::string str = el->text(1).toStdString(); + if (str.empty()) + { + str = el->text(3).toStdString(); + } + try + { + createdAronFloat->setValue(simox::alg::to_<float>(str)); + } + catch (const simox::error::SimoxError& err) + { + handleErrors(); + ARMARX_VERBOSE << "Conversion from String to Float failed. Error:\"" << err.what() + << "\""; + return; + } + handleErrors(false); + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::DoublePtr& i) + { + ARMARX_TRACE; + auto createdAronDouble = std::make_shared<aron::data::Double>(i->getPath()); + createdAron = createdAronDouble; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + std::string str = el->text(1).toStdString(); + if (str.empty()) + { + str = el->text(3).toStdString(); + } + try + { + createdAronDouble->setValue(simox::alg::to_<double>(str)); + } + catch (const simox::error::SimoxError& err) + { + handleErrors(); + ARMARX_VERBOSE << "Conversion from String to Double failed. Error:\"" << err.what() + << "\""; + return; + } + handleErrors(false); + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::BoolPtr& i) + { + ARMARX_TRACE; + auto createdAronBool = std::make_shared<aron::data::Bool>(i->getPath()); + createdAron = createdAronBool; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + std::string str = el->text(1).toStdString(); + if (str.empty()) + { + str = el->text(3).toStdString(); + } + try + { + createdAronBool->setValue(simox::alg::to_<bool>(str)); + } + catch (const simox::error::SimoxError& err) + { + handleErrors(); + ARMARX_VERBOSE << "Conversion from String to Bool failed. Error:\"" << err.what() + << "\""; + return; + } + handleErrors(false); + } + + void + AronTreeWidgetConverterVisitor::visitAronVariant(const aron::type::StringPtr& i) + { + ARMARX_TRACE; + auto createdAronString = std::make_shared<aron::data::String>(i->getPath()); + createdAron = createdAronString; + QTreeWidgetItem* el = parentItem->child(index); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + // its a maybetype. We have to check the state + if (el->checkState(1) == Qt::CheckState::Unchecked) + { + createdAron = nullptr; + return; + } + } + + std::string str = el->text(1).toStdString(); + createdAronString->setValue(str); + } + + void + AronTreeWidgetConverterVisitor::visitUnknown(Input&) + { + ARMARX_WARNING_S << "Received an unknown type when trying to convert a skill argument type " + "to an aron data object."; + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetConverter.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetConverter.h new file mode 100644 index 0000000000000000000000000000000000000000..ff16506dabdfdcda5d23e128fb8975e435bb8158 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetConverter.h @@ -0,0 +1,84 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @package RobotAPI::gui-plugins::SkillManagerMonitorWidgetController + * @author Raphael Grimm ( raphael dot grimm at kit dot edu ) + * @date 2020 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ +#pragma once + +#include <RobotAPI/libraries/aron/core/data/variant/All.h> +#include <RobotAPI/libraries/aron/core/type/variant/All.h> +#include <RobotAPI/libraries/aron/core/type/visitor/variant/VariantVisitor.h> + +// forward declarations of qt +class QTreeWidgetItem; + +namespace armarx::skills::gui +{ + // Conversion from TreeView to aron data + class AronTreeWidgetConverterVisitor : public armarx::aron::type::ConstVariantVisitor + { + public: + QTreeWidgetItem* parentItem; + int index; + aron::data::VariantPtr createdAron = nullptr; + + AronTreeWidgetConverterVisitor() = delete; + + AronTreeWidgetConverterVisitor(QTreeWidgetItem* i, int x) : parentItem(i), index(x) + { + } + + // if the conversion was successful after calling visit() + bool isConversionSuccessful(); + // returns true if this type itself was sucessfully parsed, but some contained object failed. + // also false if there is no error + bool onlyChildFailedConversion(); + + bool hasDirectError() const; + + private: + bool isDirectError = false; + bool hasTransitiveError = false; + // adds all errors from other visitor to our own error collection -> collecting errors + // with ownFault, we also add this node to the collection + void handleErrors(AronTreeWidgetConverterVisitor childV, bool ownFault = false); + // we are the cause... + void handleErrors(bool ownFault = true); + + public: + void visitAronVariant(const aron::type::ObjectPtr&) final; + void visitAronVariant(const aron::type::DictPtr&) final; + void visitAronVariant(const aron::type::PairPtr&) final; + void visitAronVariant(const aron::type::TuplePtr&) final; + void visitAronVariant(const aron::type::ListPtr&) final; + void visitAronVariant(const aron::type::NDArrayPtr&) final; + void visitAronVariant(const aron::type::MatrixPtr&) final; + void visitAronVariant(const aron::type::QuaternionPtr&) final; + void visitAronVariant(const aron::type::ImagePtr&) final; + void visitAronVariant(const aron::type::PointCloudPtr&) final; + void visitAronVariant(const aron::type::IntEnumPtr&) final; + void visitAronVariant(const aron::type::IntPtr&) final; + void visitAronVariant(const aron::type::LongPtr&) final; + void visitAronVariant(const aron::type::FloatPtr&) final; + void visitAronVariant(const aron::type::DoublePtr&) final; + void visitAronVariant(const aron::type::BoolPtr&) final; + void visitAronVariant(const aron::type::StringPtr&) final; + void visitUnknown(Input&) final; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetCreator.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetCreator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..037c8dab99d890443224e1491d25337dd901c085 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetCreator.cpp @@ -0,0 +1,354 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * \package RobotAPI::gui-plugins::SkillManagerMonitorWidgetController + * \author Raphael Grimm ( raphael dot grimm at kit dot edu ) + * \date 2020 + * \copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#include <string> + +#include "../widgets/EditMatrixWidget.h" +#include "../widgets/IntEnumWidget.h" +#include "../widgets/QuaternionWidget.h" + +// base class +#include "AronTreeWidgetCreator.h" + +// data +#include <QComboBox> + +#include "../AronTreeWidgetItem.h" +#include "../Data.h" +#include "../ListDictHelper.h" +#include "AronTreeWidgetContextMenu.h" + +namespace armarx::skills::gui +{ + + AronTreeWidgetCreatorVisitor::AronTreeWidgetCreatorVisitor(QTreeWidgetItem* parentInstance) : + parentOfCreatedObj(parentInstance) + { + // The parent of the root aron Tree Widget will not be aron type. + // there an explicit setTopLevelWidget() is required. + auto* aronParent = AronTreeWidgetItem::DynamicCast(parentInstance); + if (aronParent) + { + toplevelWidget = aronParent->treeWidget(); + } + } + + std::string + AronTreeWidgetCreatorVisitor::generateUniqueKeyFromSet(std::set<std::string>&& usedKeys) + { + size_t num = 0; + while (true) + { + std::string proposedName = this->defaultMapKeyName + std::to_string(num); + auto it = usedKeys.find(proposedName); + if (it == usedKeys.end()) + { + break; + } + ++num; + } + return this->defaultMapKeyName + std::to_string(num); + } + + void + AronTreeWidgetCreatorVisitor::insertNewTreeViewWidget(Input& i, const std::string& defaul) + { + ARMARX_CHECK_NOT_NULL(i); + + auto key = i->getPath().getLastElement(); + bool isDictChild = false; + // edit key, to be a unique string, if the parent is a dict + auto* aronParent = AronTreeWidgetItem::DynamicCast(parentOfCreatedObj); + if (aronParent && aronParent->aronType->getDescriptor() == aron::type::Descriptor::DICT) + { + isDictChild = true; + std::set<std::string> usedKeys; + for (int i = 0; i < parentOfCreatedObj->childCount(); ++i) + { + auto* sibling = AronTreeWidgetItem::DynamicCast(parentOfCreatedObj->child(i)); + if (sibling) + { + usedKeys.insert(sibling->text(0).toStdString()); + } + } + key = generateUniqueKeyFromSet(std::move(usedKeys)); + } + // if it's a list -> choose the right number + else if (aronParent && + aronParent->aronType->getDescriptor() == aron::type::Descriptor::LIST) + { + key = std::to_string(parentOfCreatedObj->childCount()); + } + + createdQWidgetItem = + new AronTreeWidgetItem(isDictChild, editableValue, QString::fromStdString(key), i); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + createdQWidgetItem->setCheckState(1, Qt::CheckState::Unchecked); + } + createdQWidgetItem->setText(1, QString::fromStdString(defaul)); + createdQWidgetItem->setText(2, QString::fromStdString(i->getShortName())); + createdQWidgetItem->setText( + 3, + QString::fromStdString( + aron_tree_widget::constantes:: + ITEM_EMPTY_MESSAGE) /*QString::fromStdString(i->getDefaultFromString())*/); + + if (editableValue || isDictChild) + { + createdQWidgetItem->setFlags(createdQWidgetItem->flags() | Qt::ItemIsEditable); + } + parentOfCreatedObj->addChild(createdQWidgetItem); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::ObjectPtr& i) + { + ARMARX_CHECK_NOT_NULL(i); + + auto key = i->getObjectName(); + if (i->getPath().hasElement()) + { + key = i->getPath().getLastElement(); + } + + createdQWidgetItem = + new AronTreeWidgetItem(editableValue, false, QString::fromStdString(key), i); + + if (i->getMaybe() != armarx::aron::type::Maybe::NONE) + { + createdQWidgetItem->setCheckState(1, Qt::CheckState::Unchecked); + } + + parentOfCreatedObj->addChild(createdQWidgetItem); + + for (const auto& [key, value] : i->getMemberTypes()) + { + AronTreeWidgetCreatorVisitor v(createdQWidgetItem); + aron::type::visit(v, value); + + assert(v.createdQWidgetItem); + } + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::DictPtr& i) + { + ARMARX_CHECK_NOT_NULL(i); + + insertNewTreeViewWidget(i, ""); + // The DictType has only one member, its key-type. This also must be present + ARMARX_CHECK_EQUAL(i->getChildren().size(), 1); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::PairPtr& pair) + { + // create default, uneditable tree widget item. + insertNewTreeViewWidget(pair, ""); + // attach two children + ARMARX_CHECK(pair->getChildren().size() == 2); + for (size_t i = 0; i < 2; ++i) + { + AronTreeWidgetCreatorVisitor v(createdQWidgetItem); + aron::type::visit(v, pair->getChildren()[i]); + if (v.createdQWidgetItem) + { + std::string descr = "p[" + std::to_string(i) + "]"; + v.createdQWidgetItem->setText(0, descr.c_str()); + } + } + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::TuplePtr& tuple) + { + // CAUTION; UNTESTED + // create default, uneditable tree widget item. + insertNewTreeViewWidget(tuple, ""); + // attach all children + for (size_t i = 0; i < tuple->getChildren().size(); ++i) + { + AronTreeWidgetCreatorVisitor v(createdQWidgetItem); + aron::type::visit(v, tuple->getChildren()[i]); + if (v.createdQWidgetItem) + { + std::string descr = "tup[" + std::to_string(i) + "]"; + v.createdQWidgetItem->setText(0, descr.c_str()); + } + } + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::ListPtr& i) + { + insertNewTreeViewWidget(i, ""); + auto txt = misc::generateNumElementsText(0); + createdQWidgetItem->setText(1, txt); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::NDArrayPtr& i) + { + insertNewTreeViewWidget(i, ""); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::MatrixPtr& i) + { + editableValue = false; + insertNewTreeViewWidget(i, ""); + // special code to print the type of base type used + QString type = ""; + switch (i->getElementType()) + { + case armarx::aron::type::matrix::INT16: + type = "<int16>"; + break; + case armarx::aron::type::matrix::INT32: + type = "<int32>"; + break; + case armarx::aron::type::matrix::INT64: + type = "<int64>"; + break; + case armarx::aron::type::matrix::FLOAT32: + type = "<float>"; + break; + case armarx::aron::type::matrix::FLOAT64: + type = "<double>"; + break; + } + type = createdQWidgetItem->text(2) + type; + createdQWidgetItem->setText(2, type); + + // Widget fiddling source: https://blog.manash.io/quick-qt-6-how-to-add-qpushbutton-or-widgets-to-a-qtreewidget-as-qtreewidgetitem-2ae9f54c0e5f + // overlay custom widget in column 1 + auto* toplevelWidget = createdQWidgetItem->treeWidget(); + EditMatrixWidget* matWidget = new EditMatrixWidget( + i->getRows(), i->getCols(), i->getElementType(), createdQWidgetItem); + toplevelWidget->setItemWidget(createdQWidgetItem, 1, matWidget); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::QuaternionPtr& i) + { + editableValue = false; + insertNewTreeViewWidget(i, ""); + // special code to print the type of base type used + QString type = ""; + switch (i->getElementType()) + { + case armarx::aron::type::quaternion::FLOAT32: + type = "<float>"; + break; + case armarx::aron::type::quaternion::FLOAT64: + type = "<double>"; + break; + } + type = createdQWidgetItem->text(2) + type; + createdQWidgetItem->setText(2, type); + + auto* toplevelWidget = createdQWidgetItem->treeWidget(); + QuaternionWidget* quatWidget = + new QuaternionWidget(i->getElementType(), createdQWidgetItem); + toplevelWidget->setItemWidget(createdQWidgetItem, 1, quatWidget); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::ImagePtr& i) + { + insertNewTreeViewWidget(i, ""); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::PointCloudPtr& i) + { + insertNewTreeViewWidget(i, ""); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::IntEnumPtr& i) + { + editableValue = false; + ARMARX_CHECK_NOT_NULL(i); + ARMARX_CHECK_GREATER(i->getAcceptedValueNames().size(), 0); + + insertNewTreeViewWidget(i, ""); + IntEnumWidget* widget = new IntEnumWidget(i, createdQWidgetItem); + createdQWidgetItem->treeWidget()->setItemWidget(createdQWidgetItem, 1, widget); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::IntPtr& i) + { + editableValue = true; + insertNewTreeViewWidget(i, "0"); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::LongPtr& i) + { + editableValue = true; + insertNewTreeViewWidget(i, "0"); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::FloatPtr& i) + { + editableValue = true; + insertNewTreeViewWidget(i, "0.0"); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::DoublePtr& i) + { + editableValue = true; + insertNewTreeViewWidget(i, "0.0"); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::BoolPtr& i) + { + editableValue = true; + insertNewTreeViewWidget(i, "false"); + } + + void + AronTreeWidgetCreatorVisitor::visitAronVariant(const aron::type::StringPtr& i) + { + insertNewTreeViewWidget(i, ""); + } + + void + AronTreeWidgetCreatorVisitor::visitUnknown(Input&) + { + ARMARX_WARNING_S << "Received an unknown type when trying to create a tree view widget for " + "a skill argument type."; + } + + void + AronTreeWidgetCreatorVisitor::setTopLevelWidget(QTreeWidget* widget) + { + toplevelWidget = widget; + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetCreator.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetCreator.h new file mode 100644 index 0000000000000000000000000000000000000000..ee3943dcd481d28abce4ef58d6f3e87956002cd8 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetCreator.h @@ -0,0 +1,81 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @package RobotAPI::gui-plugins::SkillManagerMonitorWidgetController + * @author Raphael Grimm ( raphael dot grimm at kit dot edu ) + * @date 2020 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ +#pragma once + +#include <QTreeWidgetItem> + +#include <RobotAPI/libraries/aron/core/data/variant/All.h> +#include <RobotAPI/libraries/aron/core/type/variant/All.h> +#include <RobotAPI/libraries/aron/core/type/visitor/variant/VariantVisitor.h> + +namespace armarx::skills::gui +{ + class AronTreeWidgetItem; + + // Convert aron type to tree widgets + class AronTreeWidgetCreatorVisitor : public armarx::aron::type::ConstVariantVisitor + { + public: + AronTreeWidgetItem* createdQWidgetItem = nullptr; + + AronTreeWidgetCreatorVisitor() = delete; + + // Takes the parent tree element and attaches the newly crated object during visit() to it. + // This allows us to also use information of the parent object during creation (like if this child is a dict entry or part of a list) + // IMPORTANT: For the root element, manually set the topLevelWidget after this constructor + AronTreeWidgetCreatorVisitor(QTreeWidgetItem* parentInstance); + + void insertNewTreeViewWidget(Input& i, const std::string&); + + + void visitAronVariant(const aron::type::ObjectPtr&) final; + void visitAronVariant(const aron::type::DictPtr& i) final; + void visitAronVariant(const aron::type::PairPtr& i) final; + void visitAronVariant(const aron::type::TuplePtr& i) final; + void visitAronVariant(const aron::type::ListPtr& i) final; + void visitAronVariant(const aron::type::NDArrayPtr& i) final; + void visitAronVariant(const aron::type::MatrixPtr& i) final; + void visitAronVariant(const aron::type::QuaternionPtr& i) final; + void visitAronVariant(const aron::type::ImagePtr& i) final; + void visitAronVariant(const aron::type::PointCloudPtr& i) final; + void visitAronVariant(const aron::type::IntEnumPtr& i) final; + void visitAronVariant(const aron::type::IntPtr& i) final; + void visitAronVariant(const aron::type::LongPtr& i) final; + void visitAronVariant(const aron::type::FloatPtr& i) final; + void visitAronVariant(const aron::type::DoublePtr& i) final; + void visitAronVariant(const aron::type::BoolPtr& i) final; + void visitAronVariant(const aron::type::StringPtr& i) final; + void visitUnknown(Input&) final; + + // setter for the widget attachment point. Only needs to be manually set for the root element. + void setTopLevelWidget(QTreeWidget* widget); + + private: + void handleEditable(); + std::string generateUniqueKeyFromSet(std::set<std::string>&& usedKeys); + QTreeWidgetItem* parentOfCreatedObj = nullptr; + QTreeWidget* toplevelWidget = nullptr; + const std::string defaultMapKeyName = "key_"; + // controls, if values (column 1) can be edited directly + bool editableValue = false; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetModalCreator.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetModalCreator.cpp new file mode 100644 index 0000000000000000000000000000000000000000..81a75a5da145e85c1f39c84c49dc8bf1372a21a5 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetModalCreator.cpp @@ -0,0 +1,53 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * \package RobotAPI::gui-plugins::SkillManagerMonitorWidgetController + * \author Raphael Grimm ( raphael dot grimm at kit dot edu ) + * \date 2020 + * \copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#include "AronTreeWidgetModalCreator.h" + +#include <string> + +#include <SimoxUtility/algorithm/string.h> + +// modals +#include "../modal/text/AronTreeWidgetTextInputModalController.h" +//#include "../modal/dict/AronTreeWidgetDictInputModalController.h" + +// qt +#include <QTreeWidget> + +//visitors +namespace armarx::skills::gui +{ + + void + AronTreeWidgetModalCreatorVisitor::visitAronVariant(const aron::type::StringPtr& i) + { + createdModal = + std::make_shared<AronTreeWidgetTextInputModalController>(label, item, parent); + } + + void + AronTreeWidgetModalCreatorVisitor::visitUnknown(Input&) + { + ARMARX_WARNING_S << "Received an unknown type when trying to create a tree view widget " + "modal for a skill argument type."; + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetModalCreator.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetModalCreator.h new file mode 100644 index 0000000000000000000000000000000000000000..413ea9bcff79e8f650a00b66d12165d6832a64db --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetModalCreator.h @@ -0,0 +1,55 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @package RobotAPI::gui-plugins::SkillManagerMonitorWidgetController + * @author Raphael Grimm ( raphael dot grimm at kit dot edu ) + * @date 2020 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ +#pragma once + +#include <RobotAPI/libraries/aron/core/data/variant/All.h> +#include <RobotAPI/libraries/aron/core/type/variant/All.h> +#include <RobotAPI/libraries/aron/core/type/visitor/variant/VariantVisitor.h> + +#include "../modal/AronTreeWidgetModal.h" + +namespace armarx::skills::gui +{ + // Convert aron type to tree widget. + // (Widgets are only created for string types to enter longer texts. + // However, the visitor implementation allows modals for differnt types. Might be useful in the future..) + class AronTreeWidgetModalCreatorVisitor : public armarx::aron::type::ConstVariantVisitor + { + public: + std::string label = ""; + AronTreeWidgetItem* item = nullptr; + QTreeWidget* parent = nullptr; + AronTreeWidgetModalControllerPtr createdModal = nullptr; + + AronTreeWidgetModalCreatorVisitor() = delete; + + AronTreeWidgetModalCreatorVisitor(const std::string& label, + AronTreeWidgetItem* item, + QTreeWidget* parent) : + label(label), item(item), parent(parent) + { + } + + void visitAronVariant(const aron::type::StringPtr& i) final; + void visitUnknown(Input&) final; + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetSetter.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetSetter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1ae9dbc9d8aa99ae0e0eabfb489a29dfdf0a8d78 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetSetter.cpp @@ -0,0 +1,403 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * \package RobotAPI::gui-plugins::SkillManagerMonitorWidgetController + * \author Raphael Grimm ( raphael dot grimm at kit dot edu ) + * \date 2020 + * \copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ + +#include "AronTreeWidgetSetter.h" + +#include <string> + +#include "../ListDictHelper.h" +#include "../widgets/EditMatrixWidget.h" +#include "../widgets/IntEnumWidget.h" +#include "../widgets/QuaternionWidget.h" +#include "AronTreeWidgetContextMenu.h" +#include "AronTreeWidgetCreator.h" + +template <typename T> +std::string +usString(T number, size_t precision = 3) +{ + std::stringstream ss; + const char* locale = "C"; + ss.imbue(std::locale(locale)); + + ss << std::fixed << std::setprecision(precision) << number; + return ss.str(); +} + +//visitors +namespace armarx::skills::gui +{ + bool + AronTreeWidgetSetterVisitor::checkTreeWidgetItemForSimilarName(const std::string& name) const + { + QTreeWidgetItem* el = parentItem->child(index); + + // do not check attribute name, if the element is part of a list or map + auto* castedThis = AronTreeWidgetItem::DynamicCast(el->parent()); + if (castedThis) + { + auto descr = castedThis->aronType->getDescriptor(); + if (descr == aron::type::Descriptor::LIST || descr == aron::type::Descriptor::DICT) + { + return true; + } + } + std::string n = el->text(0).toStdString(); + if (name != n) + { + ARMARX_WARNING_S << "Could not set a tree widget value for the element with key '" + << name << "' because it is different from the expected name '" << n + << "'."; + return false; + } + return true; + } + + void + AronTreeWidgetSetterVisitor::adjustNumberOfChildren(AronTreeWidgetItem* parent, + size_t numChildren) + { + if (((size_t)parent->childCount()) < numChildren) + { + // The type to create must be the only child of the current aron type + ARMARX_CHECK_EQUAL(parent->aronType->childrenSize(), 1); + size_t childrenToAdd = numChildren - parent->childCount(); + for (size_t j = 0; j < childrenToAdd; ++j) + { + AronTreeWidgetCreatorVisitor childCreator(parent); + aron::type::visit(childCreator, parent->aronType->getChildren()[0]); + ARMARX_CHECK_NOT_NULL(childCreator.createdQWidgetItem); + } + } + else if ((size_t)parent->childCount() > numChildren) + { + size_t numChilds = (size_t)parent->childCount() - numChildren; + // pop the last child + for (size_t j = 0; j < numChilds; ++j) + { + parent->removeChild(parent->child(parent->childCount() - 1)); + } + } + } + + void + AronTreeWidgetSetterVisitor::visitAronVariant(const aron::data::DictPtr& i) + { + // either it is the root or it has a name + if (i->getPath().size() == 0 || + checkTreeWidgetItemForSimilarName(i->getPath().getLastElement())) + { + AronTreeWidgetItem* el = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + auto* aronTreeWidget = AronTreeWidgetItem::DynamicCastAndCheck(el); + // allocate enough child items + adjustNumberOfChildren(aronTreeWidget, i->childrenSize()); + + // write child values + unsigned int x = 0; + for (const auto& [key, value] : i->getElements()) + { + el->child(x)->setText(0, {key.c_str()}); + + AronTreeWidgetSetterVisitor v(el, x++); + aron::data::visit(v, value); + } + + if (el->aronType && el->aronType->getMaybe() != armarx::aron::type::Maybe::NONE) + { + el->setCheckState(1, Qt::CheckState::Checked); + } + } + } + + void + AronTreeWidgetSetterVisitor::visitAronVariant(const aron::data::ListPtr& i) + { + if (checkTreeWidgetItemForSimilarName(i->getPath().getLastElement())) + { + AronTreeWidgetItem* el = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + auto* aronTreeWidget = AronTreeWidgetItem::DynamicCastAndCheck(el); + adjustNumberOfChildren(aronTreeWidget, i->childrenSize()); + + unsigned int x = 0; + for (const auto& value : i->getElements()) + { + AronTreeWidgetSetterVisitor v(el, x); + aron::data::visit(v, value); + auto* currChild = el->child(x); + std::string listNum = usString(x); + currChild->setText(0, listNum.c_str()); + + ++x; + } + // This displays the number of children also when the list is collapsed + QString numElemsText = misc::generateNumElementsText(i->getElements().size()); + aronTreeWidget->setText(1, numElemsText); + // set italic + auto currFont = aronTreeWidget->font(1); + currFont.setItalic(true); + aronTreeWidget->setFont(1, currFont); + + if (el->aronType && el->aronType->getMaybe() != armarx::aron::type::Maybe::NONE) + { + el->setCheckState(1, Qt::CheckState::Checked); + } + } + } + + void + visitMatrix(EditMatrixWidget* matrixWidget, + const std::shared_ptr<armarx::aron::type::Matrix>& matrixType, + const aron::data::NDArrayPtr& arr) + { + auto elemType = matrixType->getElementType(); + auto* rawData = arr->getData(); + // string can convert any item + auto toString = [elemType, rawData](size_t elementNr) -> std::string + { + switch (elemType) + { + case aron::type::matrix::ElementType::FLOAT32: + { + static_assert(sizeof(float) == 4); + float* interpreted = reinterpret_cast<float*>(rawData); + float laundered = std::launder(interpreted)[elementNr]; + return usString<float>(laundered); + } + case aron::type::matrix::ElementType::FLOAT64: + { + static_assert(sizeof(double) == 8); + double* interpreted = reinterpret_cast<double*>(rawData); + float laundered = std::launder(interpreted)[elementNr]; + return usString<double>(laundered); + } + case aron::type::matrix::ElementType::INT16: + { + int16_t* interpreted = reinterpret_cast<int16_t*>(rawData); + int16_t laudered = std::launder(interpreted)[elementNr]; + return usString<int16_t>(laudered); + } + case aron::type::matrix::ElementType::INT32: + { + int32_t* interpreted = reinterpret_cast<int32_t*>(rawData); + int32_t laudered = std::launder(interpreted)[elementNr]; + return usString<int32_t>(laudered); + } + case aron::type::matrix::ElementType::INT64: + { + int64_t* interpreted = reinterpret_cast<int64_t*>(rawData); + int64_t laudered = std::launder(interpreted)[elementNr]; + return usString<int64_t>(laudered); + } + } + return "Error!"; + }; + + + for (size_t row = 0; (int)row < matrixType->getRows(); ++row) + { + for (size_t col = 0; (int)col < matrixType->getCols(); ++col) + { + matrixWidget->setText(row, col, toString(col * matrixType->getRows() + row)); + } + } + } + + void + visitQuaternion(QuaternionWidget* quatWidget, + std::shared_ptr<armarx::aron::type::Quaternion>& quatType, + const aron::data::NDArrayPtr& arr) + { + auto elemType = quatType->getElementType(); + auto rawData = arr->getData(); + auto shape = arr->getShape(); + // string can convert any item + auto toString = [elemType, rawData](size_t elementNr) -> std::string + { + switch (elemType) + { + case aron::type::quaternion::ElementType::FLOAT32: + { + static_assert(sizeof(float) == 4); + float* interpreted = reinterpret_cast<float*>(rawData); + float laundered = std::launder(interpreted)[elementNr]; + return usString<float>(laundered); + } + case aron::type::quaternion::ElementType::FLOAT64: + { + static_assert(sizeof(double) == 8); + double* interpreted = reinterpret_cast<double*>(rawData); + + float laundered = std::launder(interpreted)[elementNr]; + return usString<double>(laundered); + } + } + return "Error!"; + }; + for (size_t i = 0; i < 4; ++i) + { + quatWidget->setText((QuaternionWidget::QuaternionComponents)i, toString(i)); + } + } + + void + AronTreeWidgetSetterVisitor::visitAronVariant(const aron::data::NDArrayPtr& arr) + { + // Matrices are handled as NDArray. Raw ndarrays cannot be created currently + auto* el = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + ARMARX_CHECK(el); + + auto matrixCast = aron::type::Matrix::DynamicCast(el->aronType); + auto quaternionCast = aron::type::Quaternion::DynamicCast(el->aronType); + + auto* rootWidget = el->treeWidget(); + ARMARX_CHECK(rootWidget); + auto* matrixWidget = EditMatrixWidget::DynamicCast(rootWidget->itemWidget(el, 1)); + auto* quaternionWidget = QuaternionWidget::DynamicCast(rootWidget->itemWidget(el, 1)); + + if (matrixCast && matrixWidget) + { + visitMatrix(matrixWidget, matrixCast, arr); + } + else if (quaternionCast && quaternionWidget) + { + visitQuaternion(quaternionWidget, quaternionCast, arr); + } + else + { + ARMARX_ERROR + << "we do not support raw NDArrays. Ask Fabian Peller for more information."; + } + + if (el->aronType && el->aronType->getMaybe() != armarx::aron::type::Maybe::NONE) + { + el->setCheckState(1, Qt::CheckState::Checked); + } + } + + void + AronTreeWidgetSetterVisitor::visitAronVariant(const aron::data::IntPtr& i) + { + if (checkTreeWidgetItemForSimilarName(i->getPath().getLastElement())) + { + AronTreeWidgetItem* el = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + auto* enumWidget = IntEnumWidget::DynamicCast(el->treeWidget()->itemWidget(el, 1)); + auto newText = QString::fromStdString(usString<int>(i->getValue())); + if (enumWidget) + { + // Its an IntEnum! -> Ask the custom widget + enumWidget->setText(newText); + } + else + { + // Its just an int. -> do the QTreeWidgetItem call + el->setText(1, newText); + } + + if (el->aronType && el->aronType->getMaybe() != armarx::aron::type::Maybe::NONE) + { + el->setCheckState(1, Qt::CheckState::Checked); + } + } + } + + void + AronTreeWidgetSetterVisitor::visitAronVariant(const aron::data::LongPtr& i) + { + if (checkTreeWidgetItemForSimilarName(i->getPath().getLastElement())) + { + AronTreeWidgetItem* el = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + el->setText(1, QString::fromStdString(usString<long>(i->getValue()))); + + if (el->aronType && el->aronType->getMaybe() != armarx::aron::type::Maybe::NONE) + { + el->setCheckState(1, Qt::CheckState::Checked); + } + } + } + + void + AronTreeWidgetSetterVisitor::visitAronVariant(const aron::data::FloatPtr& i) + { + if (checkTreeWidgetItemForSimilarName(i->getPath().getLastElement())) + { + AronTreeWidgetItem* el = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + el->setText(1, QString::fromStdString(usString<float>(i->getValue()))); + + if (el->aronType && el->aronType->getMaybe() != armarx::aron::type::Maybe::NONE) + { + el->setCheckState(1, Qt::CheckState::Checked); + } + } + } + + void + AronTreeWidgetSetterVisitor::visitAronVariant(const aron::data::DoublePtr& i) + { + if (checkTreeWidgetItemForSimilarName(i->getPath().getLastElement())) + { + AronTreeWidgetItem* el = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + el->setText(1, QString::fromStdString(usString<double>(i->getValue()))); + + if (el->aronType && el->aronType->getMaybe() != armarx::aron::type::Maybe::NONE) + { + el->setCheckState(1, Qt::CheckState::Checked); + } + } + } + + void + AronTreeWidgetSetterVisitor::visitAronVariant(const aron::data::BoolPtr& i) + { + if (checkTreeWidgetItemForSimilarName(i->getPath().getLastElement())) + { + AronTreeWidgetItem* el = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + el->setText(1, QString::fromStdString(usString<bool>(i->getValue()))); + + if (el->aronType && el->aronType->getMaybe() != armarx::aron::type::Maybe::NONE) + { + el->setCheckState(1, Qt::CheckState::Checked); + } + } + } + + void + AronTreeWidgetSetterVisitor::visitAronVariant(const aron::data::StringPtr& i) + { + if (checkTreeWidgetItemForSimilarName(i->getPath().getLastElement())) + { + AronTreeWidgetItem* el = AronTreeWidgetItem::DynamicCast(parentItem->child(index)); + el->setText(1, QString::fromStdString(i->getValue())); + + if (el->aronType && el->aronType->getMaybe() != armarx::aron::type::Maybe::NONE) + { + el->setCheckState(1, Qt::CheckState::Checked); + } + } + } + + void + AronTreeWidgetSetterVisitor::visitUnknown(Input&) + { + ARMARX_WARNING_S << "Received an unknown type when trying to set a skill argument type " + "from an aron data object."; + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetSetter.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetSetter.h new file mode 100644 index 0000000000000000000000000000000000000000..010b1e2e20a3e8850707fe55a97bc1d859f53236 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/visitors/AronTreeWidgetSetter.h @@ -0,0 +1,65 @@ +/* + * This file is part of ArmarX. + * + * ArmarX is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * ArmarX is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * @package RobotAPI::gui-plugins::SkillManagerMonitorWidgetController + * @author Raphael Grimm ( raphael dot grimm at kit dot edu ) + * @date 2020 + * @copyright http://www.gnu.org/licenses/gpl-2.0.txt + * GNU General Public License + */ +#pragma once + +#include <stack> + +#include <RobotAPI/libraries/aron/core/data/variant/All.h> +#include <RobotAPI/libraries/aron/core/data/visitor/variant/VariantVisitor.h> +#include <RobotAPI/libraries/aron/core/type/variant/All.h> + +#include "../AronTreeWidgetItem.h" + +namespace armarx::skills::gui +{ + // Conversion from aron data to Tree View data + // This should be used to programatically change data to be displayed in the GUI. + // This code will not be called, if changes are made on the GUI side. + class AronTreeWidgetSetterVisitor : public armarx::aron::data::ConstVariantVisitor + { + public: + QTreeWidgetItem* parentItem; + int index; + AronTreeWidgetItem* qWidgetItem; + + AronTreeWidgetSetterVisitor() = delete; + + AronTreeWidgetSetterVisitor(QTreeWidgetItem* i, int x) : parentItem(i), index(x) + { + } + + virtual void visitAronVariant(const aron::data::DictPtr&) final; + virtual void visitAronVariant(const aron::data::ListPtr&) final; + virtual void visitAronVariant(const aron::data::NDArrayPtr&) final; + virtual void visitAronVariant(const aron::data::IntPtr&) final; + virtual void visitAronVariant(const aron::data::LongPtr&) final; + virtual void visitAronVariant(const aron::data::FloatPtr&) final; + virtual void visitAronVariant(const aron::data::DoublePtr&) final; + virtual void visitAronVariant(const aron::data::BoolPtr&) final; + virtual void visitAronVariant(const aron::data::StringPtr&) final; + void visitUnknown(Input&) final; + + private: + bool checkTreeWidgetItemForSimilarName(const std::string& name) const; + void adjustNumberOfChildren(AronTreeWidgetItem* parent, size_t numChildren); + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/CustomWidget.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/CustomWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e8ebdb950ce55318b868c124124880e1228ef901 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/CustomWidget.cpp @@ -0,0 +1,42 @@ +#include "CustomWidget.h" + +#include "ArmarXCore/core/exceptions/local/ExpressionException.h" + +namespace armarx::skills::gui +{ + CustomWidget::CustomWidget(QTreeWidgetItem* overlayingItem) : overlayingItem(overlayingItem) + { + // connect to general QTreeWidgetItem callback. + // This also lets the conversion start for other objects. (Not just this widget) + ARMARX_CHECK(connect(this, + SIGNAL(elemChanged(QTreeWidgetItem*, int)), + overlayingItem->treeWidget(), + SIGNAL(itemChanged(QTreeWidgetItem*, int)))); + } + + CustomWidget* + CustomWidget::DynamicCast(QWidget* elem) + { + return dynamic_cast<CustomWidget*>(elem); + } + + CustomWidget* + CustomWidget::DynamicCastAndCheck(QWidget* elem) + { + if (!elem) + { + return nullptr; + } + auto* casted = DynamicCast(elem); + ARMARX_CHECK_NOT_NULL(casted); + return casted; + } + + void + CustomWidget::setSupressSignals(bool doSupress) + { + QObject::blockSignals(doSupress); + } + + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/CustomWidget.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/CustomWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..16b397116351f31d510a9871b28fb1066d2f4b01 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/CustomWidget.h @@ -0,0 +1,26 @@ +#pragma once + +#include <QTreeWidgetItem> +#include <QWidget> + +namespace armarx::skills::gui +{ + + class CustomWidget : public QWidget + { + Q_OBJECT + + public: + CustomWidget(QTreeWidgetItem* overlayingItem); + static CustomWidget* DynamicCast(QWidget*); + static CustomWidget* DynamicCastAndCheck(QWidget*); + + virtual void setSupressSignals(bool doSupress); + + protected: + QTreeWidgetItem* overlayingItem; + + signals: + void elemChanged(QTreeWidgetItem* elem, int col); + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/EditMatrixWidget.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/EditMatrixWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c40c0821533ad3ce24d23dcc76611f4a5e1cb3f3 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/EditMatrixWidget.cpp @@ -0,0 +1,252 @@ +#include "EditMatrixWidget.h" + +#include <QGridLayout> +#include <QLabel> + +#include "ArmarXCore/core/exceptions/local/ExpressionException.h" +#include "ArmarXCore/core/logging/Logging.h" + +#include "../ColorPalettes.h" +#include "../visitors/AronTreeWidgetCreator.h" +#include "NDArrayHelper.h" + +namespace armarx::skills::gui +{ + EditMatrixWidget::EditMatrixWidget(long numRows, + long numCols, + aron::type::matrix::ElementType elemType, + QTreeWidgetItem* currentItem) : + CustomWidget(currentItem) + { + realRows = numRows; + realCols = numCols; + this->elemType = elemType; + // init surrounding layout + outerVerticalLayout = new QVBoxLayout(); + innerHorizontalLayout = new QHBoxLayout(); + outerVerticalLayout->addItem(innerHorizontalLayout); + + + QGridLayout* innerGrid = new QGridLayout(); + const long createRows = std::min(numRows, MAX_ROWS_DISPLAY); + const long createCols = std::min(numCols, MAX_COLS_DISPLAY); + + for (long i = 0; i < createRows * createCols; ++i) + { + auto* edit = new QLineEdit(); + dispElements.push_back(edit); + int currRow = i / createCols; + int currCol = i % createCols; + + innerGrid->addWidget(edit, currRow, currCol); + std::stringstream ss; + ss << "(" << currRow << ", " << currCol << ")"; + std::string text = ss.str(); + + edit->setText(text.c_str()); + } + innerHorizontalLayout->addItem(innerGrid); + + // check, if we need to signal to the user, that not all elements are displayed. + if (numRows > MAX_ROWS_DISPLAY) + { + QLabel* fullLabel = new QLabel("..."); + outerVerticalLayout->addWidget(fullLabel); + } + if (numCols > MAX_COLS_DISPLAY) + { + QLabel* fullLabel = new QLabel("..."); + innerHorizontalLayout->addWidget(fullLabel); + } + setLayout(outerVerticalLayout); + + // add hidden elements in vector + { + const auto hiddenCols = numCols - createCols; + // the 0th element holds all columns that are + // to the right of the displayed cols AND besides the displayed rows! + hiddenElems.push_back(std::vector<std::string>(hiddenCols * createRows, "")); + + // add all rows that are hidden + for (long i = 0; i < numRows - createRows; ++i) + { + // add full cols; everything is hidden here + hiddenElems.push_back(std::vector<std::string>(numCols, "")); + } + } + + for (size_t i = 0; i < dispElements.size(); ++i) + { + ARMARX_CHECK(connect( + dispElements[i], SIGNAL(editingFinished()), this, SLOT(matrixElementChanged()))); + } + } + + EditMatrixWidget* + EditMatrixWidget::DynamicCast(QWidget* elem) + { + return dynamic_cast<EditMatrixWidget*>(elem); + } + + EditMatrixWidget* + EditMatrixWidget::DynamicCastAndCheck(QWidget* elem) + { + if (!elem) + { + return nullptr; + } + auto* casted = DynamicCast(elem); + ARMARX_CHECK_NOT_NULL(casted); + return casted; + } + + void + EditMatrixWidget::setText(long row, long col, const std::string& str) + { + ARMARX_CHECK(row < realRows); + ARMARX_CHECK(col < realCols); + auto dispCols = getDisplayedCols(); + auto dispRows = getDisplayedRows(); + if (row < dispRows && col < dispCols) + { + dispElements[row * dispCols + col]->setText(str.c_str()); + dispElements[row * dispCols + col]->setCursorPosition(0); + } + else if (row < dispRows) + { + long idx = row * (realCols - dispCols) + col - dispCols; + hiddenElems.at(0).at(idx) = str; + } + else + { + hiddenElems.at(row - dispRows + 1).at(col) = str; + } + } + + std::string + EditMatrixWidget::getText(long row, long col) + { + ARMARX_CHECK(row < realRows); + ARMARX_CHECK(col < realCols); + const auto dispRows = getDisplayedRows(); + const auto dispCols = getDisplayedCols(); + if (row < dispRows && col < dispCols) + { + auto txt = dispElements.at(row * dispCols + col)->text(); + return txt.toStdString(); + } + else if (row < getDisplayedRows()) + { + // the stuff besides the displayed rows + long idx = row * (realCols - dispCols) + col - dispCols; + return hiddenElems.at(0).at(idx); + } + else + { + // stuff beneath displayed rows + return hiddenElems.at(row - dispRows + 1).at(col); + } + } + + void + EditMatrixWidget::highlightUnparsableEntries() + { + auto dispRows = getDisplayedRows(); + auto dispCols = getDisplayedCols(); + for (long row = 0; row < dispRows; ++row) + { + for (long col = 0; col < dispCols; ++col) + { + auto parsed = parseElement(row, col); + if (parsed.empty()) + { + dispElements.at(row * dispCols + col) + ->setPalette(gui_color_palette::getErrorPalette()); + } + else + { + dispElements.at(row * dispCols + col) + ->setPalette(gui_color_palette::getNormalPalette()); + } + } + } + } + + bool + EditMatrixWidget::hasParseErrors() + { + // also parse the hidden stuff! + for (long row = 0; row < realRows; ++row) + { + for (long col = 0; col < realCols; ++col) + { + auto parsed = parseElement(row, col); + if (parsed.empty()) + { + if (row >= getDisplayedRows() || col >= getDisplayedCols()) + { + ARMARX_ERROR + << "Programming error! Could not parse content in EditMatrixWidget " + "that was set programatically from inside the SkillManagerPlugin. " + "The error occured in row " + << row << " and col " << col << "."; + } + return true; + } + } + } + return false; + } + + std::vector<unsigned char> + EditMatrixWidget::parseElement(long row, long col) + { + std::string str = getText(row, col); + try + { + switch (elemType) + { + case armarx::aron::type::matrix::INT16: + return NDArrayHelper::toByteVector<int16_t>(str); + + case armarx::aron::type::matrix::INT32: + return NDArrayHelper::toByteVector<int32_t>(str); + + case armarx::aron::type::matrix::INT64: + return NDArrayHelper::toByteVector<int64_t>(str); + + case armarx::aron::type::matrix::FLOAT32: + return NDArrayHelper::toByteVector<float>(str); + + case armarx::aron::type::matrix::FLOAT64: + return NDArrayHelper::toByteVector<double>(str); + } + } + catch (const simox::error::SimoxError& err) + { + return {}; + } + return {}; + } + + long + EditMatrixWidget::getDisplayedRows() const + { + return std::min(realRows, MAX_ROWS_DISPLAY); + } + + long + EditMatrixWidget::getDisplayedCols() const + { + return std::min(realCols, MAX_COLS_DISPLAY); + } + + void + EditMatrixWidget::matrixElementChanged() + { + blockSignals(true); + highlightUnparsableEntries(); + blockSignals(false); + emit elemChanged(overlayingItem, 1); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/EditMatrixWidget.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/EditMatrixWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..9d0c8cbfbd9463ab37d8498410381aebd54c6956 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/EditMatrixWidget.h @@ -0,0 +1,81 @@ +#pragma once +#include <vector> + +#include <QHBoxLayout> +#include <QLineEdit> +#include <QObject> +#include <QTreeWidgetItem> +#include <QVBoxLayout> + +#include "RobotAPI/libraries/aron/core/type/variant/ndarray/Matrix.h" + +#include "CustomWidget.h" + +namespace armarx::skills::gui +{ + // Custom Widget used to display Matrices + // MAX_ROWS_DISPLAY and MAX_COLS_DISPLAY are the maximal number of elements displayable + // everything else will not be displayed. However, there can still be made changes to this data with setText() + class EditMatrixWidget : public CustomWidget + { + Q_OBJECT + public: + EditMatrixWidget() = delete; + EditMatrixWidget(long numRows, + long numCols, + aron::type::matrix::ElementType elemType, + QTreeWidgetItem* currentWidget); + + static EditMatrixWidget* DynamicCast(QWidget*); + static EditMatrixWidget* DynamicCastAndCheck(QWidget*); + + // Sets the text on all visible rows and cols. + void setText(long row, long col, const std::string& str); + std::string getText(long row, long col); + void highlightUnparsableEntries(); + bool hasParseErrors(); + // returns an empty vector if parsing failed. Else, contains the individual bytes. + std::vector<unsigned char> parseElement(long row, long col); + + + private: + // Dimensions of the underlying Type to represent + long realRows = 1; + long realCols = 1; + + // maximum of rows / cols to display + static constexpr long MAX_ROWS_DISPLAY = 5; + static constexpr long MAX_COLS_DISPLAY = 5; + long getDisplayedRows() const; + long getDisplayedCols() const; + + // Want to add dots to show the user that not all is displayed. This needs some wrapping layouts + QVBoxLayout* outerVerticalLayout; + QHBoxLayout* innerHorizontalLayout; + + // Using row major layout + std::vector<QLineEdit*> dispElements; + // rows<cols<text>>: stores get and set messages for hidden elements (if there is not enough space for all elements) + std::vector<std::vector<std::string>> hiddenElems; + + template <typename T> + static std::vector<unsigned char> toByteVector(const std::string& str); + + // TODO: It would be nice to have this code somewhere else, this way, at least the GUI-responses + // are restricted to this Widget (signal & slot stuff) + aron::type::matrix::ElementType elemType; + + private slots: + void matrixElementChanged(); + }; + + template <typename T> + std::vector<unsigned char> + EditMatrixWidget::toByteVector(const std::string& str) + { + auto val = simox::alg::to_<T>(str); + std::vector<unsigned char> res(sizeof(val), 0); + *reinterpret_cast<T*>(res.data()) = val; + return res; + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/IntEnumWidget.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/IntEnumWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bbbee283d5aee3f75a0ce77b07a3e5dbca18eab7 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/IntEnumWidget.cpp @@ -0,0 +1,139 @@ +#include "IntEnumWidget.h" + +#include <QHBoxLayout> + +#include "RobotAPI/libraries/aron/core/type/variant/All.h" + +#include "../ColorPalettes.h" +#include "../visitors/AronTreeWidgetConverter.h" + +namespace armarx::skills::gui +{ + IntEnumWidget::IntEnumWidget(const aron::type::IntEnumPtr& enumPtr, + QTreeWidgetItem* currentItem) : + CustomWidget(currentItem), element_type(enumPtr) + { + auto names = enumPtr->getAcceptedValueNames(); + auto outerLayout = new QHBoxLayout(); + outerLayout->setContentsMargins(0, 0, 0, 0); + innerWidget = new QComboBox(); + outerLayout->addWidget(innerWidget); + + for (const auto& n : names) + { + innerWidget->addItem(n.c_str()); + } + innerWidget->setInsertPolicy(QComboBox::NoInsert); + innerWidget->setEditable(true); + innerWidget->setCurrentIndex(0); + + setLayout(outerLayout); + ARMARX_CHECK(connect(innerWidget, + SIGNAL(currentTextChanged(const QString&)), + this, + SLOT(textChanged(const QString&)))); + } + + IntEnumWidget* + IntEnumWidget::DynamicCast(QWidget* widget) + { + return dynamic_cast<IntEnumWidget*>(widget); + } + + IntEnumWidget* + IntEnumWidget::DynamicCastAndCheck(QWidget* elem) + { + if (!elem) + { + return nullptr; + } + auto* casted = DynamicCast(elem); + ARMARX_CHECK_NOT_NULL(casted); + return casted; + } + + bool + IntEnumWidget::hasParseErrors() + { + return parseToAron().first; + } + + void + IntEnumWidget::setText(const QString& text) + { + innerWidget->setEditText(text); + highlightUnparsableEntries(); + } + + QString + IntEnumWidget::getText() + { + return innerWidget->currentText(); + } + + std::pair<bool, aron::data::IntPtr> + IntEnumWidget::parseToAron() + { + auto crAron = std::make_shared<aron::data::Int>(element_type->getPath()); + + // first look for strings in value map + auto valueMap = element_type->getAcceptedValueMap(); + std::string toParse = getText().toStdString(); + + auto searchRes = valueMap.find(toParse); + if (searchRes != valueMap.end()) + { + crAron->setValue(searchRes->second); + return {true, crAron}; + } + // maybe its the number directly + auto accVals = element_type->getAcceptedValues(); + int res; + try + { + res = simox::alg::to_<int>(toParse); + } + catch (const simox::error::SimoxError& err) + { + ARMARX_VERBOSE << "Failed to parse IntEnum: Could not convert \'" << toParse + << "\' to an int. It also was not one of the accepted strings."; + return {false, nullptr}; + } + if (std::find(accVals.begin(), accVals.end(), res) != accVals.end()) + { + + crAron->setValue(res); + } + else + { + ARMARX_VERBOSE << "Parsed int " << res + << "but this int is not part of the accepted values."; + return {false, nullptr}; + } + return {true, crAron}; + } + + void + IntEnumWidget::highlightUnparsableEntries() + { + auto parsed = parseToAron().first; + if (parsed) + { + innerWidget->setPalette(gui_color_palette::getNormalPalette()); + } + else + { + innerWidget->setPalette(gui_color_palette::getErrorPalette()); + } + } + + void + IntEnumWidget::textChanged(const QString&) + { + setSupressSignals(true); + highlightUnparsableEntries(); + setSupressSignals(false); + // fire change signal + emit CustomWidget::elemChanged(overlayingItem, 1); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/IntEnumWidget.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/IntEnumWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..ba70937ad28318fe512641ba7d9068978c07244a --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/IntEnumWidget.h @@ -0,0 +1,42 @@ +#pragma once + +#include <QComboBox> + +#include "RobotAPI/libraries/aron/core/data/variant/primitive/Int.h" +#include "RobotAPI/libraries/aron/core/type/variant/forward_declarations.h" + +#include "../AronTreeWidgetItem.h" +#include "CustomWidget.h" + +namespace armarx::skills::gui +{ + // Custom wrapper around the QComboBox widget to represent IntEnums + // This class handles parsing once the user edited the field and is also responsible + // to parse the according aron data from text + class IntEnumWidget : public CustomWidget + { + Q_OBJECT + public: + IntEnumWidget(const aron::type::IntEnumPtr&, QTreeWidgetItem* currentItem); + // needs to be called after the constructor, because qt needs the meta object to be fully constructed at this point + void postCtorCall(); + + static IntEnumWidget* DynamicCast(QWidget*); + static IntEnumWidget* DynamicCastAndCheck(QWidget*); + + bool hasParseErrors(); + void setText(const QString& text); + QString getText(); + std::pair<bool, aron::data::IntPtr> parseToAron(); + + private: + const aron::type::IntEnumPtr element_type; + QComboBox* innerWidget; + + bool noParseErrors = true; + + void highlightUnparsableEntries(); + private slots: + void textChanged(const QString&); + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/NDArrayHelper.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/NDArrayHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a9599a2dbdfd456e1237542d34006f5d4c4c9bbf --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/NDArrayHelper.cpp @@ -0,0 +1 @@ +#include "NDArrayHelper.h" diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/NDArrayHelper.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/NDArrayHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..bd74b4ef50ae33cab07e836157085cf23d3f517c --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/NDArrayHelper.h @@ -0,0 +1,33 @@ +#pragma once +#include <string> +#include <vector> + +#include <SimoxUtility/algorithm/string.h> + +namespace armarx::skills::gui +{ + class NDArrayHelper + { + public: + // Uses the simox utility to parse floatingpoint and fixed precision types + // it then writes the result into a byte vector + // an empty byte vector encodes a parsing error. + template <typename T> + std::vector<unsigned char> static toByteVector(const std::string& str) + { + try + { + auto val = simox::alg::to_<T>(str); + std::vector<unsigned char> res(sizeof(val), 0); + T* reinterpPtr = reinterpret_cast<T*>(res.data()); + T* laundered = std::launder(reinterpPtr); + *laundered = val; + return res; + } + catch (const simox::error::SimoxError& err) + { + return {}; + } + } + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/QuaternionWidget.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/QuaternionWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e8a48aa71942e5f86e837a8fc516e7d475615f66 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/QuaternionWidget.cpp @@ -0,0 +1,161 @@ +#include "QuaternionWidget.h" + +#include <QHBoxLayout> +#include <QLabel> +#include <QLineEdit> + +#include "../ColorPalettes.h" +#include "NDArrayHelper.h" + +namespace armarx::skills::gui +{ + + QuaternionWidget* + QuaternionWidget::DynamicCast(QWidget* elem) + { + return dynamic_cast<QuaternionWidget*>(elem); + } + + QuaternionWidget* + QuaternionWidget::DynamicCastAndCheck(QWidget* elem) + { + if (!elem) + { + return nullptr; + } + auto* casted = DynamicCast(elem); + ARMARX_CHECK_NOT_NULL(casted); + return casted; + } + + void + QuaternionWidget::setText(QuaternionComponents col, const std::string& str) + { + size_t idx = (size_t)col; + xyzw_components.at(idx)->setText(str.c_str()); + highlightUnparsableEntries(); + } + + bool + QuaternionWidget::hasParseErrors() + { + return parseErrors; + } + + QuaternionWidget::QuaternionWidget(aron::type::quaternion::ElementType elType, + QTreeWidgetItem* currentItem) : + CustomWidget(currentItem), element_type(elType) + { + auto outerLayout = new QHBoxLayout(); + auto labelCol1 = new QVBoxLayout(); + auto labelCol2 = new QVBoxLayout(); + auto xzEdit = new QVBoxLayout(); + auto ywEdit = new QVBoxLayout(); + + outerLayout->addLayout(labelCol1); + outerLayout->addLayout(xzEdit); + outerLayout->addLayout(labelCol2); + outerLayout->addLayout(ywEdit); + + xyzw_components.reserve(4); + for (size_t i = 0; i < 4; ++i) + { + xyzw_components.push_back(new QLineEdit()); + } + + // Tabbing order >> Viewing order. Thats why the indeces are a bit switched + labelCol1->addWidget(new QLabel("x")); + xzEdit->addWidget(xyzw_components.at(0)); + + labelCol2->addWidget(new QLabel("z")); + ywEdit->addWidget(xyzw_components.at(2)); + + labelCol1->addWidget(new QLabel("y")); + xzEdit->addWidget(xyzw_components.at(1)); + + labelCol2->addWidget(new QLabel("w")); + ywEdit->addWidget(xyzw_components.at(3)); + + for (const auto& el : xyzw_components) + { + ARMARX_CHECK( + connect(el, SIGNAL(editingFinished()), this, SLOT(quaternionElementChanged()))); + } + + setLayout(outerLayout); + highlightUnparsableEntries(); + } + + void + QuaternionWidget::highlightUnparsableEntries() + { + parseErrors = false; + for (size_t i = 0; i < 4; ++i) + { + bool ok = false; + switch (element_type) + { + case armarx::aron::type::quaternion::FLOAT32: + { + float flt; + std::tie(ok, flt) = parseQuatVals<float>((QuaternionComponents)i); + break; + } + case armarx::aron::type::quaternion::FLOAT64: + { + double dbl; + std::tie(ok, dbl) = parseQuatVals<double>((QuaternionComponents)i); + break; + } + } + if (ok) + { + xyzw_components.at(i)->setPalette(gui_color_palette::getNormalPalette()); + } + else + { + xyzw_components.at(i)->setPalette(gui_color_palette::getErrorPalette()); + parseErrors = true; + } + } + } + + std::vector<unsigned char> + QuaternionWidget::parseAllToNDArray() + { + std::vector<unsigned char> res; + bool success = true; + if (element_type == aron::type::quaternion::FLOAT32) + { + res.reserve(16); + for (size_t i = 0; i < 4; ++i) + { + auto bytevec = + NDArrayHelper::toByteVector<float>(xyzw_components.at(i)->text().toStdString()); + success &= !bytevec.empty(); + res.insert(res.end(), bytevec.begin(), bytevec.end()); + } + } + else + { + res.reserve(32); + for (size_t i = 0; i < 4; ++i) + { + auto bytevec = NDArrayHelper::toByteVector<double>( + xyzw_components.at(i)->text().toStdString()); + success &= !bytevec.empty(); + res.insert(res.end(), bytevec.begin(), bytevec.end()); + } + } + return (success) ? res : std::vector<unsigned char>(); + } + + void + QuaternionWidget::quaternionElementChanged() + { + CustomWidget::setSupressSignals(true); + highlightUnparsableEntries(); + CustomWidget::setSupressSignals(false); + emit CustomWidget::elemChanged(overlayingItem, 1); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/QuaternionWidget.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/QuaternionWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..f743e3acf3a2eafdfd03f3eb35bddb48b49cfbb4 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/QuaternionWidget.h @@ -0,0 +1,67 @@ +#pragma once +#include <vector> + +#include <QLineEdit> +#include <QObject> +#include <QVBoxLayout> + +#include "RobotAPI/libraries/aron/core/type/variant/ndarray/Matrix.h" + +#include "CustomWidget.h" + +namespace armarx::skills::gui +{ + // Custom Widget which represents a Quaternion - This class does not normalize the inputs + // It can parse its data to NDArray and also handles user edit signals itself. (And also the highlighting if errors occur) + class QuaternionWidget : public CustomWidget + { + Q_OBJECT + public: + QuaternionWidget(aron::type::quaternion::ElementType type, QTreeWidgetItem* currentItem); + + static QuaternionWidget* DynamicCast(QWidget*); + static QuaternionWidget* DynamicCastAndCheck(QWidget*); + + enum struct QuaternionComponents + { + X = 0, + Y, + Z, + W + }; + void setText(QuaternionComponents col, const std::string& str); + std::string getText(QuaternionComponents col); + bool hasParseErrors(); + std::vector<unsigned char> parseAllToNDArray(); + + private: + std::vector<QLineEdit*> xyzw_components; + aron::type::quaternion::ElementType element_type; + bool parseErrors = false; + + void highlightUnparsableEntries(); + template <typename T> + std::pair<bool, T> parseQuatVals(QuaternionComponents comp); + + private slots: + void quaternionElementChanged(); + }; + + template <typename T> + std::pair<bool, T> + QuaternionWidget::parseQuatVals(QuaternionWidget::QuaternionComponents comp) + { + // size_t data_size = element_type == aron::type::quaternion::ElementType::FLOAT32 ? 4 : 8; + try + { + T val = simox::alg::to_<T>(xyzw_components.at((size_t)comp)->text().toStdString()); + return {true, val}; + } + catch (const simox::error::SimoxError& err) + { + return {false, NAN}; + } + } + + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/SkillDescriptionWidget.cpp b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/SkillDescriptionWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b0132b59f6dedb147a3820d0a7d0dbcce89a4c1c --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/SkillDescriptionWidget.cpp @@ -0,0 +1,69 @@ +#include "SkillDescriptionWidget.h" + +#include <sstream> + +#include <QGridLayout> +#include <QLabel> +#include <QTextEdit> + +#include <ArmarXGui/libraries/ArmarXGuiBase/widgets/cpp-markdown/markdown.h> // ToDo: Move cpp-markdown to own Axii module. + +namespace armarx::skills::gui +{ + + static std::string + markdownToHtml(const std::string& markdownText, size_t spacesPerTab = 2) + { + ::markdown::Document document(spacesPerTab); + document.read(markdownText); + + std::stringstream html; + document.write(html); + return html.str(); + } + + SkillDescriptionWidget::SkillDescriptionWidget(QWidget* parent) : QWidget{parent} + { + QGridLayout* layout = new QGridLayout; + setLayout(layout); + + layout->setMargin(0); + + int row = 0; + int col = 0; + + layout->addWidget(new QLabel("Name:"), row, col++); + name = new QLabel; + layout->addWidget(name, row, col++); + + // Add some padding. + layout->addWidget(new QLabel(), row, col++); + + { + QLabel* label = new QLabel("Timeout:"); + label->setAlignment(Qt::AlignmentFlag::AlignRight); + layout->addWidget(label, row, col++); + } + timeout = new QLabel; + timeout->setAlignment(Qt::AlignmentFlag::AlignRight); + layout->addWidget(timeout, row, col++); + + ++row; + int numCols = col; + col = 0; + + description = new QTextEdit; + description->setReadOnly(true); + layout->addWidget(description, row, col, 1, numCols); + ++row; + } + + void + SkillDescriptionWidget::setSkillDescription(const skills::SkillDescription& desc) + { + name->setText(QString::fromStdString(desc.skillId.skillName)); + description->setHtml(QString::fromStdString(markdownToHtml(desc.description))); + timeout->setText(QString::fromStdString(desc.timeout.toDurationString())); + } + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/SkillDescriptionWidget.h b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/SkillDescriptionWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..5344c50d4188c7e700b7b4b9a8518c3d71794dec --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/SkillDescriptionWidget.h @@ -0,0 +1,27 @@ +#pragma once + +#include <QWidget> + +#include <RobotAPI/libraries/skills/core/SkillDescription.h> + +class QLabel; +class QTextEdit; + +namespace armarx::skills::gui +{ + + class SkillDescriptionWidget : public QWidget + { + public: + SkillDescriptionWidget(QWidget* parent = nullptr); + + void setSkillDescription(const skills::SkillDescription& desc); + + private: + QLabel* name = nullptr; + QTextEdit* description = nullptr; + QLabel* timeout = nullptr; + }; + + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.cpp b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..02c8c1df0871cb69f09ceb05fe0adb41e6d0a2e7 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.cpp @@ -0,0 +1,183 @@ +#include "SkillExecutionTreeWidget.h" + +#include <mutex> + +#include <QMenu> +#include <QTreeWidgetItem> + +#include "SkillExecutionTreeWidgetItem.h" + +namespace armarx::skills::gui +{ + + void + SkillExecutionTreeWidget::runContextMenu(const QPoint& pos) + { + // sanity check + ARMARX_CHECK(selectionValid()); + + QMenu* menu = new QMenu(); + + // Stop skill + QAction* stopSkillAction = new QAction("Stop execution", this); + const auto& executions = memory->fetchExecutions(); + if (executions.count(selectedExecution.skillExecutionId) == 0) + return; + skills::SkillStatus currentStatus = + memory->fetchExecutions().at(selectedExecution.skillExecutionId).status; + stopSkillAction->setDisabled(currentStatus == skills::SkillStatus::Aborted || + currentStatus == skills::SkillStatus::Failed || + currentStatus == skills::SkillStatus::Succeeded); + + QAction* rerunSkillAction = new QAction("Re-run with similar params", this); + menu->addAction(stopSkillAction); + menu->addAction(rerunSkillAction); + connect(stopSkillAction, + &QAction::triggered, + this, + &SkillExecutionTreeWidget::stopSelectedExecution); + connect(rerunSkillAction, + &QAction::triggered, + this, + &SkillExecutionTreeWidget::rerunSkillWithSimilarParams); + + // open menu + menu->popup(this->viewport()->mapToGlobal(pos)); + } + + void + SkillExecutionTreeWidget::stopSelectedExecution() + { + if (!selectionValid()) + return; + memory->stopExecution(this->selectedExecution.skillExecutionId); + } + + void + SkillExecutionTreeWidget::rerunSkillWithSimilarParams() + { + if (!selectionValid()) + return; + // we don't want to hold state in the gui, so we need to get the parameters from memory: + skills::SkillExecutionID currentExecutionId = this->selectedExecution.skillExecutionId; + auto executions = memory->fetchExecutions(); + if (executions.empty()) + return; + + if (executions.count(currentExecutionId) == 0) + { + // we didn't find an entry for the execution id + ARMARX_IMPORTANT << "The selected execution was not found in memory. The GUI is unable " + "to determine the parametrization for this execution."; + return; + } + auto params = executions[currentExecutionId].parameters; + + ARMARX_INFO << "Re-executing the skill " << currentExecutionId.skillId + << " with previous parameters."; + + // give all information to manager + this->memory->startExecutionWithParams(currentExecutionId.skillId, params); + } + + void + SkillExecutionTreeWidget::disconnectGui() + { + this->selectedExecution = SelectedExecution(); + this->clear(); + } + + void + SkillExecutionTreeWidget::setupUi() + { + this->setColumnCount(6); + + this->setContextMenuPolicy(Qt::CustomContextMenu); + + QTreeWidgetItem* qtreewidgetitem = this->headerItem(); + qtreewidgetitem->setText(5, ""); + qtreewidgetitem->setText(4, ""); + qtreewidgetitem->setText(3, "Status"); + qtreewidgetitem->setText(2, "SkillID"); + qtreewidgetitem->setText(1, "Executor"); + qtreewidgetitem->setText(0, "Timestamp"); + + this->setColumnWidth(4, 30); + + connectSignals(); + } + + void + SkillExecutionTreeWidget::connectSignals() + { + connect(this, + &QTreeWidget::customContextMenuRequested, + this, + &SkillExecutionTreeWidget::runContextMenu); + connect(this, + &QTreeWidget::currentItemChanged, + this, + &SkillExecutionTreeWidget::executionSelectionChanged); + } + + inline bool + SkillExecutionTreeWidget::selectionValid() + { + return selectedExecution.skillExecutionId.skillId.skillName != skills::SkillID::UNKNOWN; + } + + void + SkillExecutionTreeWidget::executionSelectionChanged(QTreeWidgetItem* current, + QTreeWidgetItem* previous) + { + // update internal state + SkillExecutionTreeWidgetItem* selected = + dynamic_cast<SkillExecutionTreeWidgetItem*>(current); + if (selected) + { + this->selectedExecution.skillExecutionId = selected->getExecutionId(); + } + } + + void + SkillExecutionTreeWidget::updateExecutions() + { + auto currentManagerStatuses = memory->fetchExecutions(); + if (currentManagerStatuses.empty()) + return; + + for (const auto& [k, v] : currentManagerStatuses) + { + skills::SkillExecutionID executionId = k; + skills::SkillStatusUpdate statusUpdate = v; + + SkillExecutionTreeWidgetItem* found = nullptr; + for (int i = 0; i < this->topLevelItemCount(); ++i) + { + auto c = dynamic_cast<SkillExecutionTreeWidgetItem*>(topLevelItem(i)); + if (!c) + { + // the item is probably not the correct type, skip... + continue; + } + + found = SkillExecutionTreeWidgetItem::SearchRecursiveForMatch(c, executionId); + + if (found) + { + found->updateItem(statusUpdate.status); + + break; + } + } + + if (!found) + { + // TODO: Sort to executor! + auto item = new SkillExecutionTreeWidgetItem(executionId, memory, this); + + item->updateItem(statusUpdate.status); + } + } + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.h b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..795045302b4b47655e196e4cea566d53140f90d6 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.h @@ -0,0 +1,62 @@ +#ifndef SKILLEXECUTIONTREEWIDGET_H +#define SKILLEXECUTIONTREEWIDGET_H + +#include <QTreeWidget> + +#include <ArmarXCore/core/time.h> + +#include "../memory/MemoryCommunicatorBase.h" + +namespace armarx::skills::gui +{ + + class SkillExecutionTreeWidget : public QTreeWidget, public MemoryCommunicatorBase + { + public: + SkillExecutionTreeWidget(std::shared_ptr<SkillManagerWrapper> _memory, + QWidget* parent = nullptr) : + QTreeWidget(parent), MemoryCommunicatorBase(_memory) + { + setupUi(); + } + + struct SelectedExecution + { + skills::SkillExecutionID skillExecutionId; + + // make default constructable + SelectedExecution() : + skillExecutionId{ + .skillId = {.providerId = std::nullopt, .skillName = skills::SkillID::UNKNOWN}, + .executorName = skills::SkillExecutionID::UNKNOWN, + .executionStartedTime = armarx::core::time::DateTime::Invalid()} + { + } + }; + + SelectedExecution& getSelectedExecution(); + + public slots: + void disconnectGui(); + void updateExecutions(); + + private slots: + void executionSelectionChanged(QTreeWidgetItem* current, QTreeWidgetItem* previous); + void runContextMenu(const QPoint& pos); + void stopSelectedExecution(); + void rerunSkillWithSimilarParams(); + + private: + void setupUi(); + void connectSignals(); + + /** + * @brief Checks the validity of the selected item at run time. + * @return the validity. + */ + bool selectionValid(); + SelectedExecution selectedExecution; + }; +} // namespace armarx::skills::gui + +#endif // SKILLEXECUTIONTREEWIDGET_H diff --git a/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidgetItem.cpp b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidgetItem.cpp new file mode 100644 index 0000000000000000000000000000000000000000..947671ff7fd44d0c9a9545bf82a721447f0ce952 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidgetItem.cpp @@ -0,0 +1,95 @@ +#include "SkillExecutionTreeWidgetItem.h" + +namespace armarx::skills::gui +{ + SkillExecutionTreeWidgetItem* + SkillExecutionTreeWidgetItem::SearchRecursiveForMatch(SkillExecutionTreeWidgetItem* haystack, + const SkillExecutionID& needle) + { + if (!haystack) + { + return nullptr; + } + + if (needle == haystack->executionId) + { + return haystack; + } + for (int i = 0; i < haystack->childCount(); ++i) + { + auto el = dynamic_cast<SkillExecutionTreeWidgetItem*>(haystack->child(i)); + if (!el) + { + // Dynamic casting failed. Skip element... + // We assume, that only this type can contain children in the tree. + continue; + } + return SkillExecutionTreeWidgetItem::SearchRecursiveForMatch(el, needle); + } + return nullptr; + } + + SkillExecutionID + SkillExecutionTreeWidgetItem::getExecutionId() + { + return this->executionId; + } + + void + SkillExecutionTreeWidgetItem::updateItem(skills::SkillStatus& skillStatus) + { + this->setText(0, + QString::fromStdString(executionId.executionStartedTime.toDateTimeString())); + this->setText(1, QString::fromStdString(executionId.executorName)); + this->setText(2, QString::fromStdString(executionId.skillId.toString())); + for (std::pair<skills::SkillStatus, std::string> i : EXECUTION_STATUS_TO_STRING) + { + if (i.first == skillStatus) + { + this->setText(3, QString::fromStdString(i.second)); + } + } + updateButtonState(skillStatus); + } + + void + SkillExecutionTreeWidgetItem::abortExecution() + { + memory->stopExecution(executionId); + } + + void + SkillExecutionTreeWidgetItem::setupUi() + { + parentTree->insertTopLevelItem(0, this); + abortButton = new QPushButton(); + abortButton->setMinimumHeight(20); + abortButton->setMaximumHeight(20); + parentTree->setItemWidget(this, 4, abortButton); + + QPixmap pix(":/icons/process-stop-7.ico"); + QIcon icon(pix); + + abortButton->setIcon(icon); + + + connectSignals(); + } + + void + SkillExecutionTreeWidgetItem::connectSignals() + { + connect(abortButton, + &QPushButton::clicked, + this, + &SkillExecutionTreeWidgetItem::abortExecution); + } + + void + SkillExecutionTreeWidgetItem::updateButtonState(SkillStatus& skillStatus) + { + abortButton->setDisabled(skillStatus == SkillStatus::Aborted || + skillStatus == SkillStatus::Failed || + skillStatus == SkillStatus::Succeeded); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidgetItem.h b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidgetItem.h new file mode 100644 index 0000000000000000000000000000000000000000..cd0376f8f9c123f6046e061a4955293fc833c9ec --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidgetItem.h @@ -0,0 +1,68 @@ +#ifndef SKILLEXECUTIONTREEWIDGETITEM_H +#define SKILLEXECUTIONTREEWIDGETITEM_H + +#include <QPushButton> +#include <QTreeWidgetItem> + +#include "RobotAPI/libraries/skills_gui/executions/SkillExecutionTreeWidget.h" +#include <RobotAPI/libraries/skills/core/SkillExecutionID.h> +#include <RobotAPI/libraries/skills/core/SkillStatusUpdate.h> + +#include "../memory/MemoryCommunicatorBase.h" +#include "SkillExecutionTreeWidgetItem.h" + +namespace armarx::skills::gui +{ + static const std::map<skills::SkillStatus, std::string> EXECUTION_STATUS_TO_STRING = { + // Main states + {skills::SkillStatus::Constructing, "Constructing"}, + {skills::SkillStatus::Initializing, "Initializing"}, + {skills::SkillStatus::Preparing, "Preparing"}, + {skills::SkillStatus::Running, "Running"}, + + // Terminating + {skills::SkillStatus::Aborted, "Aborted"}, + {skills::SkillStatus::Failed, "Failed"}, + {skills::SkillStatus::Succeeded, "Succeeded"}}; + + class SkillExecutionTreeWidgetItem : + public QObject, + public QTreeWidgetItem, + public MemoryCommunicatorBase + { + Q_OBJECT + public: + SkillExecutionTreeWidgetItem() = delete; + + SkillExecutionTreeWidgetItem(const skills::SkillExecutionID& id, + std::shared_ptr<SkillManagerWrapper> _memory, + SkillExecutionTreeWidget* parent) : + MemoryCommunicatorBase(_memory), executionId(id), parentTree(parent) + { + setupUi(); + } + + static SkillExecutionTreeWidgetItem* + SearchRecursiveForMatch(SkillExecutionTreeWidgetItem* haystack, + const skills::SkillExecutionID& needle); + + skills::SkillExecutionID getExecutionId(); + + void updateItem(skills::SkillStatus& skillStatus); + + protected: + skills::SkillExecutionID executionId; + + public slots: + void abortExecution(); + + private: + void setupUi(); + void connectSignals(); + void updateButtonState(skills::SkillStatus& skillStatus); + SkillExecutionTreeWidget* parentTree = nullptr; + QPushButton* abortButton = nullptr; + }; +} // namespace armarx::skills::gui + +#endif // SKILLEXECUTIONTREEWIDGETITEM_H diff --git a/source/RobotAPI/libraries/skills_gui/gui_utils.cpp b/source/RobotAPI/libraries/skills_gui/gui_utils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..666228c764379671d6ca59d142fefc02446c42d6 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/gui_utils.cpp @@ -0,0 +1,109 @@ +#include "gui_utils.h" + +#include <QLayout> +#include <QLayoutItem> +#include <QSplitter> +#include <QTreeWidgetItem> +#include <QWidget> + +#include <ArmarXCore/core/exceptions/local/ExpressionException.h> + +void +armarx::gui::clearLayout(QLayout* layout) +{ + // Source: https://stackoverflow.com/a/4857631 + + ARMARX_CHECK(layout); + + QLayoutItem* item; + while ((item = layout->takeAt(0))) + { + if (item->layout()) + { + clearLayout(item->layout()); + delete item->layout(); + } + if (item->widget()) + { + delete item->widget(); + } + delete item; + } +} + +void +armarx::gui::clearItem(QTreeWidgetItem* item) +{ + while (item->childCount() > 0) + { + delete item->takeChild(0); + } +} + +QSplitter* +armarx::gui::useSplitter(QLayout* layout) +{ + ARMARX_CHECK(layout); + + // Check all items + for (int i = 0; i < layout->count(); ++i) + { + ARMARX_CHECK_NOT_NULL(layout->itemAt(i)->widget()) + << "QSplitter only supports widgets, but layout item #" << i << " is not a widget."; + } + + QSplitter* splitter; + if (dynamic_cast<QHBoxLayout*>(layout)) + { + splitter = new QSplitter(Qt::Orientation::Horizontal); + } + else if (dynamic_cast<QVBoxLayout*>(layout)) + { + splitter = new QSplitter(Qt::Orientation::Vertical); + } + else + { + splitter = new QSplitter(); + } + + while (layout->count() > 0) + { + const int index = 0; + if (layout->itemAt(index)) + { + QLayoutItem* item = layout->takeAt(index); + + ARMARX_CHECK(item->widget()); + splitter->addWidget(item->widget()); + + delete item; + } + } + ARMARX_CHECK_EQUAL(layout->count(), 0); + + layout->addWidget(splitter); + ARMARX_CHECK_EQUAL(layout->count(), 1); + ARMARX_CHECK_EQUAL(layout->itemAt(0)->widget(), splitter); + + return splitter; +} + +armarx::gui::LeadingZeroSpinBox::LeadingZeroSpinBox(int numDigits, int base) : + numDigits(numDigits), base(base) +{ +} + +QString +armarx::gui::LeadingZeroSpinBox::textFromValue(int value) const +{ + return QString("%1").arg(value, numDigits, base, QChar('0')); +} + +void +armarx::gui::clearSplitter(QSplitter* splitter) +{ + for (int i = 0; i < splitter->count(); ++i) + { + splitter->widget(i)->deleteLater(); + } +} diff --git a/source/RobotAPI/libraries/skills_gui/gui_utils.h b/source/RobotAPI/libraries/skills_gui/gui_utils.h new file mode 100644 index 0000000000000000000000000000000000000000..3901038ba99f7b59494df40210af0da867ba6ff3 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/gui_utils.h @@ -0,0 +1,100 @@ +#pragma once + +#include <iostream> + +#include <QLayout> +#include <QLayoutItem> +#include <QSpinBox> +#include <QSplitter> +#include <QTreeWidgetItem> +#include <QWidget> + +#include <mutex> + +#include <ArmarXCore/core/exceptions/local/ExpressionException.h> + +namespace armarx::gui +{ + /** + * @brief Clear a layout. + * + * Recursively take and delete all items in `layout`. + * + * @param layout The layout. + */ + void clearLayout(QLayout* layout); + + void clearSplitter(QSplitter* splitter); + + /** + * @brief Clear a tree widget item + * + * Take and delete all children of `item`. + * + * @param item The tree widget item. + */ + void clearItem(QTreeWidgetItem* item); + + template <class WidgetT> + void + replaceWidget(WidgetT*& old, QWidget* neu, QLayout* parentLayout) + { + ARMARX_CHECK(old); + ARMARX_CHECK(neu); + ARMARX_CHECK(parentLayout); + QLayoutItem* oldItem = parentLayout->replaceWidget(old, neu); + if (oldItem) + { + delete oldItem; + delete old; + old = nullptr; + } + } + + template <class WidgetT> + void + replaceWidget(WidgetT*& old, QWidget* neu, QSplitter*& parentLayout) + { + ARMARX_CHECK(old); + ARMARX_CHECK(neu); + ARMARX_CHECK(parentLayout); + int index = parentLayout->indexOf(old); + ARMARX_CHECK(index >= 0) << "Could not find widget in splitter"; + QWidget* oldItem = parentLayout->replaceWidget(index, neu); + if (oldItem) + { + // deleting the widget(s) directly will segfault for some reason + oldItem->deleteLater(); + old->deleteLater(); + old = nullptr; + } + } + + /** + * @brief Let items in `layout` be children of a splitter. + * + * Items in `layout` are moved to a new `QSplitter`, which will + * be the only child of `layout`. + * If `layout` is a Q{H,V}BoxLayout, the respective orientation + * will be adopted. + * + * @param The parent layout. + * @return The splitter item. + */ + QSplitter* useSplitter(QLayout* layout); + + // Source: https://stackoverflow.com/a/26538572 + class LeadingZeroSpinBox : public QSpinBox + { + using QSpinBox::QSpinBox; + + public: + LeadingZeroSpinBox(int numDigits, int base); + + QString textFromValue(int value) const override; + + private: + int numDigits; + int base; + }; +} // namespace armarx::gui diff --git a/source/RobotAPI/libraries/skills_gui/memory/MemoryCommunicatorBase.cpp b/source/RobotAPI/libraries/skills_gui/memory/MemoryCommunicatorBase.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5950600afad2f71a04b0997885d986a23d82d824 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/memory/MemoryCommunicatorBase.cpp @@ -0,0 +1 @@ +#include "MemoryCommunicatorBase.h" diff --git a/source/RobotAPI/libraries/skills_gui/memory/MemoryCommunicatorBase.h b/source/RobotAPI/libraries/skills_gui/memory/MemoryCommunicatorBase.h new file mode 100644 index 0000000000000000000000000000000000000000..adefacd9358f9982555cbc860dc0a96a3f58c77f --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/memory/MemoryCommunicatorBase.h @@ -0,0 +1,27 @@ +#ifndef MEMORYCOMMUNICATOR_H +#define MEMORYCOMMUNICATOR_H + +#include <memory> + +#include "SkillManagerWrapper.h" + +namespace armarx::skills::gui +{ + /* + * This is a convenience base class to prevent repeatedly putting the SkillManagerWrapper in + * every widget. + */ + class MemoryCommunicatorBase + { + protected: + // We don't want to instantiate the base class + MemoryCommunicatorBase(std::shared_ptr<SkillManagerWrapper> _memory) : memory(_memory) + { + } + + // The interface of the memory, used by the skill memory gui + std::shared_ptr<SkillManagerWrapper> memory; + }; +} // namespace armarx::skills::gui + +#endif // MEMORYCOMMUNICATOR_H diff --git a/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.cpp b/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..92277c0eba7e1277a658e8ed685156d70bd100bc --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.cpp @@ -0,0 +1,312 @@ +#include "SkillManagerWrapper.h" + +#include <mutex> + +#include "RobotAPI/libraries/skills/core/SkillExecutionRequest.h" + +namespace armarx::skills::gui +{ + using StatusMap = std::map<skills::SkillExecutionID, skills::SkillStatusUpdate>; + using SkillMap = + std::map<skills::ProviderID, std::map<skills::SkillID, skills::SkillDescription>>; + + const StatusMap + SkillManagerWrapper::fetchExecutions() + { + if (!memory) + { + // check if null + return {}; + } + + try + { + std::scoped_lock l(mutex_memory); + + // we return this map + StatusMap statusMap; + + auto currentManagerStatuses = memory->getSkillExecutionStatuses(); + + // iterate over raw data and convert to common types + for (const auto& [k, v] : currentManagerStatuses) + { + auto executionId = skills::SkillExecutionID::FromIce(k); + auto statusUpdate = skills::SkillStatusUpdate::FromIce(v); + + // update maps + statusMap[executionId] = statusUpdate; + } + return statusMap; + } + catch (Ice::Exception const& e) + { + ARMARX_WARNING + << "Unhandled Ice exception encountered while updating executions. Exception was: " + << e; + emit connectionUpdate("Could not fetch executions", e.what()); + return {}; + } + catch (...) + { + ARMARX_WARNING << "Unknown exception encountered while updating executions."; + return {}; + } + } + + void + SkillManagerWrapper::filterSkillUpdate( + std::map<skills::manager::dto::SkillID, skills::manager::dto::SkillDescription>& update) + { + // empty search => do not filter + if (this->currentSkillSearch.empty()) + { + return; + } + + // TODO: whitespace to wildcard + + std::string key = simox::alg::to_lower(this->currentSkillSearch); + + for (auto it = update.begin(); it != update.end();) + { + if (simox::alg::to_lower(skills::SkillID::FromIce(it->first).skillName).find(key)) + { + it = update.erase(it); + } + else + { + ++it; + } + } + } + + const SkillMap + SkillManagerWrapper::fetchSkills() + { + if (!memory) + { + return {}; + } + + try + { + std::scoped_lock l(mutex_memory); + + SkillMap skills; + + auto managerSkills = memory->getSkillDescriptions(); + + this->filterSkillUpdate(managerSkills); + + + for (const auto& [sid, desc] : managerSkills) + { + auto description = skills::SkillDescription::FromIce(desc); + auto skillId = skills::SkillID::FromIce(sid); + auto providerId = skillId.providerId.value_or( + skills::ProviderID{.providerName = "UNKNOWN PROVIDER NAME"}); + + ARMARX_CHECK(skillId.isFullySpecified()); + + auto& providedSkillsMap = skills[providerId]; // create new if not existing + providedSkillsMap.insert({skillId, description}); + } + + return skills; + } + catch (Ice::Exception const& e) + { + ARMARX_WARNING + << "Unhandled Ice exception encountered while updating skills. Exception was: " + << e; + emit connectionUpdate("Could not fetch skills", e.what()); + return {}; + } + catch (...) + { + ARMARX_WARNING << "Unknown exception encountered while updating skills."; + return {}; + } + } + + void + SkillManagerWrapper::connectMemory( + skills::manager::dti::SkillManagerInterfacePrx const& updatedMemory) + { + std::scoped_lock l(mutex_memory); + this->memory = updatedMemory; + } + + void + SkillManagerWrapper::disconnectMemory() + { + // clear memory + + std::scoped_lock l(mutex_memory); + this->memory = nullptr; + } + + void + SkillManagerWrapper::acceptSearchRequest(std::string const& search) + { + this->currentSkillSearch = search; + } + + void + SkillManagerWrapper::stopAllExecutions() + { + ARMARX_IMPORTANT << "Stopping all running executions."; + const auto& executions = this->fetchExecutions(); + for (auto& [executionId, status] : executions) + { + // select all running executions... + if (!status.hasBeenTerminated()) + { + // ... and kill them. + this->stopExecution(executionId, 3); + } + } + } + + const std::optional<ProviderID> + SkillManagerWrapper::findFirstProvider(SkillMap const& map, SkillID const& skillId) + { + // check if id already contains a provider. If so, this function should not have been called! + if (skillId.isProviderSpecified()) + { + ARMARX_WARNING << "The memory snapshot was searched for any provider, when a provider " + "was specified."; + // we continue, but this might result in unexpected behaviour... + } + + for (auto& [prov, skillMap] : map) + { + for (auto& [skill, desc] : skillMap) + { + if (skill == skillId) + { + return prov; + } + } + } + return std::nullopt; + } + + void + SkillManagerWrapper::stopExecution(skills::SkillExecutionID const& executionId, + const unsigned int max_retries) + { + // memory??? + if (!memory) + { + return; + } + + unsigned int retries = max_retries; + bool retry = false; + + do + { + try + { + ARMARX_INFO << "Aborting skill '" << executionId.skillId.skillName << "'..."; + std::scoped_lock l(mutex_memory); + this->memory->abortSkillAsync(executionId.toManagerIce()); + } + catch (Ice::Exception const& e) + { + retry = true; + ARMARX_ERROR << "Unhandeled Ice exception while aborting skill '" + << executionId.skillId.skillName << "'."; + emit connectionUpdate("Could not abort skill " + executionId.skillId.skillName, + e.what()); + } + catch (...) + { + retry = true; + ARMARX_ERROR << "Unhandled error while aborting skill '" + << executionId.skillId.skillName << "'."; + } + + if (retry) + { + retries -= 1; + + if (retries > 0) + { + ARMARX_WARNING << "There where errors aborting skills. Retrying..."; + } + else + { + ARMARX_ERROR << "Couldn't abort all skills after " << max_retries + << " tries. Giving up."; + retry = false; + } + } + } while (retry); + } + + void + SkillManagerWrapper::startExecutionWithParams(skills::SkillID& skillId, + aron::data::DictPtr const params) + { + // Memory??? + if (!memory) + { + return; + } + + auto providerId = skillId.providerId; + if (!providerId.has_value()) + { + ARMARX_IMPORTANT << "The skill: '" << skillId.skillName + << "' has been requested to be executed, but no provider was " + "given. Aborting..."; + return; + } + + std::map<skills::SkillID, skills::SkillDescription> skillDescriptions; + { + skillDescriptions = this->fetchSkills().at(providerId.value()); + } + + if (skillDescriptions.find(skillId) == skillDescriptions.end()) + { + ARMARX_IMPORTANT << "The Skill: '" << skillId.skillName + << "' has been requested to be executed, but no skill description was " + "found. Aborting..."; + return; + } + + char hostname[HOST_NAME_MAX]; + gethostname(hostname, HOST_NAME_MAX); + + skills::SkillExecutionRequest req{ + .skillId = skillId, + .executorName = "Skills.Manager GUI (hostname: " + std::string(hostname) + ")", + .parameters = params}; + + ARMARX_CHECK(skillId.isFullySpecified()); // sanity check + ARMARX_IMPORTANT << "Executing skill from GUI: " << skillId << "."; + + try + { + std::scoped_lock l(mutex_memory); + memory->executeSkillAsync(req.toManagerIce()); + } + catch (Ice::Exception const& e) + { + ARMARX_ERROR << "Unhandeled Ice exception while executing skill '" << skillId.skillName + << "'. Aborting..."; + emit connectionUpdate("Execution failed of skill " + skillId.skillName, e.what()); + } + catch (...) + { + ARMARX_ERROR << "Unhandled error while executing skill '" << skillId.skillName + << "'. Aborting..."; + } + } + + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.h b/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.h new file mode 100644 index 0000000000000000000000000000000000000000..22eec04e20bfd499aa444eae74136be6d4c5490f --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/memory/SkillManagerWrapper.h @@ -0,0 +1,103 @@ +#pragma once + +#include <mutex> + +#include <QObject> + +#include "RobotAPI/libraries/skills/core/ProviderID.h" +#include "RobotAPI/libraries/skills/core/SkillDescription.h" +#include "RobotAPI/libraries/skills/core/SkillExecutionID.h" +#include "RobotAPI/libraries/skills/core/SkillStatusUpdate.h" +#include <RobotAPI/interface/skills/SkillManagerInterface.h> + +namespace armarx::skills::gui +{ + using StatusMap = std::map<skills::SkillExecutionID, skills::SkillStatusUpdate>; + using SkillMap = + std::map<skills::ProviderID, std::map<skills::SkillID, skills::SkillDescription>>; + + class SkillManagerWrapper : public QObject + { + Q_OBJECT + public: + SkillManagerWrapper(skills::manager::dti::SkillManagerInterfacePrx& _memory, + QObject* parent = nullptr) : + QObject(parent), memory(_memory) + { + } + + // We can instantiate the proxy without memory, if it's not connected yet. + SkillManagerWrapper(QObject* parent = nullptr) : QObject(parent) + { + } + + /** + * @brief Updates the memory pointer. This should be called whenever the GUI connects/reconnects. + */ + void connectMemory(skills::manager::dti::SkillManagerInterfacePrx const& updatedMemory); + + // Basic functions to work with. These will produce Ice calls! + /** + * @brief Attempts to start an execution with given parameters. If an Ice exception is encountered, a message will be logged and the method will abort. + * @param skillId The skill to execute. + * @param params The parameters to execute with. + */ + void startExecutionWithParams(skills::SkillID& skillId, aron::data::DictPtr const params); + + /** + * @brief Attempts to stop an execution. If this doesn't succeed, it will retry. + * @param executionId The ExecutionID to abort. + * @param max_retries The amount of times to retry, if aborting fails. + */ + void stopExecution(skills::SkillExecutionID const& executionId, + const unsigned int max_retries = 0); + + /** + * @brief Fetches and returns the latest skills update from memory. + * @return The map representing all skills in memory. Empty, if error occurred. + */ + const SkillMap fetchSkills(); + + /** + * @brief Fetches and returns the latest status update from memory. + * @return The map containing status updates for all execution ids. Empty, if error occurred. + */ + const StatusMap fetchExecutions(); + + static const std::optional<skills::ProviderID> findFirstProvider(SkillMap const& map, + SkillID const& skillId); + + signals: + void connectionUpdate(std::string const& message, std::string const& error); + + public slots: + /** + * @brief Disconnects the interface from memory. + */ + void disconnectMemory(); + + /** + * @brief Applies the search word to the update filter. + * @param search The search word. + */ + void acceptSearchRequest(std::string const& search); + + /** + * @brief Stops all available (and running) executions. + */ + void stopAllExecutions(); + + private: + mutable std::mutex mutex_memory; + + armarx::skills::manager::dti::SkillManagerInterfacePrx memory; + std::string currentSkillSearch = ""; + + /** + * @brief Modifies the given map to match the search. + * @param update The map to modify. + */ + void filterSkillUpdate(std::map<skills::manager::dto::SkillID, + skills::manager::dto::SkillDescription>& update); + }; +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/skill_details/ProfileMenuWidget.cpp b/source/RobotAPI/libraries/skills_gui/skill_details/ProfileMenuWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..20295978a7ea67e75136277af2a1e617e5a9aae5 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skill_details/ProfileMenuWidget.cpp @@ -0,0 +1,43 @@ +#include "ProfileMenuWidget.h" + +#include <QHBoxLayout> +#include <QLabel> +#include <QVBoxLayout> + +namespace armarx::skills::gui +{ + + void + ProfileMenuWidget::setupUi() + { + setArgsFromClipboard = new QPushButton(); + copyArgsToClipboard = new QPushButton(); + resetArgsToProfile = new QPushButton(); + profileSelector = new QComboBox(); + + // layouting + QVBoxLayout* mainLayout = new QVBoxLayout(); + QHBoxLayout* topLayout = new QHBoxLayout(); + + mainLayout->addLayout(topLayout); + mainLayout->addWidget(profileSelector); + + topLayout->addWidget(setArgsFromClipboard); + topLayout->addWidget(copyArgsToClipboard); + // this is not good. We should probably use a QSpacerItem here... + topLayout->addWidget(new QLabel()); + topLayout->addWidget(resetArgsToProfile); + + this->setLayout(mainLayout); + + // Text + setArgsFromClipboard->setText(QString::fromStdString(SET_ARGS_BUTTON_TEXT)); + copyArgsToClipboard->setText(QString::fromStdString(COPY_ARGS_BUTTON_TEXT)); + resetArgsToProfile->setText(QString::fromStdString(RESET_ARGS_BUTTON_TEXT)); + profileSelector->addItem(QString::fromStdString(DEFAULT_PROFILE_TEXT)); + profileSelector->setDisabled(true); + profileSelector->setToolTip(QString::fromStdString(PROFILE_NOT_IMPLEMENTED)); + } + + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/skill_details/ProfileMenuWidget.h b/source/RobotAPI/libraries/skills_gui/skill_details/ProfileMenuWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..32dc1f278649060b97d58f680f2a8c74d5e1f75a --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skill_details/ProfileMenuWidget.h @@ -0,0 +1,40 @@ +#ifndef PROFILEMENUWIDGET_H +#define PROFILEMENUWIDGET_H + +#include <QComboBox> +#include <QPushButton> +#include <QWidget> + +#include "../memory/MemoryCommunicatorBase.h" + +namespace armarx::skills::gui +{ + class ProfileMenuWidget : public QWidget, public MemoryCommunicatorBase + { + Q_OBJECT + public: + static const constexpr char* SET_ARGS_BUTTON_TEXT = "Set Args from Clipboard"; + static const constexpr char* COPY_ARGS_BUTTON_TEXT = "Copy Args to Clipboard"; + static const constexpr char* RESET_ARGS_BUTTON_TEXT = "Reset Args to Profile"; + static const constexpr char* DEFAULT_PROFILE_TEXT = "<No Profile selected. Using root>"; + static const constexpr char* PROFILE_NOT_IMPLEMENTED = + "Profiles other than the root profile are currently not supported."; + + // contents are public, as this class is just a convenience wrapper + QPushButton* setArgsFromClipboard = nullptr; + QPushButton* copyArgsToClipboard = nullptr; + QPushButton* resetArgsToProfile = nullptr; + QComboBox* profileSelector = nullptr; + + ProfileMenuWidget(std::shared_ptr<SkillManagerWrapper> _memory, QWidget* parent = nullptr) : + QWidget(parent), MemoryCommunicatorBase(_memory) + { + setupUi(); + } + + private: + void setupUi(); + }; +} // namespace armarx::skills::gui + +#endif // PROFILEMENUWIDGET_H diff --git a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.cpp b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b54d55c8a21b3ef8f1eb912e52473a9bcfbd230f --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.cpp @@ -0,0 +1,148 @@ +#include "SkillDetailsGroupBox.h" + +namespace armarx::skills::gui +{ + void + SkillDetailGroupBox::updateSkillDetails(SkillID& _skillId) + { + if (_skillId.skillName == SkillID::UNKNOWN) + return; + + auto const& skills = memory->fetchSkills(); + + std::optional<skills::ProviderID> provider_opt; + // check skillId + if (_skillId.isProviderSpecified()) + { + provider_opt = _skillId.providerId; + } + else + { + // find first provider to provide the skill + provider_opt = SkillManagerWrapper::findFirstProvider(skills, _skillId); + } + + if (provider_opt == std::nullopt) + { + // skill was not found in snapshot. Abort... + return; + } + + // construct skill id (with guaranteed valid provider) + skills::SkillID skillId = {provider_opt, _skillId.skillName}; + + + // Maybe the search doesn't include the shown skill? + if (skills.count(skillId.providerId.value()) == 0 || + skills.at(skillId.providerId.value()).count(skillId) == 0) + { + // reset details widget + skillDetailsTreeWidget->resetWidget(); + return; + } + + + // get skill description + skills::SkillDescription descr = skills.at(skillId.providerId.value()).at(skillId); + + // ------------ update widgets ------------ + + setDisabled(true); + + // setup groupBox + this->setTitle(QString::fromStdString(skillId.toString())); + setDisabled(false); + + // setup table view + skillDetailsTreeWidget->updateContents(skillId, descr); + + // description widget + skillDescriptionWidget->setSkillDescription(descr); + + + // add new profiles for this skill + // TODO: Where stored? + } + + void + SkillDetailGroupBox::connectGui(std::string observerName) + { + skillDetailsTreeWidget->setEditTriggers(QAbstractItemView::EditTrigger::NoEditTriggers); + } + + void + SkillDetailGroupBox::handleExecutionRequest() + { + auto data = skillDetailsTreeWidget->getConfigAsAron(); + auto skillId = skillDetailsTreeWidget->getShownId(); + if (skillId.has_value()) + { + memory->startExecutionWithParams(skillId.value(), data); + } + } + + void + SkillDetailGroupBox::resizeEvent(QResizeEvent* event) + { + QGroupBox::resizeEvent(event); + skillDetailsTreeWidget->resizeContents(); + } + + void + SkillDetailGroupBox::setupUi() + { + this->setTitle(QString::fromStdString(GROUP_BOX_TITLE)); + + this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + + // construct all sub widgets + this->executeSkillButton = new QPushButton(); + this->profileMenuWidget = new ProfileMenuWidget(this->memory, this); + this->skillDetailsTreeWidget = new SkillDetailsTreeWidget(this->memory, this); + this->skillDescriptionWidget = new SkillDescriptionWidget(this); + this->layout = new QVBoxLayout(); + + // Layouting + this->setLayout(layout); + + layout->addWidget(this->profileMenuWidget); + layout->addWidget(this->skillDescriptionWidget); + layout->addWidget(this->skillDetailsTreeWidget); + layout->addWidget(this->executeSkillButton); + + // Text + this->executeSkillButton->setText( + QString::fromStdString(SkillDetailGroupBox::EXECUTE_SKILL_BUTTON_TEXT)); + + connectSignals(); + } + + void + SkillDetailGroupBox::connectSignals() + { + connect(executeSkillButton, + &QPushButton::clicked, + this, + &SkillDetailGroupBox::handleExecutionRequest); + connect(this, + &SkillDetailGroupBox::disconnectGui, + skillDetailsTreeWidget, + &SkillDetailsTreeWidget::disconnectGui); + + // profile buttons + connect(profileMenuWidget->copyArgsToClipboard, + &QPushButton::clicked, + skillDetailsTreeWidget, + &SkillDetailsTreeWidget::copyCurrentConfig); + connect(profileMenuWidget->setArgsFromClipboard, + &QPushButton::clicked, + skillDetailsTreeWidget, + &SkillDetailsTreeWidget::pasteCurrentConfig); + connect(profileMenuWidget->resetArgsToProfile, + &QPushButton::clicked, + skillDetailsTreeWidget, + &SkillDetailsTreeWidget::resetCurrentConfig); + } + + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.h b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.h new file mode 100644 index 0000000000000000000000000000000000000000..918fa2ec3241ae512645c878de16d53042fb0005 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsGroupBox.h @@ -0,0 +1,66 @@ +#ifndef SKILLDETAILGROUPBOX_H +#define SKILLDETAILGROUPBOX_H + +#include <QGroupBox> +#include <QPushButton> +#include <QVBoxLayout> + +#include "RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetController.h" +#include "RobotAPI/libraries/skills_gui/aron_tree_widget/widgets/SkillDescriptionWidget.h" + +#include "../aron_tree_widget/widgets/SkillDescriptionWidget.h" +#include "../memory/MemoryCommunicatorBase.h" +#include "ProfileMenuWidget.h" +#include "SkillDetailsTreeWidget.h" + +namespace armarx::skills::gui +{ + class SkillDetailGroupBox : public QGroupBox, public MemoryCommunicatorBase + { + Q_OBJECT + public: + static const constexpr char* EXECUTE_SKILL_BUTTON_TEXT = "Request Execution"; + static const constexpr char* GROUP_BOX_TITLE = "Skill Description"; + + SkillDetailGroupBox(std::shared_ptr<SkillManagerWrapper> _memory, + QWidget* parent = nullptr) : + QGroupBox(parent), MemoryCommunicatorBase(_memory) + { + setupUi(); + } + + signals: + void disconnectGui(); + + /** + * @brief Notify widgets to update themselves + */ + void updateGui(); + + public slots: + /** + * @brief Update subwidgets of an updated skill selection. + * @param skillId + */ + void updateSkillDetails(skills::SkillID& skillId); + + void connectGui(std::string observerName); + + private slots: + void handleExecutionRequest(); + void resizeEvent(QResizeEvent* event) override; + + private: + void setupUi(); + void connectSignals(); + + QVBoxLayout* layout = nullptr; + QPushButton* executeSkillButton = nullptr; + SkillDetailsTreeWidget* skillDetailsTreeWidget = nullptr; + SkillDescriptionWidget* skillDescriptionWidget = nullptr; + + ProfileMenuWidget* profileMenuWidget = nullptr; + }; +} // namespace armarx::skills::gui + +#endif // SKILLDETAILGROUPBOX_H diff --git a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.cpp b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0287c5468f3a6b1f27e182e88bdbf589f4736af5 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.cpp @@ -0,0 +1,219 @@ +#include "SkillDetailsTreeWidget.h" + +#include <QApplication> +#include <QClipboard> +#include <QHeaderView> +#include <QResizeEvent> +#include <QVBoxLayout> + +#include <RobotAPI/libraries/aron/converter/json/NLohmannJSONConverter.h> + +namespace armarx::skills::gui +{ + SkillDetailsTreeWidget::SkillDetailsTreeWidget(std::shared_ptr<SkillManagerWrapper> _memory, + QWidget* parent) : + QTreeWidget(parent), MemoryCommunicatorBase(_memory) + { + setupUi(); + } + + std::optional<SkillID> + SkillDetailsTreeWidget::getShownId() + { + if (shownSkill.has_value()) + { + return shownSkill.value().skillId; + } + else + { + return std::nullopt; + } + } + + void + SkillDetailsTreeWidget::updateContents(const SkillID& skillId, const SkillDescription& descr) + { + // dont touch the widget if the skill id didn't change + // note: this is only relevant when periodic updates are enabled + /* + if (shownSkill.has_value() && skillId == shownSkill.value().skillId) + return; + */ + + // check the parameters: did they change? + if (shownSkill.has_value()) + { + auto remDesc = shownSkill.value().descr; + if (descr.rootProfileDefaults != remDesc.rootProfileDefaults) + { + // TODO: ask the user if they want to reset to defaults + // for now, we just overwrite without asking... (and do nothing) + } + // other cases: if the parameter types change, we *have* to reset; else the skill + // cannot be started with the parameters. + // same goes for the result type. + } + + this->resetWidget(); + + auto aron_args = descr.parametersType; + auto default_args_of_profile = descr.rootProfileDefaults; + // ideally we want to use the invisible root item, but aron expects a parent... + QTreeWidgetItem* aronTreeWidgetItem = new QTreeWidgetItem(this); + aronTreeWidgetItem->setText(0, QString::fromStdString("Parameters")); + + aronTreeWidgetController = std::make_shared<AronTreeWidgetController>( + this, aronTreeWidgetItem, aron_args, default_args_of_profile); + + + this->expandAll(); + resizeContents(); + + // update the ShownSkill + shownSkill = {skillId, descr}; + } + + void + SkillDetailsTreeWidget::disconnectGui() + { + this->aronTreeWidgetController = nullptr; + } + + void + SkillDetailsTreeWidget::updateGui() + { + if (!shownSkill.has_value()) + return; + skills::SkillID sid = shownSkill.value().skillId; + + // we assume the id to be fully specified, as it is checked while constructing + // sanity check + ARMARX_CHECK(sid.isFullySpecified()); + + // maybe the search is empty? + auto skillsMap = memory->fetchSkills(); + if (skillsMap.count(sid.providerId.value()) == 0 || + skillsMap.at(sid.providerId.value()).count(sid) == 0) + { + this->resetWidget(); + return; + } + + auto descr = memory->fetchSkills().at(sid.providerId.value()).at(sid); + + this->updateContents(sid, descr); + } + + void + SkillDetailsTreeWidget::setupUi() + { + this->setColumnCount(3); + + this->setContextMenuPolicy(Qt::CustomContextMenu); + this->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); + + QTreeWidgetItem* qtreewidgetitem2 = this->headerItem(); + qtreewidgetitem2->setText(3, "defaultValue (hidden in GUI)"); + qtreewidgetitem2->setText(2, "Type"); + qtreewidgetitem2->setText(1, "Value"); + qtreewidgetitem2->setText(0, "Key"); + + setColumnHidden(3, true); + } + + /** + * Problem: columns 0 and 1 have arbitrary size; so we want to limit their size, to make sure + * everything is visible without scrolling. Column 2 is limited by design, as it only contains + * type information. + */ + void + SkillDetailsTreeWidget::resizeContents() + { + // take remainder of width (which we want to assign to dynamic columns) + const int widthRemainder = this->width() - typeWidth; + + // we want to assign half of it to each dynamic column + const int dynamicColumnSize = widthRemainder / 2; + + // set width... + + this->setColumnWidth(0, dynamicColumnSize); + + this->setColumnWidth(1, dynamicColumnSize); + } + + aron::data::DictPtr + SkillDetailsTreeWidget::getConfigAsAron() + { + // create argument aron (if there is an accepted type set) + if (aronTreeWidgetController) + { + return aronTreeWidgetController->convertToAron(); + } + return nullptr; + } + + void + SkillDetailsTreeWidget::copyCurrentConfig() + { + auto data = getConfigAsAron(); + if (!data) + { + return; + } + + auto json = aron::data::converter::AronNlohmannJSONConverter::ConvertToNlohmannJSON(data); + QClipboard* clipboard = QApplication::clipboard(); + clipboard->setText(QString::fromStdString(json.dump(2))); + } + + void + SkillDetailsTreeWidget::pasteCurrentConfig() + { + QClipboard* clipboard = QApplication::clipboard(); + std::string s = clipboard->text().toStdString(); + nlohmann::json json = nlohmann::json::parse(s); + auto data = + aron::data::converter::AronNlohmannJSONConverter::ConvertFromNlohmannJSONObject(json); + + if (!aronTreeWidgetController) + { + return; + } + + aronTreeWidgetController->setFromAron(data); + } + + void + SkillDetailsTreeWidget::resetCurrentConfig() + { + // this will always reset the args to the root profile + // good while there is only the root, not good when profiles are properly implemented + + + if (!shownSkill.has_value()) + { + return; + } + skills::SkillID& skillId = shownSkill.value().skillId; + auto& skills = memory->fetchSkills(); + ARMARX_CHECK(skillId.isProviderSpecified()); + + // find description + // did the provider die? + if (skills.count(skillId.providerId.value()) == 0 || + skills.at(skillId.providerId.value()).count(skillId) == 0) + return; + skills::SkillDescription descr = skills.at(skillId.providerId.value()).at(skillId); + this->updateContents(skillId, descr); + } + + void + SkillDetailsTreeWidget::resetWidget() + { + this->clear(); + aronTreeWidgetController = nullptr; + } + + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.h b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..1ac3bcdd1e59c8d6b842943ab09f7da174273273 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skill_details/SkillDetailsTreeWidget.h @@ -0,0 +1,52 @@ +#ifndef SKILLDETAILSTREEWIDGET_H +#define SKILLDETAILSTREEWIDGET_H + +#include <QTreeWidget> + +#include "RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetController.h" + +#include "../memory/MemoryCommunicatorBase.h" + +namespace armarx::skills::gui +{ + class SkillDetailsTreeWidget : public QTreeWidget, public MemoryCommunicatorBase + { + Q_OBJECT + public: + SkillDetailsTreeWidget(std::shared_ptr<SkillManagerWrapper> _memory, + QWidget* parent = nullptr); + + std::optional<skills::SkillID> getShownId(); + void updateContents(skills::SkillID const& skillId, skills::SkillDescription const& descr); + + aron::data::DictPtr getConfigAsAron(); + void copyCurrentConfig(); + void pasteCurrentConfig(); + void resetWidget(); + + public slots: + // this will reset the args to the profile defaults + void resetCurrentConfig(); + void disconnectGui(); + void updateGui(); + void resizeContents(); + + private: + struct ShownSkill + { + skills::SkillID skillId; + skills::SkillDescription descr; + }; + + std::optional<ShownSkill> shownSkill; + + // The size, which we assume the last column to be. + // If the last column has too little space, increase this value. + const int typeWidth = 200; + + AronTreeWidgetControllerPtr aronTreeWidgetController = nullptr; + void setupUi(); + }; +} // namespace armarx::skills::gui + +#endif // SKILLDETAILSTREEWIDGET_H diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.cpp b/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.cpp new file mode 100644 index 0000000000000000000000000000000000000000..91fb11c021677e96890a001a0538fc6cf98f4aa0 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.cpp @@ -0,0 +1,74 @@ +#include "SkillGroupBox.h" + +#include <QHBoxLayout> +#include <QVBoxLayout> + +namespace armarx::skills::gui +{ + void + SkillGroupBox::handleSearch() + { + std::string search = this->searchBar->text().toStdString(); + emit searchRequest(search); + } + + void + SkillGroupBox::connectGui(std::string observerName) + { + setTitle(QString::fromStdString(observerName)); + } + + void + SkillGroupBox::setupUi() + { + this->setTitle(QString::fromStdString(GROUP_BOX_TITLE)); + + this->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + + + // construct widgets + this->searchBar = new QLineEdit(); + this->acceptSearchButton = new QPushButton(); + this->skillTreeWidget = new SkillTreeWidget(memory, this); + + // layouting + QHBoxLayout* searchLayout = new QHBoxLayout(); + QVBoxLayout* layout = new QVBoxLayout(); + this->setLayout(layout); + layout->addLayout(searchLayout); + layout->addWidget(this->skillTreeWidget); + + searchLayout->addWidget(this->searchBar); + searchLayout->addWidget(this->acceptSearchButton); + + // text + this->searchBar->setPlaceholderText(QString::fromStdString("Search ...")); + this->acceptSearchButton->setText(QString::fromStdString("Search")); + + connectSignals(); + } + + void + SkillGroupBox::connectSignals() + { + connect( + this->acceptSearchButton, &QPushButton::clicked, this, &SkillGroupBox::handleSearch); + connect(this->searchBar, &QLineEdit::editingFinished, this, &SkillGroupBox::handleSearch); + connect(this, + &SkillGroupBox::searchRequest, + this->memory.get(), + &SkillManagerWrapper::acceptSearchRequest); + + connect(skillTreeWidget, + &SkillTreeWidget::updateSkillDetails, + this, + &SkillGroupBox::updateSkillDetails); + + // disconnect + connect( + this, &SkillGroupBox::disconnectGui, skillTreeWidget, &SkillTreeWidget::disconnectGui); + + // update cascade + connect(this, &SkillGroupBox::updateGui, skillTreeWidget, &SkillTreeWidget::updateSkills); + } +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.h b/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.h new file mode 100644 index 0000000000000000000000000000000000000000..2ac6044ba72807ef300ae48fa19fe53d9230d1f3 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillGroupBox.h @@ -0,0 +1,62 @@ +#ifndef SKILLGROUPBOX_H +#define SKILLGROUPBOX_H + +#include <QGroupBox> +#include <QLineEdit> +#include <QPushButton> + +#include "../memory/MemoryCommunicatorBase.h" +#include "SkillTreeWidget.h" + +namespace armarx::skills::gui +{ + class SkillGroupBox : public QGroupBox, public MemoryCommunicatorBase + { + Q_OBJECT + public: + static const constexpr char* GROUP_BOX_TITLE = "Skills"; + + SkillGroupBox(std::shared_ptr<SkillManagerWrapper> _memory, QWidget* parent = nullptr) : + QGroupBox(parent), MemoryCommunicatorBase(_memory) + { + setupUi(); + } + + signals: + /** + * @brief Output signal to controller. + */ + void searchRequest(std::string search); + + /** + * @brief Notify the skill description widget of an updated selection. + * @param skillId + */ + void updateSkillDetails(skills::SkillID& skillId); + + void disconnectGui(); + + /** + * @brief Notify widgets to update themselves + */ + void updateGui(); + + public slots: + void connectGui(std::string observerName); + + private slots: + /** + * Takes string from line edit and emits the search request. + */ + void handleSearch(); + + private: + void setupUi(); + void connectSignals(); + QLineEdit* searchBar = nullptr; + QPushButton* acceptSearchButton = nullptr; + SkillTreeWidget* skillTreeWidget = nullptr; + }; +} // namespace armarx::skills::gui + +#endif // SKILLGROUPBOX_H diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.cpp b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9909cc1675edf8ae1ae94a33ae878bde21955498 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.cpp @@ -0,0 +1,152 @@ +#include "SkillTreeWidget.h" + +#include "RobotAPI/libraries/skills_gui/skills/SkillTreeWidgetItem.h" + +namespace armarx::skills::gui +{ + void + SkillTreeWidget::skillSelectionChanged(QTreeWidgetItem* current, int) + { + if (!current) + { + // gui has died? + return; + } + + auto c = dynamic_cast<SkillTreeWidgetItem*>(current); + if (!c) + { + // we don't know what to do with the item. Abort... + return; + } + + selectedSkill.skillId = c->skillId; + emit updateSkillDetails(selectedSkill.skillId); + } + + void + SkillTreeWidget::updateSkills() + { + const auto skills = memory->fetchSkills(); + + // update tree view. Remove non-existing elements + int i = 0; + while (i < this->topLevelItemCount()) + { + auto* providerItem = this->topLevelItem(i); + auto providerName = providerItem->text(0).toStdString(); + skills::ProviderID providerId{.providerName = providerName}; + + if (skills.find(providerId) == skills.end()) + { + providerItem = nullptr; // reset + auto remove = this->takeTopLevelItem(i); + delete remove; + continue; + } + + ++i; + + // sanity check + ARMARX_CHECK(skills.count(providerId) > 0); + auto& providedSkills = skills.at(providerId); + + int j = 0; + while (j < providerItem->childCount()) + { + auto* skillItem = providerItem->child(j); + auto skillName = skillItem->text(0).toStdString(); + + skills::SkillID skillId{.providerId = + skills::ProviderID{.providerName = providerName}, + .skillName = skillName}; + + if (providedSkills.find(skillId) == providedSkills.end()) + { + skillItem = nullptr; + auto remove = providerItem->takeChild(j); + delete remove; + continue; + } + + ++j; + } + } + + // update tree view. Add new elements + for (const auto& [providerId, providedSkills] : skills) + { + QTreeWidgetItem* providerItem = nullptr; + for (int i = 0; i < this->topLevelItemCount(); ++i) + { + auto el = this->topLevelItem(i); + auto providerName = el->text(0).toStdString(); + skills::ProviderID elProviderId{.providerName = providerName}; + + if (providerId == elProviderId) + { + providerItem = el; + break; + } + } + + if (!providerItem) + { + providerItem = new QTreeWidgetItem(this); + providerItem->setText(0, QString::fromStdString(providerId.providerName)); + } + + for (const auto& [skillId, skill] : providedSkills) + { + QTreeWidgetItem* skillItem = nullptr; + for (int i = 0; i < providerItem->childCount(); ++i) + { + auto el = providerItem->child(i); + auto skillName = el->text(0).toStdString(); + skills::SkillID elSkillId{providerId, skillName}; + + if (skillId == elSkillId) + { + skillItem = el; + break; + } + } + + if (!skillItem) + { + skillItem = new SkillTreeWidgetItem(skillId, providerItem); + skillItem->setText(0, QString::fromStdString(skillId.skillName)); + } + } + } + } + + void + SkillTreeWidget::disconnectGui() + { + this->clear(); + selectedSkill = SelectedSkill(); + } + + void + SkillTreeWidget::setupUi() + { + this->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + QTreeWidgetItem* qtreewidgetitem = this->headerItem(); + + qtreewidgetitem->setText(0, "Skill"); + + connectSignals(); + } + + void + SkillTreeWidget::connectSignals() + { + // The selectionChanged signal does not trigger when clicking the active item + // => we use itemClicked + connect(this, &QTreeWidget::itemClicked, this, &SkillTreeWidget::skillSelectionChanged); + } + + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.h b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..c076d2101f0016056f13dd4a75adc7022fa5a50a --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidget.h @@ -0,0 +1,60 @@ +#ifndef ARMARX_SKILLS_GUI_SKILLTREEWIDGET_H +#define ARMARX_SKILLS_GUI_SKILLTREEWIDGET_H + +#include <QTreeWidget> + +#include <RobotAPI/libraries/skills/core/SkillID.h> + +#include "../memory/MemoryCommunicatorBase.h" + +namespace armarx::skills::gui +{ + + class SkillTreeWidget : public QTreeWidget, public MemoryCommunicatorBase + { + Q_OBJECT + public: + SkillTreeWidget(std::shared_ptr<SkillManagerWrapper> _memory, QWidget* parent = nullptr) : + QTreeWidget(parent), MemoryCommunicatorBase(_memory) + { + setupUi(); + } + + struct SelectedSkill + { + skills::SkillID skillId; + + // make default constructable + SelectedSkill() : + skillId({.providerId = std::nullopt, .skillName = skills::SkillID::UNKNOWN}) + { + } + }; + + SelectedSkill& getSelectedSkill(); + + signals: + /** + * @brief Notify the skill description widget of an updated selection. + * @param skillId + */ + void updateSkillDetails(skills::SkillID& skillId); + + public slots: + void disconnectGui(); + void updateSkills(); + + private slots: + void skillSelectionChanged(QTreeWidgetItem* current, int column); + + + private: + SelectedSkill selectedSkill; + + void setupUi(); + void connectSignals(); + }; + +} // namespace armarx::skills::gui + +#endif // ARMARX_SKILLS_GUI_SKILLTREEWIDGET_H diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidgetItem.cpp b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidgetItem.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e4845c11d5bc6264a0f4200d412eb6eb28198b19 --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidgetItem.cpp @@ -0,0 +1,6 @@ +#include "SkillTreeWidgetItem.h" + +namespace armarx::skills::gui +{ + +} // namespace armarx::skills::gui diff --git a/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidgetItem.h b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidgetItem.h new file mode 100644 index 0000000000000000000000000000000000000000..2a2ae418e4f546027753a4faf62daf7693ee0f7a --- /dev/null +++ b/source/RobotAPI/libraries/skills_gui/skills/SkillTreeWidgetItem.h @@ -0,0 +1,26 @@ +#pragma once + +#include <QTreeWidgetItem> +#include "RobotAPI/libraries/skills/core/SkillID.h" + + + +namespace armarx::skills::gui +{ + class SkillTreeWidgetItem : public QTreeWidgetItem + { + public: + SkillTreeWidgetItem(const skills::SkillID& desc, QTreeWidgetItem* parent) : + QTreeWidgetItem(parent), skillId(desc) + { + } + + SkillTreeWidgetItem(const skills::SkillID& desc, QTreeWidget* parent) : + QTreeWidgetItem(parent), skillId(desc) + { + } + + skills::SkillID skillId; + }; +} +