Newer
Older
#include <QApplication>
#include <QClipboard>
#include <qpushbutton.h>
#include <qtreewidget.h>
#include <ArmarXCore/core/logging/Logging.h>
#include "RobotAPI/libraries/skills/core/SkillID.h"
#include "RobotAPI/libraries/skills_gui/aron_tree_widget/AronTreeWidgetController.h"
#include <RobotAPI/libraries/aron/converter/json/NLohmannJSONConverter.h>
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);
shownSkill = {skillId, descr, aronTreeWidgetController->convertToAron()};
}
void
SkillDetailsTreeWidget::disconnectGui()
{
this->aronTreeWidgetController = nullptr;
}
SkillDetailsTreeWidget::updateGui(SkillManagerWrapper::Snapshot update)
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?
if (skillsMap.count(sid.providerId.value()) == 0 ||
skillsMap.at(sid.providerId.value()).count(sid) == 0)
{
this->resetWidget();
return;
}
auto descr = update.skills.at(sid.providerId.value()).at(sid);
// only triggers if the description does not match
// TODO: for some reason the == operator does not work for the aron ObjectPtrs (data and type) in the description.
// We exclude them here, but it should be adressed elsewhere...
if (descr.skillId != shownSkill->descr.skillId || descr.timeout != shownSkill->descr.timeout || descr.description != shownSkill->descr.description)
ARMARX_WARNING << "The skill description of the currently shown skill has changed. Resetting the widget...";
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");
SkillDetailsTreeWidget::askUserToConfirmWidgetReset()
std::string msg = "The skill details widget will be reset. All changes will be lost, unless saved.";
QMessageBox msgBox;
msgBox.setText(QString::fromStdString(msg));
QCheckBox* ignoreCheckbox = new QCheckBox("Do not ask again in this session.");
msgBox.setCheckBox(ignoreCheckbox);
QPushButton* copyParamsButton = msgBox.addButton(tr("Copy Parameters && Close"), QMessageBox::ActionRole);
QPushButton* copySkillIdButton = msgBox.addButton(tr("Copy SkillID && Close"), QMessageBox::ActionRole);
QPushButton* closeWithoutSavingButton = msgBox.addButton(tr("Close"), QMessageBox::ActionRole);
msgBox.setDefaultButton(closeWithoutSavingButton);
QObject::connect(ignoreCheckbox, &QCheckBox::stateChanged, [this](int state){
if (static_cast<Qt::CheckState>(state) == Qt::CheckState::Checked) {
this->showWidgetResetConfirmation_ = false;
}
});
connect(copyParamsButton, &QPushButton::pressed, this, &SkillDetailsTreeWidget::copyCurrentConfig);
msgBox.exec();
bool SkillDetailsTreeWidget::checkIfParametersAreModified()
{
if (not shownSkill.has_value())
{
return false;
}
auto defaults = shownSkill->originalParameters;
auto params = this->aronTreeWidgetController->convertToAron();
if (defaults == nullptr && params == nullptr)
{
return false;
}
bool modified = *defaults.get() != *params.get();
if (modified)
{
ARMARX_DEBUG << "The parameters have been modified.";
/**
* 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);
}
void SkillDetailsTreeWidget::reloadLastParameters()
{
if (not shownSkill.has_value())
{
return;
}
auto params = memory->getLatestParametersForSkill(shownSkill->skillId);
if (not params.has_value())
{
return;
}
ARMARX_INFO << "Reloaded parameters from the last execution";
aronTreeWidgetController->setFromAron(params.value());
}
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::copyCurrentSkillID()
{
if (not shownSkill.has_value())
{
return;
}
QClipboard* clipboard = QApplication::clipboard();
clipboard->setText(QString::fromStdString(shownSkill->skillId.toString()));
}
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;
const auto skills = memory->getSkills();
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()
{
if (checkIfParametersAreModified())
ARMARX_IMPORTANT << "A skill parametrization has been lost in the GUI. It can now be reloaded.";
memory->addParametersToHistory(shownSkill->skillId, aronTreeWidgetController->convertToAron());
emit updated(shownSkill->skillId);
this->clear();
aronTreeWidgetController = nullptr;
}