Skip to content
Snippets Groups Projects
Commit 916d68b4 authored by Christian Dreher's avatar Christian Dreher
Browse files

Merge branch 'feature/skillMemoryGUI' into 'master'

Refactor skill memory GUI

See merge request !406
parents d446ae4f 911e0849
No related branches found
No related tags found
1 merge request!406Refactor skill memory GUI
Pipeline #17360 passed
Showing
with 1056 additions and 1015 deletions
......@@ -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
......@@ -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)
......
......@@ -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>&lt;No Profile selected. Using root&gt;</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/>
......
......@@ -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;
......
......@@ -41,3 +41,5 @@ add_subdirectory(GraspingUtility)
add_subdirectory(obstacle_avoidance)
add_subdirectory(robot_name_service)
add_subdirectory(skills_gui)
#include "ProviderID.h"
#include "SkillID.h"
namespace armarx
{
namespace skills
......
......@@ -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>
......
......@@ -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;
......
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)
#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
#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
#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
#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
#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
#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
#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
#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
#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
#pragma once
#include <QPalette>
namespace armarx::skills::gui::gui_color_palette
{
QPalette getNormalPalette();
QPalette getErrorPalette();
QPalette getIndirectErrorPalette();
} // namespace armarx::skills::gui::gui_color_palette
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment