|
#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; |
|
} |
|
} |