From bd98507169875728e3ac201ff478fcc2787b2c48 Mon Sep 17 00:00:00 2001 From: QianMoth <1306057656@qq.com> Date: Mon, 19 Dec 2022 19:14:16 +0800 Subject: [PATCH] plugin system initialisation --- CMakeLists.txt | 3 + examples/CMakeLists.txt | 4 + examples/plugin_text/CMakeLists.txt | 11 ++ examples/plugin_text/PluginDefinition.cpp | 18 ++ examples/plugin_text/PluginDefinition.hpp | 40 +++++ examples/plugin_text/TextData.hpp | 28 +++ examples/plugin_text/TextModel.cpp | 96 ++++++++++ examples/plugin_text/TextModel.hpp | 69 ++++++++ examples/plugins_load/CMakeLists.txt | 6 + examples/plugins_load/main.cpp | 102 +++++++++++ include/QtNodes/PluginInterface | 1 + include/QtNodes/PluginsManager | 1 + include/QtNodes/internal/PluginInterface.hpp | 28 +++ include/QtNodes/internal/PluginsManager.hpp | 78 +++++++++ src/PluginsManager.cpp | 174 +++++++++++++++++++ 15 files changed, 659 insertions(+) create mode 100644 examples/plugin_text/CMakeLists.txt create mode 100644 examples/plugin_text/PluginDefinition.cpp create mode 100644 examples/plugin_text/PluginDefinition.hpp create mode 100644 examples/plugin_text/TextData.hpp create mode 100644 examples/plugin_text/TextModel.cpp create mode 100644 examples/plugin_text/TextModel.hpp create mode 100644 examples/plugins_load/CMakeLists.txt create mode 100644 examples/plugins_load/main.cpp create mode 100644 include/QtNodes/PluginInterface create mode 100644 include/QtNodes/PluginsManager create mode 100644 include/QtNodes/internal/PluginInterface.hpp create mode 100644 include/QtNodes/internal/PluginsManager.hpp create mode 100644 src/PluginsManager.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 76ab0c14..e1d4ab17 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,6 +103,7 @@ set(CPP_SOURCE_FILES src/StyleCollection.cpp src/UndoCommands.cpp src/locateNode.cpp + src/PluginsManager.cpp ) set(HPP_HEADER_FILES @@ -136,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..3109360a --- /dev/null +++ b/examples/plugin_text/CMakeLists.txt @@ -0,0 +1,11 @@ +file(GLOB_RECURSE CPPS ./*.cpp) +file(GLOB_RECURSE HPPS ./*.hpp) + +add_library(plugin_text SHARED ${CPPS} ${HPPS}) + +target_link_libraries(plugin_text QtNodes) + +target_compile_definitions(plugin_text + PUBLIC + NODE_EDITOR_SHARED +) diff --git a/examples/plugin_text/PluginDefinition.cpp b/examples/plugin_text/PluginDefinition.cpp new file mode 100644 index 00000000..28930828 --- /dev/null +++ b/examples/plugin_text/PluginDefinition.cpp @@ -0,0 +1,18 @@ +#include "PluginDefinition.hpp" + +#include "TextModel.hpp" + +Plugin* Plugin::_this_plugin = nullptr; + +Plugin:: +Plugin() +{ _this_plugin = this; } + +void +Plugin:: +registerDataModels(std::shared_ptr & reg) +{ + assert(reg); + + reg->registerModel(); +} diff --git a/examples/plugin_text/PluginDefinition.hpp b/examples/plugin_text/PluginDefinition.hpp new file mode 100644 index 00000000..ea920ad2 --- /dev/null +++ b/examples/plugin_text/PluginDefinition.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include + +// This needs to be the same as the name of your project file ${PROJECT_NAME} +// 这里需要和您的工程文件名一致 ${PROJECT_NAME} +#if defined(plugin_text_EXPORTS) +#define DLL_EXPORT Q_DECL_EXPORT +#else +#define DLL_EXPORT Q_DECL_IMPORT +#endif + +using QtNodes::NodeDelegateModelRegistry; +using QtNodes::PluginInterface; + +#define PLUGIN_NAME "pluginText" + +class DLL_EXPORT Plugin + : public QObject + , public QtNodes::PluginInterface +{ + Q_OBJECT + Q_INTERFACES(QtNodes::PluginInterface) + Q_PLUGIN_METADATA(IID PLUGIN_NAME) + +public: + Plugin(); + + QString + name() const override + { return PLUGIN_NAME; }; + + void + registerDataModels(std::shared_ptr & reg) 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..c417c324 --- /dev/null +++ b/examples/plugin_text/TextData.hpp @@ -0,0 +1,28 @@ +#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..e39182e2 --- /dev/null +++ b/examples/plugin_text/TextModel.cpp @@ -0,0 +1,96 @@ +#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..f7117796 --- /dev/null +++ b/examples/plugin_text/TextModel.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +#include "TextData.hpp" + +#include + +#include + +using QtNodes::PortType; +using QtNodes::PortIndex; +using QtNodes::NodeData; +using QtNodes::NodeDelegateModel; + +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("TextModel"); } + + 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/main.cpp b/examples/plugins_load/main.cpp new file mode 100644 index 00000000..970f3260 --- /dev/null +++ b/examples/plugins_load/main.cpp @@ -0,0 +1,102 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +using QtNodes::DataFlowGraphicsScene; +using QtNodes::DataFlowGraphModel; +using QtNodes::GraphicsView; +using QtNodes::NodeDelegateModelRegistry; +using QtNodes::ConnectionStyle; +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(); + + PluginsManager* pluginsManager = PluginsManager::instance(); + std::shared_ptr registry = pluginsManager->registry(); + pluginsManager->loadPlugins(R"(./nodes)"); + for (auto plugin : pluginsManager->plugins()) + { + plugin.second->registerDataModels(registry); + } + + QWidget mainWidget; + + auto menuBar = new QMenuBar(); + QMenu* menu = menuBar->addMenu("Plugins"); + auto loadAction = menu->addAction("Load Plugin"); + auto unloadAction = menu->addAction("Unload Plugin"); + + QObject::connect(loadAction, &QAction::triggered, + [&]() + { + // TODO: load plugins + QString fileName = + QFileDialog::getOpenFileName(nullptr, + "Load Plugin", + QDir::homePath()); + + if (!QFileInfo::exists(fileName)) + return; + + auto plugin = pluginsManager->loadPluginFromPath(fileName); + if (plugin) + { + plugin->registerDataModels(registry); + } + }); + + QVBoxLayout* l = new QVBoxLayout(&mainWidget); + + DataFlowGraphModel dataFlowGraphModel(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..ebe2ecf7 --- /dev/null +++ b/include/QtNodes/internal/PluginInterface.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace QtNodes +{ + +class NodeDelegateModelRegistry; + +class PluginInterface +{ +public: + virtual + ~PluginInterface() = default; + + virtual + QString + name() const = 0; + + virtual + void + registerDataModels(std::shared_ptr & reg) = 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..49d70be3 --- /dev/null +++ b/include/QtNodes/internal/PluginsManager.hpp @@ -0,0 +1,78 @@ +#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); + + /** + * @brief Unload the plugin from the full file path + * + * @param filePath "C:/plugin_text.dll" + * @return int + */ + int + unloadPluginFromPath(const QString &filePath); + + /** + * @brief Uninstall a plugin by its name, not its file name + * + * @param pluginName "pluginText" + * @return int + */ + int + unloadPluginFromName(const QString &pluginName); + + inline + std::unordered_map + plugins() + { return _plugins; }; + +private: + static PluginsManager* _instance; + + std::unordered_map _plugins; + std::unordered_map _loaders; ///< plugin path + + 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..c7f05d38 --- /dev/null +++ b/src/PluginsManager.cpp @@ -0,0 +1,174 @@ +#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 loadMap : _loaders) + { + unloadPluginFromPath(loadMap.second->fileName()); + } +} + + +PluginInterface* +PluginsManager:: +loadPluginFromPath(const QString & filePath) +{ + if (!QLibrary::isLibrary(filePath)) + return nullptr; + + QPluginLoader* loader = new QPluginLoader(filePath); + if (loader->load()) + { + PluginInterface* plugin = qobject_cast(loader->instance()); + if (plugin) + { + const QString name = plugin->name(); + qDebug() << "add plugin: " << name; + + _loaders[name] = loader; + _plugins[filePath] = plugin; + + return plugin; + } + else + { + delete loader; + loader = nullptr; + } + } + else + { + qCritical() << "loadPlugin:" << filePath << loader->errorString(); + } + return nullptr; +} + + +int +PluginsManager:: +unloadPluginFromPath(const QString & filePath) +{ + auto pluginIter = _plugins.find(filePath); + if(pluginIter != _plugins.end()) + { + auto loaderIter = _loaders.find(pluginIter->second->name()); + if(loaderIter != _loaders.end()) + { + // delete loader + loaderIter->second->unload(); + delete loaderIter->second; + _loaders.erase(loaderIter->first); + } + + // delete plugin + _plugins.erase(pluginIter->first); + return 0; + } + return -1; +} + +int +PluginsManager:: +unloadPluginFromName(const QString &pluginName) +{ + auto loaderIter = _loaders.find(pluginName); + if(loaderIter != _loaders.end()) + { + auto pluginIter = _plugins.find(loaderIter->second->fileName()); + if(pluginIter != _plugins.end()) + { + // delete plugin + _plugins.erase(pluginIter->first); + } + + // delete loaders + loaderIter->second->unload(); + delete loaderIter->second; + _loaders.erase(loaderIter->first); + return 0; + } + return -1; +} + +} // namespace QtNodes