Skip to content

Instantly share code, notes, and snippets.

@zflat
Last active April 9, 2022 18:25
Show Gist options
  • Save zflat/138385fb3934fd75ddfcfe42f802e2cf to your computer and use it in GitHub Desktop.
Save zflat/138385fb3934fd75ddfcfe42f802e2cf to your computer and use it in GitHub Desktop.
Embedding ign-gui into QML application

Minimal example of embedding ign-gui into a QML application.

Host:

git clone https://gist.github.com/138385fb3934fd75ddfcfe42f802e2cf.git downstream
git clone https://github.com/zflat/ign-gui.git -b ign5-application_base
git clone https://github.com/ignitionrobotics/ign-rviz.git
docker build -t my_ign:latest ./downstream
xhost + local:root
docker run -it --rm --name my_ign --privileged \
       -v my_ign_ws:/opt/ws -v $(pwd):/src \
       -v /tmp/.X11-unix:/tmp/.X11-unix -e DISPLAY=$DISPLAY \
       my_ign:latest /bin/bash
# when finished testing
xhost - local:root

Guest:

source /opt/ign/install/setup.bash
colcon build
source install/setup.bash
ros2 run downstream example
cmake_minimum_required(VERSION 3.5)
project(downstream)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++17
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 17)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# rosdep install --from-paths src -i -y
find_package(ament_cmake REQUIRED)
find_package(ament_index_cpp REQUIRED)
find_package(backward_ros REQUIRED)
find_package(Qt5 COMPONENTS Core Gui Qml Quick Test REQUIRED)
# Ignition libraries
find_package(ign_rviz REQUIRED)
find_package(ign_rviz_common REQUIRED)
find_package(ign_rviz_plugins REQUIRED)
find_package(ignition-math6 REQUIRED)
find_package(ignition-gui5 REQUIRED)
find_package(ignition-common4 REQUIRED)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
qt5_add_resources(RESOURCES_EXAMPLE example.qrc)
add_executable(example
${CMAKE_CURRENT_SOURCE_DIR}/example_main.cc
${CMAKE_CURRENT_SOURCE_DIR}/my_application.cc
${RESOURCES_EXAMPLE})
ament_target_dependencies(example
PUBLIC
ament_index_cpp
ign_rviz
ign_rviz_common
ign_rviz_plugins
ignition-math6
ignition-gui5)
target_include_directories(example
PRIVATE
${IGNITION-COMMON_INCLUDE_DIRS}
${IGNITION-GUI_INCLUDE_DIRS}
${CMAKE_CURRENT_SOURCE_DIR})
target_link_libraries(example
PUBLIC
Qt5::Core
Qt5::Gui
Qt5::Quick)
install(TARGETS example
RUNTIME DESTINATION "lib/${PROJECT_NAME}")
ament_package()
ARG ROS_DISTRO="rolling"
FROM ros:${ROS_DISTRO}-ros-base
# Add ign apt repositories
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
lsb-release gnupg wget \
&& rm -rf /var/lib/apt/lists/*
RUN echo "deb http://packages.osrfoundation.org/gazebo/ubuntu-stable `lsb_release -cs` main" > /etc/apt/sources.list.d/gazebo-stable.list
RUN wget http://packages.osrfoundation.org/gazebo.key -O - | sudo apt-key add -
# Get ign source and install dependnecies
WORKDIR /opt/ign/src
RUN git clone https://github.com/ignitionrobotics/ign-gui.git -b ign-gui5
RUN git clone https://github.com/ignitionrobotics/ign-rviz.git
WORKDIR /opt/ign/src/ign-gui
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
$(sort -u $(find . -iname 'packages.apt') | tr '\n' ' ') \
&& rm -rf /var/lib/apt/lists/*
# Build the ign packages
WORKDIR /opt/ign
RUN . /opt/ros/$ROS_DISTRO/setup.sh && colcon build
# Install dependencies for the example
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
ros-${ROS_DISTRO}-backward-ros \
&& rm -rf /var/lib/apt/lists/*
# Set up a workspace source directory that links to a location that
# will be created with a bind mount
VOLUME my_ign_ws:/opt/ws
WORKDIR /opt/ws
RUN ln -s /src /opt/ws/src
import QtQuick 2.9
import QtQuick.Window 2.2
// ignition-gui components
import "qrc:/qml"
Item {
Window {
id: "example_window"
width: 600
height: 400
Text {
anchors.bottom: parent.bottom
text: "Example text"
}
IgnSplit {
id: "ignContainer"
objectName: "ignContainer"
anchors.fill: parent
anchors.bottomMargin: 20
onChildrenChanged: {
console.log(JSON.stringify(__items))
}
}
}
Component.onCompleted: function onCompleteHandler () {
example_window.show()
}
}
<?xml version="1.0"?>
<!DOCTYPE RCC>
<RCC>
<qresource prefix="/">
<file>example.qml</file>
</qresource>
</RCC>
#include <chrono>
#include <thread>
#include <QGuiApplication>
#include <QProcessEnvironment>
#include <QQmlApplicationEngine>
#include <QtQuick/QQuickView>
#include <ament_index_cpp/get_package_prefix.hpp>
#include <ament_index_cpp/get_package_share_directory.hpp>
#include <ignition/gui/Application.hh>
#include <ignition/gui/MainWindow.hh>
#include <ignition/gui/qt.h>
#include "my_application.hh"
int main(int argc, char **argv)
{
MyApplication app(argc, argv, ignition::gui::WindowType::kMainWindow);
// QML application engine with downstream customizations
auto engine = QQmlApplicationEngine(/*&app*/);
QObject::connect(
&engine, &QQmlApplicationEngine::objectCreated, [](QObject *object, const QUrl &url) {
if (object == nullptr)
{
qCritical() << "Failed to load QML.";
exit(1);
}
});
engine.addImportPath("qrc:///");
for (const auto &path : QProcessEnvironment::systemEnvironment().value("LD_LIBRARY_PATH").split(":"))
{
engine.addPluginPath(path);
}
engine.load(QUrl("qrc:///example.qml"));
// Dependency injection of the QML engine to be compatible with downstream applications
app.SetEngine(&engine);
auto quickWindow = qobject_cast<QQuickWindow *>(app.Engine()->rootObjects().value(0));
auto rootItem = qobject_cast<QQuickItem *>(app.Engine()->rootObjects().first());
auto container = rootItem->findChild<QQuickItem *>("ignContainer");
// Dependency injection of the plugin container
app.SetPluginItemContainer(container);
// Same config as used in rviz/application.cpp
auto rviz_share_directory = ament_index_cpp::get_package_share_directory("ign_rviz");
app.LoadConfig(rviz_share_directory + "/config/rviz.config");
// Same plugins as rviz/application.cpp
// auto plugin_directory = ament_index_cpp::get_package_prefix("ign_rviz_plugins");
// app.AddPluginPath(plugin_directory + "/lib");
auto code = QGuiApplication::exec();
auto plugin_names = app.PluginList();
for(auto const name_and_path: plugin_names)
{
auto name = name_and_path.first;
app.RemovePlugin(name);
qInfo() << "Removed plugin " << name.c_str();
}
std::this_thread::sleep_for(std::chrono::milliseconds(300));
return code;
}
#include "my_application.hh"
#include <tinyxml2.h>
#include <QDebug>
#include <ignition/common/Console.hh>
#include <ignition/common/SignalHandler.hh>
#include <ignition/common/SystemPaths.hh>
#include <ignition/common/Util.hh>
#include <ignition/plugin/Loader.hh>
#include <queue>
#include "ignition/gui/Application.hh"
#include "ignition/gui/Dialog.hh"
#include "ignition/gui/MainWindow.hh"
#include "ignition/gui/Plugin.hh"
#include "ignition/gui/config.hh"
class MyApplicationPrivate
{
/// \brief QML engine
public:
QQmlApplicationEngine *engine{nullptr};
public:
QQuickItem *plugin_container_item{nullptr};
/// \brief Pointer to main window
public:
ignition::gui::MainWindow *mainWin{nullptr};
/// \brief Vector of pointers to dialogs
public:
std::vector<ignition::gui::Dialog *> dialogs;
/// \brief Queue of plugins which should be added to the window
public:
std::queue<std::shared_ptr<ignition::gui::Plugin>> pluginsToAdd;
/// \brief Vector of pointers to plugins already added, we hang on to
/// these until it is ok to unload the plugin's shared library.
public:
std::vector<std::shared_ptr<ignition::gui::Plugin>> pluginsAdded;
/// \brief Environment variable which holds paths to look for plugins
public:
std::string pluginPathEnv = "IGN_GUI_PLUGIN_PATH";
/// \brief Vector of paths to look for plugins
public:
std::vector<std::string> pluginPaths;
/// \brief Holds a configuration which may be applied to mainWin once it
/// is created by calling applyConfig(). It's no longer needed and
/// should not be used after that.
public:
ignition::gui::WindowConfig windowConfig;
/// \brief The path containing the default configuration file.
public:
std::string defaultConfigPath;
public:
ignition::common::SignalHandler signalHandler;
/// \brief QT message handler that pipes qt messages into our console
/// system.
public:
static void MessageHandler(QtMsgType _type, const QMessageLogContext &_context, const QString &_msg);
};
/////////////////////////////////////////////////
void MyApplication::SetEngine(QQmlApplicationEngine *engine)
{
this->dataPtr->engine = engine;
}
QObject *MyApplication::MainEventReceiver() const
{
return this->dataPtr->plugin_container_item;
}
void MyApplication::SetPluginItemContainer(QQuickItem *item)
{
this->dataPtr->plugin_container_item = item;
}
MyApplication::MyApplication(int &_argc, char **_argv, const ignition::gui::WindowType _type)
: ApplicationBase(_argc, _argv, _type), dataPtr(new MyApplicationPrivate)
{
igndbg << "Initializing application." << std::endl;
// Configure console
ignition::common::Console::SetPrefix("[GUI] ");
// QML engine is set by calling SetEngine
// this->dataPtr->engine = new QQmlApplicationEngine();
// Install signal handler for graceful shutdown
this->dataPtr->signalHandler.AddCallback([](int) // NOLINT(readability/casting)
{ ignition::gui::App()->quit(); });
// Handle qt console messages
qInstallMessageHandler(this->dataPtr->MessageHandler);
// Default config path
std::string home;
ignition::common::env(IGN_HOMEDIR, home);
this->dataPtr->defaultConfigPath = ignition::common::joinPaths(home, ".ignition", "gui", "default.config");
// If it's a main window, initialize it
if (_type == ignition::gui::WindowType::kMainWindow)
{
if (!this->InitializeMainWindow())
ignerr << "Failed to initialize main window." << std::endl;
}
else if (_type == ignition::gui::WindowType::kDialog)
{
// Do nothing, dialogs are initialized as plugins are loaded
}
else
{
ignerr << "Unknown WindowType [" << static_cast<int>(_type) << "]\n";
}
}
/////////////////////////////////////////////////
MyApplication::~MyApplication()
{
igndbg << "Terminating application." << std::endl;
if (this->dataPtr->mainWin && this->dataPtr->mainWin->QuickWindow())
{
// Detach object from main window and leave libraries for ign-common
auto plugins = this->dataPtr->mainWin->findChildren<ignition::gui::Plugin *>();
for (auto plugin : plugins)
{
auto pluginName = plugin->CardItem()->objectName();
this->RemovePlugin(pluginName.toStdString());
}
if (this->dataPtr->mainWin->QuickWindow()->isVisible())
this->dataPtr->mainWin->QuickWindow()->close();
delete this->dataPtr->mainWin;
this->dataPtr->mainWin = nullptr;
}
for (auto dialog : this->dataPtr->dialogs)
{
if (dialog->QuickWindow())
dialog->QuickWindow()->close();
dialog->deleteLater();
}
this->dataPtr->dialogs.clear();
if (this->dataPtr->engine)
{
this->dataPtr->engine->deleteLater();
}
std::queue<std::shared_ptr<ignition::gui::Plugin>> empty;
std::swap(this->dataPtr->pluginsToAdd, empty);
this->dataPtr->pluginsAdded.clear();
this->dataPtr->pluginPaths.clear();
this->dataPtr->pluginPathEnv = "IGN_GUI_PLUGIN_PATH";
}
/////////////////////////////////////////////////
QQmlApplicationEngine *MyApplication::Engine() const
{
return this->dataPtr->engine;
}
/////////////////////////////////////////////////
bool MyApplication::RemovePlugin(const std::string &_pluginName)
{
auto plugin = this->PluginByName(_pluginName);
if (nullptr == plugin)
return false;
auto cardItem = plugin->CardItem();
if (nullptr == cardItem)
return false;
// Remove on QML
cardItem->deleteLater();
// Remove split on QML
auto bgItem = this->dataPtr->mainWin->QuickWindow()->findChild<QQuickItem *>("background");
if (bgItem)
{
QMetaObject::invokeMethod(bgItem, "removeSplitItem", Q_ARG(QVariant, cardItem->parentItem()->objectName()));
}
// Unload shared library
this->RemovePlugin(plugin);
return true;
}
/////////////////////////////////////////////////
bool MyApplication::LoadConfig(const std::string &_config)
{
if (_config.empty())
{
ignerr << "Missing config file" << std::endl;
return false;
}
// Use tinyxml to read config
tinyxml2::XMLDocument doc;
auto success = !doc.LoadFile(_config.c_str());
if (!success)
{
// We do not show an error message if the default config path doesn't exist
// yet. It's expected behavior, it will be created the first time the user
// presses "Save configuration".
if (_config != this->DefaultConfigPath())
{
ignerr << "Failed to load file [" << _config << "]: XMLError" << std::endl;
}
return false;
}
qInfo() << "Loading config from " << _config.c_str();
// Clear all previous plugins
auto plugins = this->dataPtr->mainWin->findChildren<ignition::gui::Plugin *>();
for (auto plugin : plugins)
{
auto pluginName = plugin->CardItem()->objectName();
this->RemovePlugin(pluginName.toStdString());
}
if (this->dataPtr->pluginsAdded.size() > 0)
{
qCritical() << "The plugin list was not properly cleaned up.";
}
this->dataPtr->pluginsAdded.clear();
// Process each plugin
for (auto pluginElem = doc.FirstChildElement("plugin"); pluginElem != nullptr;
pluginElem = pluginElem->NextSiblingElement("plugin"))
{
auto filename = pluginElem->Attribute("filename");
this->LoadPlugin(filename, pluginElem);
}
// // Process window properties
// if (auto winElem = doc.FirstChildElement("window"))
// {
// igndbg << "Loading window config" << std::endl;
// tinyxml2::XMLPrinter printer;
// if (!winElem->Accept(&printer))
// {
// ignwarn << "There was an error parsing the <window> element" << std::endl;
// return false;
// }
// this->dataPtr->windowConfig.MergeFromXML(std::string(printer.CStr()));
// // Closing behavior.
// if (auto dialogOnExitElem = winElem->FirstChildElement("dialog_on_exit"))
// {
// bool showDialogOnExit{false};
// dialogOnExitElem->QueryBoolText(&showDialogOnExit);
// this->dataPtr->mainWin->SetShowDialogOnExit(showDialogOnExit);
// }
// }
this->ApplyConfig();
return true;
}
/////////////////////////////////////////////////
bool MyApplication::LoadDefaultConfig()
{
return this->LoadConfig(this->dataPtr->defaultConfigPath);
}
/////////////////////////////////////////////////
void MyApplication::SetDefaultConfigPath(const std::string &_path)
{
this->dataPtr->defaultConfigPath = _path;
}
/////////////////////////////////////////////////
std::string MyApplication::DefaultConfigPath()
{
return this->dataPtr->defaultConfigPath;
}
/////////////////////////////////////////////////
bool MyApplication::LoadPlugin(const std::string &_filename, const tinyxml2::XMLElement *_pluginElem)
{
qDebug() << "Loading plugin " << _filename.c_str();
ignition::common::SystemPaths systemPaths;
systemPaths.SetPluginPathEnv(this->dataPtr->pluginPathEnv);
for (const auto &path : this->dataPtr->pluginPaths)
systemPaths.AddPluginPaths(path);
// Add default folder and install folder
std::string home;
ignition::common::env(IGN_HOMEDIR, home);
systemPaths.AddPluginPaths(home + "/.ignition/gui/plugins:" + IGN_GUI_PLUGIN_INSTALL_DIR);
auto pathToLib = systemPaths.FindSharedLibrary(_filename);
if (pathToLib.empty())
{
ignerr << "Failed to load plugin [" << _filename << "] : couldn't find shared library." << std::endl;
qCritical() << "Failed to load plugin [1]";
return false;
}
// Load plugin
ignition::plugin::Loader pluginLoader;
auto pluginNames = pluginLoader.LoadLib(pathToLib);
if (pluginNames.empty())
{
ignerr << "Failed to load plugin [" << _filename << "] : couldn't load library on path [" << pathToLib << "]."
<< std::endl;
qCritical() << "Failed to load plugin [2]";
return false;
}
// Go over all plugin names and get the first one that implements the
// ignition::gui::Plugin interface
ignition::plugin::PluginPtr commonPlugin;
std::shared_ptr<ignition::gui::Plugin> plugin{nullptr};
for (auto pluginName : pluginNames)
{
commonPlugin = pluginLoader.Instantiate(pluginName);
if (!commonPlugin)
continue;
plugin = commonPlugin->QueryInterfaceSharedPtr<ignition::gui::Plugin>();
if (plugin)
break;
}
if (!commonPlugin)
{
qCritical() << "Failed to load plugin [3]";
ignerr << "Failed to load plugin [" << _filename << "] : couldn't instantiate plugin on path [" << pathToLib
<< "]. Tried plugin names: " << std::endl;
for (auto pluginName : pluginNames)
{
ignerr << " * " << pluginName << std::endl;
}
return false;
}
if (!plugin)
{
qCritical() << "Failed to load plugin [4]";
ignerr << "Failed to load plugin [" << _filename << "] : couldn't get [ignition::gui::Plugin] interface."
<< std::endl;
return false;
}
// Basic config in case there is none
if (!_pluginElem)
{
std::string pluginStr = "<plugin filename=\"" + _filename + "\"></plugin>";
tinyxml2::XMLDocument pluginDoc;
pluginDoc.Parse(pluginStr.c_str());
plugin->Load(pluginDoc.FirstChildElement("plugin"));
}
else
plugin->Load(_pluginElem);
// Store plugin in queue to be added to the window
// TODO(wrw): remove this datapointer stuff....
this->dataPtr->pluginsToAdd.push(plugin);
/// TODO(WRW): dependency injection for how to handle loaded plugin? Or simply extract to a protected method?
this->AddPluginsToWindow();
// // Add to window or dialog
// if (this->dataPtr->mainWin)
// this->AddPluginsToWindow();
// else
// this->InitializeDialogs();
this->PluginAdded(plugin->CardItem()->objectName());
qInfo() << "Finished loading plugin " << _filename.c_str();
ignmsg << "Loaded plugin [" << _filename << "] from path [" << pathToLib << "]" << std::endl;
return true;
}
/////////////////////////////////////////////////
std::shared_ptr<ignition::gui::Plugin> MyApplication::PluginByName(const std::string &_pluginName) const
{
for (auto &plugin : this->dataPtr->pluginsAdded)
{
auto cardItem = plugin->CardItem();
if (!cardItem)
continue;
if (cardItem->objectName().toStdString() != _pluginName)
continue;
return plugin;
}
return nullptr;
}
/////////////////////////////////////////////////
bool MyApplication::InitializeMainWindow()
{
igndbg << "Create main window" << std::endl;
// TODO(wrw): This is where the main window is loaded into the existing QML engine
// Subclass the MainWindow type so that it is just a QML component
// Possibly extract it out into a QML type
// this->dataPtr->mainWin = new ignition::gui::MainWindow();
// if (!this->dataPtr->mainWin->QuickWindow())
// return false;
// this->dataPtr->mainWin->setParent(this);
return true;
}
/////////////////////////////////////////////////
bool MyApplication::ApplyConfig()
{
qDebug() << "Applying config";
if (!this->dataPtr->mainWin)
return false;
return this->dataPtr->mainWin->ApplyConfig(this->dataPtr->windowConfig);
}
/////////////////////////////////////////////////
bool MyApplication::AddPluginsToWindow()
{
if (!this->dataPtr->plugin_container_item)
{
qCritical() << "No plugin container Item";
return false;
}
// Get main window background item
// TODO(WRW): extract this to a protected method
auto bgItem = this->dataPtr->plugin_container_item;
if (!this->dataPtr->pluginsToAdd.empty() && !bgItem)
{
qCritical() << "Null background QQuickItem!";
return false;
}
// Create a widget for each plugin
while (!this->dataPtr->pluginsToAdd.empty())
{
auto plugin = this->dataPtr->pluginsToAdd.front();
this->dataPtr->pluginsAdded.push_back(plugin);
this->dataPtr->pluginsToAdd.pop();
if (plugin->DeleteLaterRequested())
{
this->RemovePlugin(plugin);
continue;
}
auto cardItem = plugin->CardItem();
if (!cardItem)
continue;
// Add split item
QVariant splitName;
QMetaObject::invokeMethod(bgItem, "addSplitItem", Q_RETURN_ARG(QVariant, splitName));
auto splitItem = bgItem->findChild<QQuickItem *>(splitName.toString());
if (!splitItem)
{
qCritical() << "Failed to create split";
ignerr << "Internal error: failed to create split [" << splitName.toString().toStdString() << "]"
<< std::endl;
return false;
}
// Add card to main window
cardItem->setParentItem(splitItem);
cardItem->setParent(this->dataPtr->engine);
plugin->setParent(this);
// Apply anchors and state changes now that it's attached to window
plugin->PostParentChanges();
// Signals
// TODO(wrw): can this be normal QObject::connect syntax?
QObject::connect(cardItem, SIGNAL(close()), this, SLOT(OnPluginClose()));
qInfo() << "Added plugin to main window " << plugin->Title().c_str();
}
// this->dataPtr->mainWin->SetPluginCount(this->dataPtr->pluginsAdded.size());
return true;
}
/////////////////////////////////////////////////
bool MyApplication::InitializeDialogs()
{
igndbg << "Initialize dialogs" << std::endl;
while (!this->dataPtr->pluginsToAdd.empty())
{
auto plugin = this->dataPtr->pluginsToAdd.front();
this->dataPtr->pluginsToAdd.pop();
// Create card
auto cardItem = plugin->CardItem();
if (!cardItem)
continue;
// Create dialog
auto dialog = new ignition::gui::Dialog();
if (!dialog || !dialog->QuickWindow())
continue;
this->dataPtr->dialogs.push_back(dialog);
// Configure card
cardItem->setProperty("standalone", true);
// Configure dialog
auto cardWidth = cardItem->property("width").toInt();
auto cardHeight = cardItem->property("height").toInt();
dialog->QuickWindow()->setProperty("minimumWidth", cardWidth);
dialog->QuickWindow()->setProperty("minimumHeight", cardHeight);
cardItem->setParentItem(dialog->RootItem());
// Signals
this->dataPtr->mainWin->connect(cardItem, SIGNAL(close()), this, SLOT(OnPluginClose()));
this->dataPtr->pluginsAdded.push_back(plugin);
auto title = QString::fromStdString(plugin->Title());
igndbg << "Initialized dialog [" << title.toStdString() << "]" << std::endl;
}
if (this->dataPtr->pluginsAdded.empty())
return false;
return true;
}
/////////////////////////////////////////////////
void MyApplication::SetPluginPathEnv(const std::string &_env)
{
this->dataPtr->pluginPathEnv = _env;
}
/////////////////////////////////////////////////
void MyApplication::AddPluginPath(const std::string &_path)
{
this->dataPtr->pluginPaths.push_back(_path);
}
/////////////////////////////////////////////////
std::vector<std::pair<std::string, std::vector<std::string>>> MyApplication::PluginList()
{
// 1. Paths from env variable
auto paths = ignition::common::SystemPaths::PathsFromEnv(this->dataPtr->pluginPathEnv);
// 2. Paths added by calling addPluginPath
for (auto const &path : this->dataPtr->pluginPaths)
paths.push_back(path);
// 3. ~/.ignition/gui/plugins
std::string home;
ignition::common::env(IGN_HOMEDIR, home);
paths.push_back(home + "/.ignition/gui/plugins");
// 4. Install path
paths.push_back(IGN_GUI_PLUGIN_INSTALL_DIR);
// Populate map
std::vector<std::pair<std::string, std::vector<std::string>>> plugins;
for (auto const &path : paths)
{
std::vector<std::string> ps;
ignition::common::DirIter endIter;
for (ignition::common::DirIter dirIter(path); dirIter != endIter; ++dirIter)
{
auto plugin = ignition::common::basename(*dirIter);
// All we verify is that the file starts with "lib", any further
// checks would require loading the plugin.
if (plugin.find("lib") == 0)
ps.push_back(plugin);
}
plugins.push_back(std::make_pair(path, ps));
}
return plugins;
}
/////////////////////////////////////////////////
void MyApplication::OnPluginClose()
{
auto pluginName = this->sender()->objectName();
this->RemovePlugin(pluginName.toStdString());
}
/////////////////////////////////////////////////
void MyApplication::RemovePlugin(std::shared_ptr<ignition::gui::Plugin> _plugin)
{
this->dataPtr->pluginsAdded.erase(
std::remove(this->dataPtr->pluginsAdded.begin(), this->dataPtr->pluginsAdded.end(), _plugin),
this->dataPtr->pluginsAdded.end());
// TODO(wrw): move this to a protected method tht is implementation specific
auto pluginCount = this->dataPtr->pluginsAdded.size();
// Update main window count
if (this->dataPtr->mainWin)
{
this->dataPtr->mainWin->SetPluginCount(pluginCount);
}
// Or close app if it's the last dialog
else if (pluginCount == 0)
{
this->exit();
}
}
//////////////////////////////////////////////////
void MyApplicationPrivate::MessageHandler(QtMsgType _type, const QMessageLogContext &_context, const QString &_msg)
{
std::string msg = "[QT] " + _msg.toStdString();
if (_context.function)
msg += std::string("(") + _context.function + ")";
switch (_type)
{
case QtDebugMsg:
igndbg << msg << std::endl;
break;
case QtInfoMsg:
ignmsg << msg << std::endl;
break;
case QtWarningMsg:
ignwarn << msg << std::endl;
break;
case QtFatalMsg:
case QtCriticalMsg:
ignerr << msg << std::endl;
break;
default:
ignwarn << "Unknown QT Message type[" << _type << "]: " << msg << std::endl;
break;
}
}
#ifndef OCU_RVIZ_APPLICATION
#include <ignition/gui/qt.h>
#include <ignition/common/Console.hh>
#include <ignition/gui/Application.hh>
#include <ignition/gui/MainWindow.hh>
// #include "ignition/rviz/common/frame_manager.hpp"
class MyApplicationPrivate;
class MyApplication : public ignition::gui::ApplicationBase
{
Q_OBJECT
public:
MyApplication(int &_argc, char **_argv,
const ignition::gui::WindowType _type = ignition::gui::WindowType::kMainWindow);
/// \brief Destructor
public:
virtual ~MyApplication();
/// \brief Set the QML engine
public:
virtual void SetEngine(QQmlApplicationEngine *engine);
/// \brief Set the QML engine
public:
virtual void SetPluginItemContainer(QQuickItem *item);
public:
QObject *MainEventReceiver() const;
/// \brief Get the QML engine
/// \return Pointer to QML engine
public:
virtual QQmlApplicationEngine *Engine() const override;
/// \brief Load a plugin from a file name. The plugin file must be in the
/// path.
/// If a window has been initialized, the plugin is added to the window.
/// Otherwise, the plugin is stored and can be later added to a window or
/// dialog.
/// \param[in] _filename Plugin filename.
/// \param[in] _pluginElem Element containing plugin configuration
/// \return True if successful
/// \sa LoadConfig
/// \sa AddPluginsToWindow
public:
virtual bool LoadPlugin(const std::string &_filename, const tinyxml2::XMLElement *_pluginElem = nullptr) override;
/// \brief Load a configuration file, which includes window configurations
/// and plugins. This function doesn't instantiate the plugins, it just
/// keeps them in memory and they can be applied later by either
/// instantiating a window or several dialogs.
/// \param[in] _path Full path to configuration file.
/// \return True if successful
/// \sa InitializeMainWindow
/// \sa InitializeDialogs
public:
virtual bool LoadConfig(const std::string &_path) override;
/// \brief Load the configuration from the default config file.
/// \return True if successful
/// \sa SetDefaultConfigPath
/// \sa DefaultConfigPath
/// \sa LoadConfig
public:
virtual bool LoadDefaultConfig() override;
/// \brief Specifies the location of the default configuration file.
/// This is the file that stores the user settings when pressing
/// "Save configuration".
/// \param[in] _path The default configuration full path including
/// filename.
/// \sa LoadDefaultConfig
/// \sa defaultConfigPath
public:
virtual void SetDefaultConfigPath(const std::string &_path) override;
/// \brief Get the location of the default configuration file.
/// \return The default configuration path.
/// \sa LoadDefaultConfig
/// \sa SetDefaultConfigPath
public:
virtual std::string DefaultConfigPath() override;
/// \brief Set the environment variable which defines the paths to
/// look for plugins.
/// \param[in] _env Name of environment variable.
public:
virtual void SetPluginPathEnv(const std::string &_env) override;
/// \brief Add an path to look for plugins.
/// \param[in] _path Full path.
public:
virtual void AddPluginPath(const std::string &_path) override;
/// \brief Get the list of available plugins, organized by path. The
/// paths are given in the following order:
///
/// 1. Paths given by the environment variable
/// 2. Paths added by calling addPluginPath
/// 3. Path ~/.ignition/gui/plugins
/// 4. The path where Ignition GUI plugins are installed
///
/// \return A vector of pairs, where each pair contains:
/// * A path
/// * A vector of plugins in that path
public:
virtual std::vector<std::pair<std::string, std::vector<std::string>>> PluginList() override;
/// \brief Remove plugin by name. The plugin is removed from the
/// application and its shared library unloaded if this was its last
/// instance.
/// \param[in] _pluginName Plugn instance's unique name. This is the
/// plugin card's object name.
/// \return True if successful
public:
virtual bool RemovePlugin(const std::string &_pluginName) override;
/// \brief Get a plugin by its unique name.
/// \param[in] _pluginName Plugn instance's unique name. This is the
/// plugin card's object name.
/// \return Pointer to plugin object, null if not found.
public:
virtual std::shared_ptr<ignition::gui::Plugin> PluginByName(const std::string &_pluginName) const override;
// /// \brief Notify that a plugin has been added.
// /// \param[in] _objectName Plugin's object name.
// signals:
// void PluginAdded(const QString &_objectName);
// /// \brief Callback when user requests to close a plugin
public slots:
virtual void OnPluginClose() override;
/// \brief Create a main window, populate with previously loaded plugins
/// and apply previously loaded configuration.
/// An empty window will be created if no plugins have been loaded.
/// \return True if successful
/// \sa LoadConfig
/// \sa LoadPlugin
private:
bool InitializeMainWindow();
/// \brief Create individual dialogs for all previously loaded plugins.
/// This has no effect if no plugins have been loaded.
/// \return True if successful
/// \sa LoadConfig
/// \sa LoadPlugin
private:
bool InitializeDialogs();
/// \brief Remove plugin by pointer.
/// \param[in] _plugin Shared pointer to plugin
private:
void RemovePlugin(std::shared_ptr<ignition::gui::Plugin> _plugin);
/// \brief Add previously loaded plugins to the main window.
/// \return True if successful. Will fail if the window hasn't been
/// created yet.
/// \sa LoadPlugin
private:
bool AddPluginsToWindow();
/// \brief Apply previously loaded config to the main window.
/// \return True if successful, will fail if there's no main window
/// initialized.
private:
bool ApplyConfig();
/// \internal
/// \brief Private data pointer
private:
std::unique_ptr<MyApplicationPrivate> dataPtr;
};
#endif
<?xml version='1.0' encoding='utf-8'?>
<package format="2">
<name>downstream</name>
<version>0.0.0</version>
<description>example</description>
<maintainer email="[email protected]"></maintainer>
<license>MIT</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<depend>backward_ros</depend>
<depend>ignition-gui5</depend>
<depend>ign_rviz</depend>
<depend>libqt5-core</depend>
<depend>libqt5-qml</depend>
<depend>libqt5-quick</depend>
<depend>qml-module-qtquick-controls2</depend>
<depend>qml-module-qtquick-controls</depend>
<depend>qml-module-qtquick-window2</depend>
<depend>qml-module-qtquick2</depend>
<depend>qtbase5-dev</depend>
<depend>qtdeclarative5-dev</depend>
<test_depend>opengl</test_depend>
<test_depend>xvfb</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment