diff --git a/include/ignition/gazebo/Conversions.hh b/include/ignition/gazebo/Conversions.hh index 985bec6a97..e39b579340 100644 --- a/include/ignition/gazebo/Conversions.hh +++ b/include/ignition/gazebo/Conversions.hh @@ -49,6 +49,7 @@ #include #include #include +#include #include #include #include @@ -709,6 +710,38 @@ namespace ignition /// \return Particle emitter message. template<> sdf::ParticleEmitter convert(const msgs::ParticleEmitter &_in); + + /// \brief Generic conversion from an SDF element to another type. + /// \param[in] _in SDF element. + /// \return Conversion result. + /// \tparam Out Output type. + template + Out convert(const sdf::Element &/*_in*/) + { + Out::ConversionNotImplemented; + } + + /// \brief Specialized conversion from an SDF element to a plugin message. + /// \param[in] _in SDF element. + /// \return Plugin message. + template<> + msgs::Plugin convert(const sdf::Element &_in); + + /// \brief Generic conversion from an SDF plugin to another type. + /// \param[in] _in SDF plugin. + /// \return Conversion result. + /// \tparam Out Output type. + template + Out convert(const sdf::Plugin &/*_in*/) + { + Out::ConversionNotImplemented; + } + + /// \brief Specialized conversion from an SDF plugin to a plugin message. + /// \param[in] _in SDF plugin. + /// \return Plugin message. + template<> + msgs::Plugin convert(const sdf::Plugin &_in); } } } diff --git a/include/ignition/gazebo/components/SystemPluginInfo.hh b/include/ignition/gazebo/components/SystemPluginInfo.hh new file mode 100644 index 0000000000..365886c1e4 --- /dev/null +++ b/include/ignition/gazebo/components/SystemPluginInfo.hh @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#ifndef IGNITION_GAZEBO_COMPONENTS_SYSTEMINFO_HH_ +#define IGNITION_GAZEBO_COMPONENTS_SYSTEMINFO_HH_ + +#include +#include +#include +#include +#include + +namespace ignition +{ +namespace gazebo +{ +// Inline bracket to help doxygen filtering. +inline namespace IGNITION_GAZEBO_VERSION_NAMESPACE { +namespace components +{ + /// \brief This component holds information about all the system plugins that + /// are attached to an entity. The content of each system is populated the + /// moment the plugin is instantiated and isn't modified throughout + /// simulation. + using SystemPluginInfo = Component; + IGN_GAZEBO_REGISTER_COMPONENT("ign_gazebo_components.SystemPluginInfo", + SystemPluginInfo) +} +} +} +} + +#endif diff --git a/src/Conversions.cc b/src/Conversions.cc index b2670983cb..28f07167d3 100644 --- a/src/Conversions.cc +++ b/src/Conversions.cc @@ -711,28 +711,13 @@ msgs::GUI ignition::gazebo::convert(const sdf::Gui &_in) out.set_fullscreen(_in.Fullscreen()); // Set gui plugins - auto elem = _in.Element(); - if (elem && elem->HasElement("plugin")) + for (uint64_t i = 0; i < _in.PluginCount(); ++i) { - auto pluginElem = elem->GetElement("plugin"); - while (pluginElem) - { - auto pluginMsg = out.add_plugin(); - pluginMsg->set_name(pluginElem->Get("name")); - pluginMsg->set_filename(pluginElem->Get("filename")); - - std::stringstream ss; - for (auto innerElem = pluginElem->GetFirstElement(); - innerElem; innerElem = innerElem->GetNextElement("")) - { - ss << innerElem->ToString(""); - } - pluginMsg->set_innerxml(ss.str()); - pluginElem = pluginElem->GetNextElement("plugin"); - } + auto pluginMsg = out.add_plugin(); + pluginMsg->CopyFrom(convert(*_in.PluginByIndex(i))); } - if (elem->HasElement("camera")) + if (_in.Element()->HasElement("camera")) { ignwarn << " can't be converted yet" << std::endl; } @@ -1759,3 +1744,51 @@ sdf::ParticleEmitter ignition::gazebo::convert(const msgs::ParticleEmitter &_in) return out; } + +////////////////////////////////////////////////// +template<> +IGNITION_GAZEBO_VISIBLE +msgs::Plugin ignition::gazebo::convert(const sdf::Element &_in) +{ + msgs::Plugin result; + + if (_in.GetName() != "plugin") + { + ignerr << "Tried to convert SDF [" << _in.GetName() + << "] into [plugin]" << std::endl; + return result; + } + + result.set_name(_in.Get("name")); + result.set_filename(_in.Get("filename")); + + std::stringstream ss; + for (auto innerElem = _in.GetFirstElement(); innerElem; + innerElem = innerElem->GetNextElement("")) + { + ss << innerElem->ToString(""); + } + result.set_innerxml(ss.str()); + + return result; +} + +////////////////////////////////////////////////// +template<> +IGNITION_GAZEBO_VISIBLE +msgs::Plugin ignition::gazebo::convert(const sdf::Plugin &_in) +{ + msgs::Plugin result; + + result.set_name(_in.Name()); + result.set_filename(_in.Filename()); + + std::stringstream ss; + for (auto content : _in.Contents()) + { + ss << content->ToString(""); + } + result.set_innerxml(ss.str()); + + return result; +} diff --git a/src/Conversions_TEST.cc b/src/Conversions_TEST.cc index 9123dd497c..4dc334fd6f 100644 --- a/src/Conversions_TEST.cc +++ b/src/Conversions_TEST.cc @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -1060,3 +1061,51 @@ TEST(Conversions, ParticleEmitter) EXPECT_EQ(emitter2.RawPose(), emitter.RawPose()); EXPECT_FLOAT_EQ(emitter2.ScatterRatio(), emitter.ScatterRatio()); } + +///////////////////////////////////////////////// +TEST(Conversions, PluginElement) +{ + sdf::Root root; + root.LoadSdfString("" + "" + " " + " 0.5" + " " + ""); + + auto world = root.WorldByIndex(0); + ASSERT_NE(nullptr, world); + + auto worldElem = world->Element(); + ASSERT_NE(nullptr, worldElem); + + auto pluginElem = worldElem->GetElement("plugin"); + ASSERT_NE(nullptr, pluginElem); + + auto pluginMsg = convert(*(pluginElem.get())); + EXPECT_EQ("plum", pluginMsg.filename()); + EXPECT_EQ("peach", pluginMsg.name()); + + EXPECT_NE(pluginMsg.innerxml().find("0.5"), + std::string::npos); +} + +///////////////////////////////////////////////// +TEST(Conversions, Plugin) +{ + sdf::Plugin pluginSdf; + pluginSdf.SetName("peach"); + pluginSdf.SetFilename("plum"); + + auto content = std::make_shared(); + content->SetName("avocado"); + content->AddValue("double", "0.5", false, ""); + pluginSdf.InsertContent(content); + + auto pluginMsg = convert(pluginSdf); + EXPECT_EQ("plum", pluginMsg.filename()); + EXPECT_EQ("peach", pluginMsg.name()); + + EXPECT_NE(pluginMsg.innerxml().find("0.5"), + std::string::npos) << pluginMsg.innerxml(); +} diff --git a/src/SystemManager.cc b/src/SystemManager.cc index 5f75fc3ec9..902804a59d 100644 --- a/src/SystemManager.cc +++ b/src/SystemManager.cc @@ -15,6 +15,8 @@ * */ +#include "ignition/gazebo/components/SystemPluginInfo.hh" +#include "ignition/gazebo/Conversions.hh" #include "SystemManager.hh" using namespace ignition; @@ -120,6 +122,29 @@ void SystemManager::AddSystemImpl( SystemInternal _system, std::shared_ptr _sdf) { + // Add component + if (this->entityCompMgr && kNullEntity != _system.parentEntity) + { + msgs::Plugin_V systemInfoMsg; + auto systemInfoComp = + this->entityCompMgr->Component( + _system.parentEntity); + if (systemInfoComp) + { + systemInfoMsg = systemInfoComp->Data(); + } + if (_sdf) + { + auto pluginMsg = systemInfoMsg.add_plugins(); + pluginMsg->CopyFrom(convert(*_sdf.get())); + } + + this->entityCompMgr->SetComponentData( + _system.parentEntity, systemInfoMsg); + this->entityCompMgr->SetChanged(_system.parentEntity, + components::SystemPluginInfo::typeId); + } + // Configure the system, if necessary if (_system.configure && this->entityCompMgr && this->eventMgr) { @@ -160,16 +185,15 @@ const std::vector& SystemManager::SystemsPostUpdate() ////////////////////////////////////////////////// std::vector SystemManager::TotalByEntity(Entity _entity) { + auto checkEntity = [&](const SystemInternal &_system) + { + return _system.parentEntity == _entity; + }; + std::vector result; - for (auto system : this->systems) - { - if (system.parentEntity == _entity) - result.push_back(system); - } - for (auto system : this->pendingSystems) - { - if (system.parentEntity == _entity) - result.push_back(system); - } + std::copy_if(this->systems.begin(), this->systems.end(), + std::back_inserter(result), checkEntity); + std::copy_if(this->pendingSystems.begin(), this->pendingSystems.end(), + std::back_inserter(result), checkEntity); return result; } diff --git a/src/SystemManager.hh b/src/SystemManager.hh index 1d83272787..bb6db6e593 100644 --- a/src/SystemManager.hh +++ b/src/SystemManager.hh @@ -112,13 +112,20 @@ namespace ignition /// \return Vector of systems. public: std::vector TotalByEntity(Entity _entity); + /// \brief Implementation for AddSystem functions that takes an SDF + /// element. This calls the AddSystemImpl that accepts an SDF Plugin. + /// \param[in] _system Generic representation of a system. + /// \param[in] _sdf SDF element. + private: void AddSystemImpl(SystemInternal _system, + std::shared_ptr _sdf); + /// \brief Implementation for AddSystem functions. This only adds systems /// to a queue, the actual addition is performed by `AddSystemToRunner` at /// the appropriate time. /// \param[in] _system Generic representation of a system. /// \param[in] _sdf SDF received from AddSystem. private: void AddSystemImpl(SystemInternal _system, - std::shared_ptr _sdf); + const sdf::Plugin &_sdf); /// \brief All the systems. private: std::vector systems; diff --git a/src/SystemManager_TEST.cc b/src/SystemManager_TEST.cc index 454f53e45a..b53d63d24c 100644 --- a/src/SystemManager_TEST.cc +++ b/src/SystemManager_TEST.cc @@ -21,6 +21,7 @@ #include "ignition/gazebo/System.hh" #include "ignition/gazebo/SystemLoader.hh" #include "ignition/gazebo/Types.hh" +#include "ignition/gazebo/components/SystemPluginInfo.hh" #include "ignition/gazebo/test_config.hh" // NOLINT(build/include) #include "SystemManager.hh" @@ -202,3 +203,60 @@ TEST(SystemManager, AddSystemEcm) EXPECT_EQ(1u, systemMgr.SystemsPostUpdate().size()); } +///////////////////////////////////////////////// +TEST(SystemManager, AddSystemWithInfo) +{ + auto loader = std::make_shared(); + + EntityComponentManager ecm; + auto entity = ecm.CreateEntity(); + EXPECT_NE(kNullEntity, entity); + + auto eventManager = EventManager(); + + SystemManager systemMgr(loader, &ecm, &eventManager); + + // No element, no SystemPluginInfo component + auto configSystem = std::make_shared(); + systemMgr.AddSystem(configSystem, entity, nullptr); + + // Element becomes SystemPluginInfo component + auto pluginElem = std::make_shared(); + sdf::initFile("plugin.sdf", pluginElem); + sdf::readString("" + " " + " 0.5" + " " + "", pluginElem); + + auto updateSystem = std::make_shared(); + systemMgr.AddSystem(updateSystem, entity, pluginElem); + + int entityCount{0}; + ecm.Each( + [&](const Entity &_entity, + const components::SystemPluginInfo *_systemInfoComp) -> bool + { + EXPECT_EQ(entity, _entity); + + EXPECT_NE(nullptr, _systemInfoComp); + if (nullptr == _systemInfoComp) + return true; + + auto pluginsMsg = _systemInfoComp->Data(); + EXPECT_EQ(1, pluginsMsg.plugins().size()); + if (1u != pluginsMsg.plugins().size()) + return true; + + auto pluginMsg = pluginsMsg.plugins(0); + EXPECT_EQ("plum", pluginMsg.filename()); + EXPECT_EQ("peach", pluginMsg.name()); + EXPECT_NE(pluginMsg.innerxml().find("0.5"), + std::string::npos); + + entityCount++; + return true; + }); + EXPECT_EQ(1, entityCount); +} + diff --git a/src/gui/plugins/component_inspector/CMakeLists.txt b/src/gui/plugins/component_inspector/CMakeLists.txt index d4bc795abc..53119092b1 100644 --- a/src/gui/plugins/component_inspector/CMakeLists.txt +++ b/src/gui/plugins/component_inspector/CMakeLists.txt @@ -2,7 +2,9 @@ gz_add_gui_plugin(ComponentInspector SOURCES ComponentInspector.cc Pose3d.cc + SystemPluginInfo.cc QT_HEADERS ComponentInspector.hh Pose3d.hh + SystemPluginInfo.hh ) diff --git a/src/gui/plugins/component_inspector/ComponentInspector.cc b/src/gui/plugins/component_inspector/ComponentInspector.cc index 5bf8124493..4185b826fb 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.cc +++ b/src/gui/plugins/component_inspector/ComponentInspector.cc @@ -62,6 +62,7 @@ #include "ignition/gazebo/components/SourceFilePath.hh" #include "ignition/gazebo/components/SphericalCoordinates.hh" #include "ignition/gazebo/components/Static.hh" +#include "ignition/gazebo/components/SystemPluginInfo.hh" #include "ignition/gazebo/components/ThreadPitch.hh" #include "ignition/gazebo/components/Transparency.hh" #include "ignition/gazebo/components/Visual.hh" @@ -73,6 +74,7 @@ #include "ComponentInspector.hh" #include "Pose3d.hh" +#include "SystemPluginInfo.hh" namespace ignition::gazebo { @@ -114,6 +116,9 @@ namespace ignition::gazebo /// \brief Handles all components displayed as a 3D pose. public: std::unique_ptr pose3d; + + /// \brief Handles all system info components. + public: std::unique_ptr systemInfo; }; } @@ -475,6 +480,8 @@ void ComponentInspector::LoadConfig(const tinyxml2::XMLElement *) // Type-specific handlers this->dataPtr->pose3d = std::make_unique(this); + this->dataPtr->systemInfo = + std::make_unique(this); } ////////////////////////////////////////////////// diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qml b/src/gui/plugins/component_inspector/ComponentInspector.qml index 90a1e53dcb..21f43e9506 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qml +++ b/src/gui/plugins/component_inspector/ComponentInspector.qml @@ -245,6 +245,14 @@ Rectangle { delegate: Loader { id: loader + + /** + * Most items can access model.data directly, but items like ListView, + * which have their own `model` property, can't. They can use + * `componentData` instead. + */ + property var componentData: model.data + source: delegateQml(model) } } diff --git a/src/gui/plugins/component_inspector/ComponentInspector.qrc b/src/gui/plugins/component_inspector/ComponentInspector.qrc index 79b7d8a30d..24b9a85cfb 100644 --- a/src/gui/plugins/component_inspector/ComponentInspector.qrc +++ b/src/gui/plugins/component_inspector/ComponentInspector.qrc @@ -12,6 +12,7 @@ Pose3d.qml SphericalCoordinates.qml String.qml + SystemPluginInfo.qml TypeHeader.qml Vector3d.qml plottable_icon.svg diff --git a/src/gui/plugins/component_inspector/ExpandingTypeHeader.qml b/src/gui/plugins/component_inspector/ExpandingTypeHeader.qml index c233ecf775..f36a0c6f6f 100644 --- a/src/gui/plugins/component_inspector/ExpandingTypeHeader.qml +++ b/src/gui/plugins/component_inspector/ExpandingTypeHeader.qml @@ -34,6 +34,9 @@ Rectangle { // Left indentation property int indentation: 10 + // Expose TypeHeader's custom tooltip + property alias customToolTip: typeHeader.customToolTip + RowLayout { anchors.fill: parent Item { @@ -55,7 +58,17 @@ Rectangle { Layout.fillWidth: true } } + ToolTip { + visible: ma.containsMouse + delay: tooltipDelay + text: typeHeader.customToolTip.length > 0 ? + typeHeader.customToolTip : typeHeader.tooltipText(componentData) + y: typeHeader.y - 30 + enter: null + exit: null + } MouseArea { + id: ma anchors.fill: parent hoverEnabled: true cursorShape: Qt.PointingHandCursor diff --git a/src/gui/plugins/component_inspector/SystemPluginInfo.cc b/src/gui/plugins/component_inspector/SystemPluginInfo.cc new file mode 100644 index 0000000000..e8ecf8a932 --- /dev/null +++ b/src/gui/plugins/component_inspector/SystemPluginInfo.cc @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#include + +#include +#include + +#include "SystemPluginInfo.hh" +#include "ComponentInspector.hh" +#include "Types.hh" + +using namespace ignition; +using namespace gazebo; +using namespace inspector; + +///////////////////////////////////////////////// +SystemPluginInfo::SystemPluginInfo(ComponentInspector *_inspector) +{ + _inspector->Context()->setContextProperty("SystemPluginInfoImpl", this); + this->inspector = _inspector; + + this->inspector->AddUpdateViewCb(components::SystemPluginInfo::typeId, + std::bind(&SystemPluginInfo::UpdateView, this, std::placeholders::_1, + std::placeholders::_2)); +} + +///////////////////////////////////////////////// +void SystemPluginInfo::UpdateView(const EntityComponentManager &_ecm, + QStandardItem *_item) +{ + auto comp = _ecm.Component( + this->inspector->GetEntity()); + if (nullptr == _item || nullptr == comp) + return; + + auto msg = comp->Data(); + + _item->setData(QString("SystemPluginInfo"), + ComponentsModel::RoleNames().key("dataType")); + + QList pluginList; + for (int i = 0; i < msg.plugins().size(); ++i) + { + QList dataList; + dataList.push_back( + QVariant(QString::fromStdString(msg.plugins(i).name()))); + dataList.push_back( + QVariant(QString::fromStdString(msg.plugins(i).filename()))); + dataList.push_back( + QVariant(QString::fromStdString(msg.plugins(i).innerxml()))); + pluginList.push_back(dataList); + } + + _item->setData(pluginList, + ComponentsModel::RoleNames().key("data")); +} + diff --git a/src/gui/plugins/component_inspector/SystemPluginInfo.hh b/src/gui/plugins/component_inspector/SystemPluginInfo.hh new file mode 100644 index 0000000000..43c2698c9c --- /dev/null +++ b/src/gui/plugins/component_inspector/SystemPluginInfo.hh @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +#ifndef IGNITION_GAZEBO_GUI_COMPONENTINSPECTOR_SYSTEMINFO_HH_ +#define IGNITION_GAZEBO_GUI_COMPONENTINSPECTOR_SYSTEMINFO_HH_ + +#include "ignition/gazebo/EntityComponentManager.hh" + +#include +#include + +namespace ignition +{ +namespace gazebo +{ +class ComponentInspector; +namespace inspector +{ + /// \brief A class that handles SystemPluginInfo components. + class SystemPluginInfo : public QObject + { + Q_OBJECT + + /// \brief Constructor + /// \param[in] _inspector The component inspector. + public: explicit SystemPluginInfo(ComponentInspector *_inspector); + + /// \brief Callback when there are ECM updates. + /// \param[in] _ecm Immutable reference to the ECM. + /// \param[in] _item Item to update. + public: void UpdateView(const EntityComponentManager &_ecm, + QStandardItem *_item); + + /// \brief Pointer to the component inspector. This is used to add + /// callbacks. + private: ComponentInspector *inspector{nullptr}; + }; +} +} +} +#endif diff --git a/src/gui/plugins/component_inspector/SystemPluginInfo.qml b/src/gui/plugins/component_inspector/SystemPluginInfo.qml new file mode 100644 index 0000000000..2e4d487b7f --- /dev/null +++ b/src/gui/plugins/component_inspector/SystemPluginInfo.qml @@ -0,0 +1,235 @@ +/* + * Copyright (C) 2022 Open Source Robotics Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +*/ +import QtQuick 2.9 +import QtQuick.Controls 1.4 +import QtQuick.Controls 2.2 +import QtQuick.Controls.Material 2.1 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Styles 1.4 +import "qrc:/ComponentInspector" + +Rectangle { + id: systemInfoComponent + height: header.height + content.height + width: componentInspector.width + color: index % 2 == 0 ? lightGrey : darkGrey + + // Left indentation + property int indentation: 10 + + // Horizontal margins + property int margin: 5 + + // Light text color according to theme + property string propertyColor: Material.theme == Material.Light ? "#444444" : "#bbbbbb" + + // Dark text color according to theme + property string valueColor: Material.theme == Material.Light ? "black" : "white" + + Column { + anchors.fill: parent + + ExpandingTypeHeader { + id: header + customToolTip: "Information about system plugins attached to this entity" + } + + // Content + Rectangle { + id: content + property bool show: false + width: parent.width + height: show ? column.height : 0 + clip: true + color: "transparent" + + Behavior on height { + NumberAnimation { + duration: 200; + easing.type: Easing.InOutQuad + } + } + + ListView { + id: column + height: contentHeight + model: componentData + width: content.width + spacing: 5 + + delegate: Column { + width: content.width + height: pluginHeader.height + pluginContent.height + + Rectangle { + id: pluginHeader + width: content.width + color: "transparent" + height: 20 + RowLayout { + anchors.fill: parent + Item { + width: margin * 2 + indentation + } + Image { + id: icon + sourceSize.height: indentation + sourceSize.width: indentation + fillMode: Image.Pad + Layout.alignment : Qt.AlignVCenter + source: pluginContent.show ? + "qrc:/Gazebo/images/minus.png" : "qrc:/Gazebo/images/plus.png" + } + Text { + height: parent.height + text: modelData[0].substring(modelData[0].lastIndexOf('::') + 2) + Layout.alignment : Qt.AlignVCenter + leftPadding: margin + color: propertyColor + font.pointSize: 12 + } + Item { + Layout.fillWidth: true + } + } + MouseArea { + anchors.fill: pluginHeader + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: { + pluginContent.show = !pluginContent.show + } + onEntered: { + pluginHeader.color = highlightColor + } + onExited: { + pluginHeader.color = "transparent" + } + } + } + GridLayout { + id: pluginContent + property bool show: false + width: parent.width + height: show ? 20 * 4 + innerXmlText.height : 0 + clip: true + columns: 4 + + Behavior on height { + NumberAnimation { + duration: 200; + easing.type: Easing.InOutQuad + } + } + Item { + Layout.rowSpan: 4 + width: margin * 4 + indentation * 2 + } + Text { + height: 20 + text: "Name" + color: propertyColor + font.pointSize: 12 + } + Text { + height: 20 + text: modelData[0] + color: valueColor + font.pointSize: 12 + clip: true + elide: Text.ElideLeft + horizontalAlignment: Text.AlignRight + Layout.fillWidth: true + + ToolTip { + visible: maName.containsMouse + delay: Qt.styleHints.mousePressAndHoldInterval + text: modelData[0] + enter: null + exit: null + } + MouseArea { + id: maName + anchors.fill: parent + hoverEnabled: true + } + } + Item { + Layout.rowSpan: 4 + width: margin + } + Text { + height: 20 + text: "Filename" + color: propertyColor + font.pointSize: 12 + } + Text { + height: 20 + text: modelData[1] + color: valueColor + font.pointSize: 12 + clip: true + elide: Text.ElideLeft + horizontalAlignment: Text.AlignRight + Layout.fillWidth: true + + ToolTip { + visible: maFilename.containsMouse + delay: Qt.styleHints.mousePressAndHoldInterval + text: modelData[1] + enter: null + exit: null + } + MouseArea { + id: maFilename + anchors.fill: parent + hoverEnabled: true + } + } + Text { + height: 20 + text: "Inner XML" + color: propertyColor + font.pointSize: 12 + Layout.columnSpan: 2 + } + Rectangle { + id: innerXmlRectangle + Layout.columnSpan: 2 + Layout.fillWidth: true + height: innerXmlText.paintedHeight + color: Material.background + TextEdit { + id: innerXmlText + width: parent.width + text: modelData[2].length == 0 ? "N/A" : modelData[2].trim() + color: valueColor + textFormat: TextEdit.PlainText + font.pointSize: 10 + clip: true + readOnly: true + wrapMode: Text.WordWrap + selectByMouse: true + } + } + } + } + } + } + } +} diff --git a/src/gui/plugins/component_inspector/TypeHeader.qml b/src/gui/plugins/component_inspector/TypeHeader.qml index 5cb0b39e2b..d8dd9221d5 100644 --- a/src/gui/plugins/component_inspector/TypeHeader.qml +++ b/src/gui/plugins/component_inspector/TypeHeader.qml @@ -27,6 +27,10 @@ Rectangle { width: headerText.width color: "transparent" + // Custom tooltip text. If not set, the component's type name and ID will be + // shown. + property string customToolTip: "" + function tooltipText(_model) { if (model === null) return "Unknown component" @@ -50,7 +54,7 @@ Rectangle { ToolTip { visible: ma.containsMouse delay: tooltipDelay - text: tooltipText(model) + text: customToolTip.length > 0 ? customToolTip : typeHeader.tooltipText(componentData) y: typeHeader.y - 30 enter: null exit: null diff --git a/src/gui/plugins/component_inspector_editor/Pose3d.qml b/src/gui/plugins/component_inspector_editor/Pose3d.qml index 8b2acf20ac..40e3bf9ba9 100644 --- a/src/gui/plugins/component_inspector_editor/Pose3d.qml +++ b/src/gui/plugins/component_inspector_editor/Pose3d.qml @@ -315,7 +315,7 @@ Rectangle { id: zSpin Layout.fillWidth: true height: 40 - numberValue: model.data[2] + numberValue: model.data[2] minValue: -Number.MAX_VALUE maxValue: Number.MAX_VALUE stepValue: 0.1