diff --git a/CMakeLists.txt b/CMakeLists.txt index 6227e341..13d2f5a4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,9 +32,23 @@ option(QT_NODES_FORCE_TEST_COLOR "Force colorized unit test output" OFF) enable_testing() if(QT_NODES_DEVELOPER_DEFAULTS) - set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") - set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") - set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") + # Set Output Path + include(GNUInstallDirs) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_LIBDIR}) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/${CMAKE_INSTALL_BINDIR}) + + if(NOT DEFINED CMAKE_CONFIGURATION_TYPES) + set(CMAKE_CONFIGURATION_TYPES ${CMAKE_BUILD_TYPE}) + endif() + + foreach(OUTPUT_TYPES ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${OUTPUT_TYPES} OUTPUT_CONFIG) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY}/${OUTPUT_TYPES}) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/${OUTPUT_TYPES}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${OUTPUT_TYPES}) + message(STATUS "CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} : ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUT_CONFIG}}") + endforeach(OUTPUT_TYPES CMAKE_CONFIGURATION_TYPES) endif() if(BUILD_DEBUG_POSTFIX_D) @@ -89,6 +103,7 @@ set(CPP_SOURCE_FILES src/StyleCollection.cpp src/UndoCommands.cpp src/locateNode.cpp + src/PluginsManager.cpp ) set(HPP_HEADER_FILES @@ -122,6 +137,8 @@ set(HPP_HEADER_FILES include/QtNodes/internal/Serializable.hpp include/QtNodes/internal/Style.hpp include/QtNodes/internal/StyleCollection.hpp + include/QtNodes/internal/PluginInterface.hpp + include/QtNodes/internal/PluginsManager.hpp src/ConnectionPainter.hpp src/DefaultHorizontalNodeGeometry.hpp src/DefaultVerticalNodeGeometry.hpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 49494da2..067bf951 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -16,3 +16,7 @@ add_subdirectory(dynamic_ports) add_subdirectory(lock_nodes_and_connections) +add_subdirectory(plugin_text) + +add_subdirectory(plugins_load) + diff --git a/examples/plugin_text/CMakeLists.txt b/examples/plugin_text/CMakeLists.txt new file mode 100644 index 00000000..78274700 --- /dev/null +++ b/examples/plugin_text/CMakeLists.txt @@ -0,0 +1,18 @@ +file(GLOB_RECURSE CPPS ./*.cpp) +file(GLOB_RECURSE HPPS ./*.hpp) + +foreach(OUTPUT_TYPES ${CMAKE_CONFIGURATION_TYPES}) + string(TOUPPER ${OUTPUT_TYPES} OUTPUT_CONFIG) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY_${OUTPUT_CONFIG}}/plugins) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} ${CMAKE_LIBRARY_OUTPUT_DIRECTORY_${OUTPUT_CONFIG}}/plugins) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUT_CONFIG}}/plugins) + # message(STATUS "CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUT_CONFIG} : ${CMAKE_RUNTIME_OUTPUT_DIRECTORY_${OUTPUT_CONFIG}}") +endforeach(OUTPUT_TYPES CMAKE_CONFIGURATION_TYPES) + +add_library(plugin_text SHARED ${CPPS} ${HPPS}) + +target_link_libraries(plugin_text QtNodes) + +target_compile_definitions(plugin_text PUBLIC NODE_EDITOR_SHARED) + +set_target_properties(plugin_text PROPERTIES SUFFIX ".node") diff --git a/examples/plugin_text/PluginDefinition.cpp b/examples/plugin_text/PluginDefinition.cpp new file mode 100644 index 00000000..a88dc118 --- /dev/null +++ b/examples/plugin_text/PluginDefinition.cpp @@ -0,0 +1,22 @@ +#include "PluginDefinition.hpp" + +#include "TextModel.hpp" + +Plugin *Plugin::_this_plugin = nullptr; + +Plugin::Plugin() +{ + _this_plugin = this; +} + +Plugin::~Plugin() +{ + // TODO: Unregister all models here +} + +void Plugin::registerDataModels(std::shared_ptr ®) +{ + assert(reg); + + reg->registerModel(); +} diff --git a/examples/plugin_text/PluginDefinition.hpp b/examples/plugin_text/PluginDefinition.hpp new file mode 100644 index 00000000..8af43906 --- /dev/null +++ b/examples/plugin_text/PluginDefinition.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + +// This needs to be the same as the name of your project file ${PROJECT_NAME} +#ifdef plugin_text_EXPORTS +#define DLL_EXPORT Q_DECL_EXPORT +#else +#define DLL_EXPORT Q_DECL_IMPORT +#endif + +#define PLUGIN_NAME "Text" + +class DLL_EXPORT Plugin + : public QObject + , public QtNodes::PluginInterface +{ + Q_OBJECT + Q_INTERFACES(QtNodes::PluginInterface) + Q_PLUGIN_METADATA(IID PLUGIN_NAME) + +public: + Plugin(); + ~Plugin(); + + QString name() const override { return PLUGIN_NAME; }; + + void registerDataModels(std::shared_ptr ®) override; + +private: + static Plugin *_this_plugin; +}; \ No newline at end of file diff --git a/examples/plugin_text/TextData.hpp b/examples/plugin_text/TextData.hpp new file mode 100644 index 00000000..b07fc90c --- /dev/null +++ b/examples/plugin_text/TextData.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +using QtNodes::NodeData; +using QtNodes::NodeDataType; + +/// The class can potentially incapsulate any user data which +/// need to be transferred within the Node Editor graph +class TextData : public NodeData +{ +public: + TextData() {} + + TextData(QString const &text) + : _text(text) + {} + + NodeDataType type() const override { return NodeDataType{"text", "Text"}; } + + QString text() const { return _text; } + +private: + QString _text; +}; diff --git a/examples/plugin_text/TextModel.cpp b/examples/plugin_text/TextModel.cpp new file mode 100644 index 00000000..95b8580d --- /dev/null +++ b/examples/plugin_text/TextModel.cpp @@ -0,0 +1,69 @@ +#include "TextModel.hpp" + +#include + +TextModel::TextModel() +{ + // +} + +unsigned int TextModel::nPorts(PortType portType) const +{ + unsigned int result = 1; + + switch (portType) { + case PortType::In: + result = 1; + break; + + case PortType::Out: + result = 1; + + default: + break; + } + + return result; +} + +void TextModel::onTextEdited() +{ + Q_EMIT dataUpdated(0); +} + +NodeDataType TextModel::dataType(PortType, PortIndex) const +{ + return TextData().type(); +} + +std::shared_ptr TextModel::outData(PortIndex const portIndex) +{ + Q_UNUSED(portIndex); + return std::make_shared(_textEdit->toPlainText()); +} + +QWidget *TextModel::embeddedWidget() +{ + if (!_textEdit) { + _textEdit = new QTextEdit(); + + connect(_textEdit, &QTextEdit::textChanged, this, &TextModel::onTextEdited); + } + + return _textEdit; +} + +void TextModel::setInData(std::shared_ptr data, PortIndex const) +{ + auto textData = std::dynamic_pointer_cast(data); + + QString inputText; + + if (textData) { + inputText = textData->text(); + } else { + inputText = ""; + } + + _textEdit->setText(inputText); +} diff --git a/examples/plugin_text/TextModel.hpp b/examples/plugin_text/TextModel.hpp new file mode 100644 index 00000000..75ab7bf1 --- /dev/null +++ b/examples/plugin_text/TextModel.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +#include "TextData.hpp" + +#include + +#include + +using QtNodes::NodeData; +using QtNodes::NodeDelegateModel; +using QtNodes::PortIndex; +using QtNodes::PortType; + +class QTextEdit; + +/// The model dictates the number of inputs and outputs for the Node. +/// In this example it has no logic. +class TextModel : public NodeDelegateModel +{ + Q_OBJECT + +public: + TextModel(); + +public: + QString caption() const override { return QString("Text"); } + + bool captionVisible() const override { return true; } + + static QString Name() { return QString("Text"); } + + QString name() const override { return TextModel::Name(); } + +public: + unsigned int nPorts(PortType portType) const override; + + NodeDataType dataType(PortType portType, PortIndex portIndex) const override; + + std::shared_ptr outData(PortIndex const portIndex) override; + + void setInData(std::shared_ptr, PortIndex const) override; + + QWidget *embeddedWidget() override; + + bool resizable() const override { return true; } + +private Q_SLOTS: + + void onTextEdited(); + +private: + QTextEdit *_textEdit = nullptr; +}; diff --git a/examples/plugins_load/CMakeLists.txt b/examples/plugins_load/CMakeLists.txt new file mode 100644 index 00000000..7a7a7d3f --- /dev/null +++ b/examples/plugins_load/CMakeLists.txt @@ -0,0 +1,6 @@ +file(GLOB_RECURSE CPPS ./*.cpp) +file(GLOB_RECURSE HPPS ./*.hpp) + +add_executable(plugins_load ${CPPS} ${HPPS}) + +target_link_libraries(plugins_load QtNodes) diff --git a/examples/plugins_load/PluginsManagerDlg.cpp b/examples/plugins_load/PluginsManagerDlg.cpp new file mode 100644 index 00000000..5ad1bb63 --- /dev/null +++ b/examples/plugins_load/PluginsManagerDlg.cpp @@ -0,0 +1,151 @@ +#include "PluginsManagerDlg.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using QtNodes::NodeDelegateModelRegistry; +using QtNodes::PluginInterface; + +PluginsManagerDlg::PluginsManagerDlg(QWidget *parent) + : QDialog(parent) +{ + setMinimumSize(300, 250); + + _pluginsFolder.setPath(QDir::cleanPath(QCoreApplication::applicationDirPath() + + QDir::separator() + R"(./plugins)")); + + QGridLayout *layout = new QGridLayout(); + setLayout(layout); + + QTableView *pluginTable = new QTableView(); + pluginTable->setSelectionBehavior(QAbstractItemView::SelectRows); + pluginTable->setEditTriggers(QAbstractItemView::NoEditTriggers); + pluginTable->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + layout->addWidget(pluginTable, 0, 0, 1, 2); + + _model = new QStandardItemModel(pluginTable); + + _model->setColumnCount(2); + _model->setHeaderData(0, Qt::Horizontal, "Name"); + _model->setHeaderData(1, Qt::Horizontal, "Version"); + pluginTable->setModel(_model); + + loadPluginsFromFolder(); + + pluginTable->selectRow(0); + + // add button + QPushButton *addButton = new QPushButton("+"); + layout->addWidget(addButton, 1, 0); + connect(addButton, &QPushButton::clicked, this, [this]() { + // TODO: How to switch different suffixes according to different os + QString fileName + = QFileDialog::getOpenFileName(this, + tr("Load Plugin"), + QCoreApplication::applicationDirPath(), + tr("Node Files(*.node);;Data Files(*.data)")); + + if (!QFileInfo::exists(fileName)) + return; + + QFileInfo f(fileName); + + QFileInfo newFile( + QDir::cleanPath(_pluginsFolder.absolutePath() + QDir::separator() + f.fileName())); + QString const newPath = newFile.absoluteFilePath(); + + if (f.absoluteFilePath() == newPath) + return; + + // Copy to the plug-in directory + if (!QFile::copy(f.absoluteFilePath(), newPath)) + return; + + PluginsManager *pluginsManager = PluginsManager::instance(); + auto plugin = pluginsManager->loadPluginFromPath(newPath); + if (!plugin) { + QFile::remove(newPath); + return; + } + + QStandardItem *item = new QStandardItem(plugin->name()); + item->setData(newPath); + _model->appendRow(item); + + std::shared_ptr reg = pluginsManager->registry(); + plugin->registerDataModels(reg); + }); + + // delete button + QPushButton *deleteButton = new QPushButton("-", this); + layout->addWidget(deleteButton, 1, 1); + connect(deleteButton, &QPushButton::clicked, this, [this, pluginTable]() { + QItemSelectionModel *selectionModel = pluginTable->selectionModel(); + + int row = selectionModel->currentIndex().row(); + + while (selectionModel->selectedRows().count() > 0) { + auto rowIdx = selectionModel->selectedRows().first(); + row = std::min(row, rowIdx.row()); + + QStandardItem *item = _model->itemFromIndex(rowIdx); + + PluginsManager *pluginsManager = PluginsManager::instance(); + + // FIXME: Unload plugin successfully, but cannot delete the plugin file in windows + if (!pluginsManager->unloadPluginFromName(item->text()) + || !QFile::remove(item->data().toString())) { + selectionModel->select(rowIdx, QItemSelectionModel::Deselect); + continue; + } + + _model->removeRow(rowIdx.row()); + } + + pluginTable->selectRow(row); + }); +} + +PluginsManagerDlg::~PluginsManagerDlg() +{ + // +} + +void PluginsManagerDlg::openPluginsFolder() +{ + // QDesktopServices::openUrl(QUrl::fromLocalFile(_pluginsFolderPath)); + QDesktopServices::openUrl(QUrl(_pluginsFolder.absolutePath())); +} + +QString PluginsManagerDlg::pluginsFolderPath() const +{ + return _pluginsFolder.absolutePath(); +} + +void PluginsManagerDlg::loadPluginsFromFolder() +{ + PluginsManager *pluginsManager = PluginsManager::instance(); + std::shared_ptr registry = pluginsManager->registry(); + pluginsManager->loadPlugins(_pluginsFolder.absolutePath()); + + for (auto l : pluginsManager->loaders()) { + PluginInterface *plugin = qobject_cast(l.second->instance()); + if (!plugin) + continue; + + QStandardItem *item = new QStandardItem(plugin->name()); + item->setData(l.second->fileName()); + _model->appendRow(item); + + plugin->registerDataModels(registry); + } +} diff --git a/examples/plugins_load/PluginsManagerDlg.hpp b/examples/plugins_load/PluginsManagerDlg.hpp new file mode 100644 index 00000000..06ae95e2 --- /dev/null +++ b/examples/plugins_load/PluginsManagerDlg.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +#include + +using QtNodes::PluginsManager; + +class PluginsManagerDlg : public QDialog +{ +public: + PluginsManagerDlg(QWidget *parent = nullptr); + + virtual ~PluginsManagerDlg(); + +public: + void openPluginsFolder(); + + QString pluginsFolderPath() const; + +private: + void loadPluginsFromFolder(); + +private: + QDir _pluginsFolder; + + QStandardItemModel *_model = nullptr; +}; diff --git a/examples/plugins_load/main.cpp b/examples/plugins_load/main.cpp new file mode 100644 index 00000000..ba55cc26 --- /dev/null +++ b/examples/plugins_load/main.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "PluginsManagerDlg.hpp" + +using QtNodes::ConnectionStyle; +using QtNodes::DataFlowGraphicsScene; +using QtNodes::DataFlowGraphModel; +using QtNodes::GraphicsView; +using QtNodes::NodeDelegateModelRegistry; +using QtNodes::PluginsManager; + +static void setStyle() +{ + ConnectionStyle::setConnectionStyle( + R"( + { + "ConnectionStyle": { + "UseDataDefinedColors": true + } + } + )"); +} + +int main(int argc, char *argv[]) +{ + qSetMessagePattern( + "[%{time yyyyMMdd h:mm:ss.zzz}] [%{time process}] " + "[%{if-debug}D%{endif}%{if-info}I%{endif}%{if-warning}W%{endif}%{if-critical}C%{endif}" + "%{if-fatal}F%{endif}]: %{message}\t| (%{function}) [%{file}:%{line}]"); + + QApplication app(argc, argv); + + setStyle(); + + QWidget mainWidget; + + // Load plugins and register models + PluginsManagerDlg pluginsManagerDlg(&mainWidget); + + auto menuBar = new QMenuBar(); + QMenu *menu = menuBar->addMenu("Plugins"); + auto pluginsManagerAction = menu->addAction("Plugins Manager"); + auto pluginsFloderAction = menu->addAction("Open Plugins Folder"); + + QObject::connect(pluginsManagerAction, + &QAction::triggered, + &pluginsManagerDlg, + &PluginsManagerDlg::exec); + + QObject::connect(pluginsFloderAction, + &QAction::triggered, + &pluginsManagerDlg, + &PluginsManagerDlg::openPluginsFolder); + + QVBoxLayout *l = new QVBoxLayout(&mainWidget); + + DataFlowGraphModel dataFlowGraphModel(PluginsManager::instance()->registry()); + + l->addWidget(menuBar); + auto scene = new DataFlowGraphicsScene(dataFlowGraphModel, &mainWidget); + + auto view = new GraphicsView(scene); + l->addWidget(view); + l->setContentsMargins(0, 0, 0, 0); + l->setSpacing(0); + + QObject::connect(scene, &DataFlowGraphicsScene::sceneLoaded, view, &GraphicsView::centerScene); + + mainWidget.setWindowTitle("Data Flow: Plugins Load"); + mainWidget.resize(800, 600); + mainWidget.show(); + + return app.exec(); +} diff --git a/include/QtNodes/PluginInterface b/include/QtNodes/PluginInterface new file mode 100644 index 00000000..d93896bd --- /dev/null +++ b/include/QtNodes/PluginInterface @@ -0,0 +1 @@ +#include "internal/PluginInterface.hpp" \ No newline at end of file diff --git a/include/QtNodes/PluginsManager b/include/QtNodes/PluginsManager new file mode 100644 index 00000000..e1a496e3 --- /dev/null +++ b/include/QtNodes/PluginsManager @@ -0,0 +1 @@ +#include "internal/PluginsManager.hpp" \ No newline at end of file diff --git a/include/QtNodes/internal/PluginInterface.hpp b/include/QtNodes/internal/PluginInterface.hpp new file mode 100644 index 00000000..9d1fd33e --- /dev/null +++ b/include/QtNodes/internal/PluginInterface.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +namespace QtNodes { + +class NodeDelegateModelRegistry; + +class PluginInterface +{ +public: + virtual ~PluginInterface() = default; + + virtual QString name() const = 0; + + virtual void registerDataModels(std::shared_ptr ®) = 0; +}; + +} // namespace QtNodes + +Q_DECLARE_INTERFACE(QtNodes::PluginInterface, "QtNodes.PluginInterface/1.0") diff --git a/include/QtNodes/internal/PluginsManager.hpp b/include/QtNodes/internal/PluginsManager.hpp new file mode 100644 index 00000000..8327019b --- /dev/null +++ b/include/QtNodes/internal/PluginsManager.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "Export.hpp" +#include "PluginInterface.hpp" + +#include +#include +#include +#include + +namespace QtNodes { + +class NodeDelegateModelRegistry; + +class NODE_EDITOR_PUBLIC PluginsManager +{ + PluginsManager(); + + ~PluginsManager(); + +public: + static PluginsManager *instance(); + + std::shared_ptr registry(); + + void loadPlugins(const QString &folderPath = "./plugins"); + + void unloadPlugins(); + + /** + * @brief Load the plug-in from the full file path + * + * @param filePath "C:/plugin_text.dll" + * @return PluginInterface* + */ + PluginInterface *loadPluginFromPath(const QString &filePath); + + std::vector loadPluginFromPaths(const QStringList filePaths); + + /** + * @brief Unload the plugin from the full file path + * + * @param filePath "C:/plugin_text.dll" + * @return bool + */ + bool unloadPluginFromPath(const QString &filePath); + + /** + * @brief Uninstall a plugin by its name, not its file name + * + * @param pluginName "pluginText" + * @return bool + */ + bool unloadPluginFromName(const QString &pluginName); + + inline std::unordered_map loaders() { return _loaders; }; + +private: + static PluginsManager *_instance; + + std::unordered_map _loaders; // plugin name + + std::shared_ptr _register; +}; + +} // namespace QtNodes \ No newline at end of file diff --git a/src/PluginsManager.cpp b/src/PluginsManager.cpp new file mode 100644 index 00000000..f92a4f82 --- /dev/null +++ b/src/PluginsManager.cpp @@ -0,0 +1,147 @@ +#include "PluginsManager.hpp" + +#include "NodeDelegateModelRegistry.hpp" + +#include +#include +#include +#include +#include + +namespace QtNodes { + +PluginsManager *PluginsManager::_instance = nullptr; + +PluginsManager::PluginsManager() +{ + if (!_register) + _register = std::make_shared(); +} + +PluginsManager::~PluginsManager() +{ + unloadPlugins(); + + if (PluginsManager::instance()) { + delete PluginsManager::instance(); + PluginsManager::_instance = nullptr; + } +} + +PluginsManager *PluginsManager::instance() +{ + if (_instance == nullptr) + _instance = new PluginsManager(); + return _instance; +} + +std::shared_ptr PluginsManager::registry() +{ + return _register; +} + +void PluginsManager::loadPlugins(const QString &folderPath) +{ + QDir pluginsDir; + if (!pluginsDir.exists(folderPath)) { + // Created if folderPath does not exist + pluginsDir.mkpath(folderPath); + } + pluginsDir.cd(folderPath); + + QFileInfoList pluginsInfo = pluginsDir.entryInfoList(QDir::Dirs | QDir::Files + | QDir::NoDotAndDotDot | QDir::Hidden); + + for (QFileInfo fileInfo : pluginsInfo) { + if (fileInfo.isFile()) { + loadPluginFromPath(fileInfo.absoluteFilePath()); + } else { + loadPlugins(fileInfo.absoluteFilePath()); + } + } +} + +void PluginsManager::unloadPlugins() +{ + for (auto l : _loaders) { + l.second->unload(); + delete l.second; + } + _loaders.clear(); +} + +PluginInterface *PluginsManager::loadPluginFromPath(const QString &filePath) +{ + // if (!QLibrary::isLibrary(filePath)) + // return nullptr; + + QPluginLoader *loader = new QPluginLoader(filePath); + + qDebug() << loader->metaData(); + + if (loader->isLoaded()) { + PluginInterface *plugin = qobject_cast(loader->instance()); + + QPluginLoader *l = _loaders.find(plugin->name())->second; + plugin = qobject_cast(l->instance()); + + loader->unload(); + delete loader; + + return plugin; + } + + PluginInterface *plugin = qobject_cast(loader->instance()); + if (plugin) { + _loaders[plugin->name()] = loader; + + return plugin; + } else { + qWarning() << loader->errorString(); + + delete loader; + } + + return nullptr; +} + +std::vector PluginsManager::loadPluginFromPaths(const QStringList filePaths) +{ + std::vector vecPlugins; + vecPlugins.clear(); + for (auto path : filePaths) { + vecPlugins.push_back(loadPluginFromPath(path)); + } + return vecPlugins; +} + +bool PluginsManager::unloadPluginFromPath(const QString &filePath) +{ + for (auto l : _loaders) { + if (l.second->fileName() == filePath) { + if (l.second->unload() == false) { + return false; + } + delete l.second; + _loaders.erase(l.first); + return true; + } + } + return false; +} + +bool PluginsManager::unloadPluginFromName(const QString &pluginName) +{ + auto loaderIter = _loaders.find(pluginName); + if (loaderIter != _loaders.end()) { + if (loaderIter->second->unload() == false) { + return false; + } + delete loaderIter->second; + _loaders.erase(loaderIter->first); + return true; + } + return false; +} + +} // namespace QtNodes