diff --git a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidget.ui b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidget.ui index b6e436bb8e4805a0b0c4ac6d40615b404447333c..e201caac70e9cce63d98578373084921c80f14e0 100644 --- a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidget.ui +++ b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidget.ui @@ -54,7 +54,7 @@ <attribute name="title"> <string>Objects</string> </attribute> - <layout class="QHBoxLayout" name="horizontalLayout_2"> + <layout class="QVBoxLayout" name="verticalLayout_3"> <item> <widget class="QTreeWidget" name="objectsTree"> <property name="alternatingRowColors"> @@ -131,6 +131,18 @@ </item> </widget> </item> + <item> + <widget class="QLabel" name="label"> + <property name="font"> + <font> + <pointsize>9</pointsize> + </font> + </property> + <property name="text"> + <string>Right click an object entry for more options.</string> + </property> + </widget> + </item> </layout> </widget> <widget class="QWidget" name="tabRequest"> diff --git a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp index 4fc35cf54815b585866146d2ce0f8f30ef784285..04fc0dfd0b5fd607406a55567d836659b7141ec1 100644 --- a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp +++ b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.cpp @@ -25,6 +25,7 @@ #include <string> #include <QTimer> +#include <QMenu> #include <ArmarXCore/core/time/TimeUtil.h> @@ -37,12 +38,15 @@ namespace armarx { + static const int OBJECTS_COLUMN_ATTACHMENT = 6; + ObjectPoseGuiWidgetController::ObjectPoseGuiWidgetController() { widget.setupUi(getWidget()); widget.objectsTree->clear(); widget.objectsTree->sortItems(0, Qt::SortOrder::AscendingOrder); + widget.objectsTree->setContextMenuPolicy(Qt::CustomContextMenu); widget.requestTree->clear(); widget.requestTree->sortItems(0, Qt::SortOrder::AscendingOrder); @@ -53,6 +57,9 @@ namespace armarx connect(widget.updateButton, &QPushButton::pressed, this, &This::updateTab); connect(widget.tabWidget, &QTabWidget::currentChanged, this, &This::updateTab); + connect(widget.objectsTree, &QTreeWidget::customContextMenuRequested, + this, &This::prepareObjectContextMenu); + connect(widget.requestButton, &QPushButton::pressed, this, &This::requestSelectedObjects); QTimer* timer = new QTimer(this); @@ -126,6 +133,16 @@ namespace armarx { getProxy(objectPoseObserver, objectPoseObserverName); } + + this->attachableFrames = objectPoseObserver->getAttachableFrames(); + std::sort(attachableFrames.begin(), attachableFrames.end(), [](const auto & lhs, const auto & rhs) + { + return lhs.agent < rhs.agent; + }); + for (objpose::AgentFrames& frames : attachableFrames) + { + std::sort(frames.frames.begin(), frames.frames.end()); + } } void ObjectPoseGuiWidgetController::onDisconnectComponent() @@ -220,7 +237,7 @@ namespace armarx std::stringstream ss; if (pose.attachment) { - ss << pose.attachment->agentName << "/" << pose.attachment->frameName; + ss << pose.attachment->frameName << " (" << pose.attachment->agentName << ")"; } item->setText(col++, QString::fromStdString(ss.str())); } @@ -314,6 +331,82 @@ namespace armarx ARMARX_VERBOSE << "Gui update took " << (IceUtil::Time::now() - start).toMilliSecondsDouble() << " ms."; } + void ObjectPoseGuiWidgetController::prepareObjectContextMenu(const QPoint& pos) + { + QTreeWidget* tree = widget.objectsTree; + QTreeWidgetItem* item = tree->itemAt(pos); + + if (item == nullptr || item->parent() == nullptr) + { + // No item or top level item => no context menu. + return; + } + + QString providerName = item->parent()->text(0); + QString objectID = item->text(0); + + QMenu* attachMenu = new QMenu("Attach to robot node", tree); + for (const objpose::AgentFrames& agentFrames : attachableFrames) + { + QMenu* agentMenu = new QMenu(QString::fromStdString(agentFrames.agent), tree); + + for (const std::string& frame : agentFrames.frames) + { + QAction* attachAgentAction = new QAction(QString::fromStdString(frame), tree); + // attachAgentAction->setStatusTip(tr("Attach object rigidly to a robot node")); + connect(attachAgentAction, &QAction::triggered, + [ = ]() + { + this->attachObjectToRobotNode(providerName, objectID, agentFrames.agent, frame); + }); + agentMenu->addAction(attachAgentAction); + } + attachMenu->addMenu(agentMenu); + } + + QAction* detachAction = new QAction(tr("Detach from to robot node"), tree); + detachAction->setEnabled(!item->text(OBJECTS_COLUMN_ATTACHMENT).isEmpty()); + connect(detachAction, &QAction::triggered, [ = ]() + { + this->detachObjectFromRobotNode(providerName, objectID); + }); + + QMenu menu(tree); + menu.addMenu(attachMenu); + menu.addAction(detachAction); + + menu.exec(tree->mapToGlobal(pos)); + } + + void ObjectPoseGuiWidgetController::attachObjectToRobotNode( + QString providerName, QString objectID, + const std::string& agentName, const std::string& frameName) + { + ARMARX_VERBOSE << "Attaching " << objectID << " by '" << providerName << "' to robot node '" + << frameName << "' of agent '" << agentName << "'."; + + objpose::AttachObjectToRobotNodeInput input; + input.providerName = providerName.toStdString(); + input.objectID = armarx::toIce(armarx::ObjectID(objectID.toStdString())); + input.agentName = agentName; + input.frameName = frameName; + + objpose::AttachObjectToRobotNodeOutput output = objectPoseObserver->attachObjectToRobotNode(input); + ARMARX_VERBOSE << "Success of attaching: " << output.success; + } + + void ObjectPoseGuiWidgetController::detachObjectFromRobotNode(QString providerName, QString objectID) + { + ARMARX_VERBOSE << "Detaching " << objectID << " by '" << providerName << "' from robot node."; + + objpose::DetachObjectFromRobotNodeInput input; + input.providerName = providerName.toStdString(); + input.objectID = armarx::toIce(armarx::ObjectID(objectID.toStdString())); + + objpose::DetachObjectFromRobotNodeOutput output = objectPoseObserver->detachObjectFromRobotNode(input); + ARMARX_VERBOSE << "Was attached: " << output.wasAttached; + } + void ObjectPoseGuiWidgetController::requestSelectedObjects() { std::map<std::string, objpose::observer::RequestObjectsInput> requestsPerProvider; diff --git a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.h b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.h index 26767131953cdd7f756951fdf011597d7716426b..84a0e6e964ffdd2cead4f144627f478f41c94d10 100644 --- a/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.h +++ b/source/RobotAPI/gui-plugins/ObjectPoseGui/ObjectPoseGuiWidgetController.h @@ -97,6 +97,11 @@ namespace armarx void updateObjectsTab(); void updateRequestTab(); + void prepareObjectContextMenu(const QPoint& pos); + void attachObjectToRobotNode(QString providerName, QString objectID, + const std::string& agentName, const std::string& frameName); + void detachObjectFromRobotNode(QString providerName, QString objectID); + void requestSelectedObjects(); @@ -113,6 +118,7 @@ namespace armarx std::string objectPoseObserverName; armarx::objpose::ObjectPoseObserverInterfacePrx objectPoseObserver; + objpose::AgentFramesSeq attachableFrames; }; }