Skip to content
Snippets Groups Projects
SkillDetailsTreeWidget.cpp 10.4 KiB
Newer Older
#include "SkillDetailsTreeWidget.h"
Peter Albrecht's avatar
Peter Albrecht committed

Peter Albrecht's avatar
Peter Albrecht committed
#include <optional>
#include <QApplication>
#include <QClipboard>
#include <QHeaderView>
#include <QResizeEvent>
#include <QVBoxLayout>
Peter Albrecht's avatar
Peter Albrecht committed
#include <qchar.h>
Peter Albrecht's avatar
Peter Albrecht committed
#include <qcheckbox.h>
Peter Albrecht's avatar
Peter Albrecht committed
#include <qmessagebox.h>
Peter Albrecht's avatar
Peter Albrecht committed
#include <qpushbutton.h>
#include <qtreewidget.h>
Peter Albrecht's avatar
Peter Albrecht committed

#include <ArmarXCore/core/logging/Logging.h>
Peter Albrecht's avatar
Peter Albrecht committed
#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>

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.
        }


        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();
        emit updated(skillId);

        // update the ShownSkill
Peter Albrecht's avatar
Peter Albrecht committed
        shownSkill = {skillId, descr, aronTreeWidgetController->convertToAron()};
    }

    void
    SkillDetailsTreeWidget::disconnectGui()
    {
        this->aronTreeWidgetController = nullptr;
    }
    SkillDetailsTreeWidget::updateGui(SkillManagerWrapper::Snapshot update)
Peter Albrecht's avatar
Peter Albrecht committed
        if (not shownSkill.has_value())
        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?
Peter Albrecht's avatar
Peter Albrecht committed
        auto skillsMap = update.skills;
        if (skillsMap.count(sid.providerId.value()) == 0 ||
            skillsMap.at(sid.providerId.value()).count(sid) == 0)
        {
            this->resetWidget();
            return;
        }
Peter Albrecht's avatar
Peter Albrecht committed
        auto descr = update.skills.at(sid.providerId.value()).at(sid);
Peter Albrecht's avatar
Peter Albrecht committed
        // 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...";
Peter Albrecht's avatar
Peter Albrecht committed
            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);
Peter Albrecht's avatar
Peter Albrecht committed

    bool
Peter Albrecht's avatar
Peter Albrecht committed
    SkillDetailsTreeWidget::askUserToConfirmWidgetReset()
Peter Albrecht's avatar
Peter Albrecht committed
        std::string msg = "The skill details widget will be reset. All changes will be lost, unless saved.";
Peter Albrecht's avatar
Peter Albrecht committed
        QMessageBox msgBox;
        msgBox.setText(QString::fromStdString(msg));
Peter Albrecht's avatar
Peter Albrecht committed
        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();

Peter Albrecht's avatar
Peter Albrecht committed
        return true;
Peter Albrecht's avatar
Peter Albrecht committed
    
    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;
        }

Peter Albrecht's avatar
Peter Albrecht committed
        bool modified = *defaults.get() != *params.get();
        if (modified)
        {
            ARMARX_DEBUG << "The parameters have been modified.";
Peter Albrecht's avatar
Peter Albrecht committed
        }
        return 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)));
    }
Peter Albrecht's avatar
Peter Albrecht committed
    
    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);
    }

        if (checkIfParametersAreModified())
            if (shownSkill.has_value())
                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);
Peter Albrecht's avatar
Peter Albrecht committed
        this->shownSkill = std::nullopt;

} // namespace armarx::skills::gui