diff --git a/3rdparty/QWingRibbon/ribbon.cpp b/3rdparty/QWingRibbon/ribbon.cpp index cbcf956..9e517f4 100644 --- a/3rdparty/QWingRibbon/ribbon.cpp +++ b/3rdparty/QWingRibbon/ribbon.cpp @@ -60,6 +60,12 @@ RibbonTabContent *Ribbon::addTab(const QString &tabName) { return ribbonTabContent; } +void Ribbon::addTab(RibbonTabContent *tabContent, const QString &tabName) { + if (tabContent) { + QTabWidget::addTab(tabContent, tabName); + } +} + RibbonTabContent *Ribbon::addTab(const QIcon &tabIcon, const QString &tabName) { // Note: superclass QTabWidget also has a function addTab() RibbonTabContent *ribbonTabContent = new RibbonTabContent; diff --git a/3rdparty/QWingRibbon/ribbon.h b/3rdparty/QWingRibbon/ribbon.h index 1568511..8e8bf7f 100644 --- a/3rdparty/QWingRibbon/ribbon.h +++ b/3rdparty/QWingRibbon/ribbon.h @@ -29,6 +29,11 @@ public: /// \param[in] tabName Name of the tab RibbonTabContent *addTab(const QString &tabName); + /// Add a tab to the ribbon. + /// + /// \param[in] tabContent pointer of the tab + void addTab(RibbonTabContent *tabContent, const QString &tabName); + /// Add a tab to the ribbon. /// /// \param[in] tabIcon Icon of the tab diff --git a/CMakeLists.txt b/CMakeLists.txt index 911eb75..e421507 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,11 @@ find_package( Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network Concurrent PrintSupport Xml LinguistTools) +option(BUILD_TEST_PLUGIN OFF) +if(BUILD_TEST_PLUGIN) + add_subdirectory(TestPlugin) +endif() + add_definitions(-DWINGHEX_VERSION="${PROJECT_VERSION}" -DAPP_ORG="WingCloudStudio" -DAPP_NAME="${PROJECT_NAME}") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/WINGHEX_VERSION" ${PROJECT_VERSION}) @@ -418,6 +423,7 @@ set(PROJECT_SOURCES main.cpp src/utilities.h src/dbghelper.h + src/define.h ${QCONSOLEWIDGET_SRC} ${QPATHEDIT_SRC} ${WIDGET_FRAME_SRC} diff --git a/README.md b/README.md index 369abfe..c7e1c6c 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,8 @@   如果你想将本软件的代码用于闭源的商业代码,想要解除`GPL`系列的必须开源的限制,请必须亲自咨询我,商讨商业授权相关事宜。 +  对于插件开发相关的,对应的开源协议就不一样了。只针对本仓库下的`src/plugin/iwingplugin.h`和`src/plugin/settingpage.h`遵守`BSD 3-Clause`协议,以允许想要做闭源和商业开发。对于本仓库下的`TestPlugin`的代码(除`TranslationUtils.cmake`这一个文件遵守`BSD 3-Clause`)遵守`MIT`协议。 + ### 使用声明 1. 本软件源代码不得私自应用于闭源商业用途,除非你完整开源(GPL协议的要求)。如果要将软件仓库的代码商用闭源,必须找我购买商业授权签订合同,价格私聊,非诚勿扰。 diff --git a/README_en.md b/README_en.md index 4e7d5f7..fa77b2e 100644 --- a/README_en.md +++ b/README_en.md @@ -107,6 +107,8 @@ This software complies with the `AGPL-3.0` agreement. Please do not use it for p If you want to use the code of this software for closed-source commercial code and want to lift the restriction of the `GPL` series that it must be open source, please consult me ​​in person to discuss commercial licensing matters. +For plugin development, the corresponding open source agreements are different. Only `src/plugin/iwingplugin.h` and `src/plugin/settingpage.h` in this repository comply with the `BSD 3-Clause` agreement to allow closed-source and commercial development. The code of `TestPlugin` in this repository (except the file `TranslationUtils.cmake` which complies with `BSD 3-Clause`) complies with the `MIT` agreement. + ### Usage Statement 1. The source code of this software shall not be used for closed-source commercial purposes unless you open source it completely (as required by the GPL agreement). If you want to commercially close the code of the software warehouse, you must contact me to purchase a commercial license and sign a contract. Please contact me for the price. Please do not disturb me if you are not serious. diff --git a/TestPlugin/CMakeLists.txt b/TestPlugin/CMakeLists.txt new file mode 100644 index 0000000..b6a2a35 --- /dev/null +++ b/TestPlugin/CMakeLists.txt @@ -0,0 +1,103 @@ +cmake_minimum_required(VERSION 3.16) + +project(TestPlugin) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) + +# Test mode, please configure the main program directory to facilitate debugging + +# 测试模式,启用请配置主程序目录,方便调试 + +# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +option(TEST_MODE TRUE) + +# Set the location where the SDK is stored. Only iwingplugin.h and settingpage.h +# are required. + +# 设置存放 SDK 的位置,只须 iwingplugin.h 和 settingpage.h 存在 + +# vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv +set(WINGHEX_SDK "${CMAKE_CURRENT_SOURCE_DIR}/../src/plugin") + +set(PLUGIN_INTERFACE_FOUND FALSE) +set(PLUGIN_SETPAGE_FOUND FALSE) +if(EXISTS "${WINGHEX_SDK}/iwingplugin.h") + set(PLUGIN_INTERFACE_FOUND TRUE) +endif() +if(EXISTS "${WINGHEX_SDK}/settingpage.h") + set(PLUGIN_SETPAGE_FOUND TRUE) +endif() +if(PLUGIN_INTERFACE_FOUND AND PLUGIN_SETPAGE_FOUND) + message(STATUS "${WINGHEX_SDK} is valid SDK path") +else() + message(FATAL_ERROR "Invalid SDK path!") +endif() + +set(WINGHEX_SDK_HEADER "${WINGHEX_SDK}/iwingplugin.h" + "${WINGHEX_SDK}/settingpage.h") +include_directories(${WINGHEX_SDK}) + +# For Qt +find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets LinguistTools) +find_package( + Qt${QT_VERSION_MAJOR} + COMPONENTS Widgets LinguistTools + REQUIRED) + +set(TS_FILES "${CMAKE_CURRENT_SOURCE_DIR}/lang/TestPlugin_zh_CN.ts") + +set(TRANSLATION_PATH ${CMAKE_CURRENT_SOURCE_DIR}) + +if(${QT_VERSION_MAJOR} EQUAL 5) + qt5_create_translation(QM_FILES ${TRANSLATION_PATH} ${TS_FILES} OPTIONS + -no-obsolete) +elseif(${QT_VERSION_MAJOR} EQUAL 6) + qt6_create_translation(QM_FILES ${TRANSLATION_PATH} ${TS_FILES} OPTIONS + -no-obsolete) +else() + message(FATAL_ERROR "Unsupported QT version") +endif() + +include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/TranslationUtils.cmake) +add_plugin_translations_resource(QM_RES TestPlugin ${QM_FILES}) + +add_library( + TestPlugin SHARED + ${WINGHEX_SDK_HEADER} + testplugin.h + testplugin.cpp + TestPlugin.json + testform.h + testform.cpp + testform.ui + ${QM_FILES} + ${QM_RES} + resources.qrc) + +set_target_properties(TestPlugin PROPERTIES SUFFIX ".wingplg") + +if(TEST_MODE) + # If you want to be able to debug easily every time you compile, please set + # this variable. Because this test plugin is a subproject of the main + # project, use CMAKE_BINARY_DIR + + # 如果你想每次编译都能够方便调试的话,请设置这个变量。因为这个测试插件是和主项目子项目,所以用 CMAKE_BINARY_DIR + + # vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv + set(WINGHEX_PATH "${CMAKE_BINARY_DIR}") + + set(WINGHEX_PLUGIN_PATH "${WINGHEX_PATH}/plugin") + add_custom_command( + TARGET TestPlugin + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E make_directory ${WINGHEX_PLUGIN_PATH} + COMMAND ${CMAKE_COMMAND} -E copy_if_different $ + ${WINGHEX_PLUGIN_PATH}) +endif() + +target_link_libraries(TestPlugin PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) diff --git a/TestPlugin/README.md b/TestPlugin/README.md new file mode 100644 index 0000000..3f81144 --- /dev/null +++ b/TestPlugin/README.md @@ -0,0 +1,36 @@ +# TestPlugin + +## How to Build + +Create a build directory and run + + cmake -DCMAKE_PREFIX_PATH= -DCMAKE_BUILD_TYPE=RelWithDebInfo + cmake --build . + +where `` is the relative or absolute path to a Qt Creator build directory, or to a +combined binary and development package (Windows / Linux), or to the `Qt Creator.app/Contents/Resources/` +directory of a combined binary and development package (macOS), and `` is the +relative or absolute path to this plugin directory. + +## How to Run + +Run a compatible Qt Creator with the additional command line argument + + -pluginpath + +where `` is the path to the resulting plugin library in the build directory +(`/lib/qtcreator/plugins` on Windows and Linux, +`/Qt Creator.app/Contents/PlugIns` on macOS). + +You might want to add `-temporarycleansettings` (or `-tcs`) to ensure that the opened Qt Creator +instance cannot mess with your user-global Qt Creator settings. + +When building and running the plugin from Qt Creator, you can use + + -pluginpath "%{buildDir}/lib/qtcreator/plugins" -tcs + +on Windows and Linux, or + + -pluginpath "%{buildDir}/Qt Creator.app/Contents/PlugIns" -tcs + +for the `Command line arguments` field in the run settings. diff --git a/TestPlugin/TestPlugin.json b/TestPlugin/TestPlugin.json new file mode 100644 index 0000000..c9fe07d --- /dev/null +++ b/TestPlugin/TestPlugin.json @@ -0,0 +1,13 @@ +{ + "Id" : "testplugin", + "Name" : "TestPlugin", + "Version" : "0.0.1", + "CompatVersion" : "0.0.1", + "Vendor" : "WingCloudStudio", + "VendorId" : "wingcloudstudio", + "Copyright" : "wingsummer", + "License" : "AGPL-3.0", + "Description" : "A Test Plugin for WingHexExplorer2", + "Url" : "https://github.com/Wing-summer/WingHexExplorer2", + "DocumentationUrl" : "" +} diff --git a/TestPlugin/cmake/LICENSE.txt b/TestPlugin/cmake/LICENSE.txt new file mode 100644 index 0000000..c27ac62 --- /dev/null +++ b/TestPlugin/cmake/LICENSE.txt @@ -0,0 +1,29 @@ +BSD 3-Clause License + +Copyright (c) 2019-2022, Pedro López-Cabanillas +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/TestPlugin/cmake/TranslationUtils.cmake b/TestPlugin/cmake/TranslationUtils.cmake new file mode 100644 index 0000000..07028b9 --- /dev/null +++ b/TestPlugin/cmake/TranslationUtils.cmake @@ -0,0 +1,51 @@ +# ============================================================================== +# Copyright (C) 2024-2027 WingSummer +# +# You can redistribute this file and/or modify it under the terms of the BSD +# 3-Clause. +# +# THIS FILE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ============================================================================= + +if(NOT TARGET Qt${QT_VERSION_MAJOR}::lconvert) + message( + FATAL_ERROR + "The package \"Qt${QT_VERSION_MAJOR}LinguistTools\" is required.") +endif() + +set(Qt_LCONVERT_EXECUTABLE Qt${QT_VERSION_MAJOR}::lconvert) + +function(ADD_PLUGIN_TRANSLATIONS_RESOURCE res_file target) + set(_qm_files ${ARGN}) + set(_res_file ${CMAKE_CURRENT_BINARY_DIR}/app_translations.qrc) + + file( + WRITE ${_res_file} + "\n \n" + ) + foreach(_lang ${_qm_files}) + get_filename_component(_filename ${_lang} NAME) + file(APPEND ${_res_file} " ${_filename}\n") + endforeach() + file(APPEND ${_res_file} " \n\n") + + set(${res_file} + ${_res_file} + PARENT_SCOPE) +endfunction() + +add_custom_target( + lupdate + COMMAND ${Qt${QT_VERSION_MAJOR}_LUPDATE_EXECUTABLE} -recursive + ${PROJECT_SOURCE_DIR} -ts *.ts + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + COMMENT "Updating translations") diff --git a/TestPlugin/images/btn.png b/TestPlugin/images/btn.png new file mode 100644 index 0000000..9882d9e Binary files /dev/null and b/TestPlugin/images/btn.png differ diff --git a/TestPlugin/images/test.png b/TestPlugin/images/test.png new file mode 100644 index 0000000..4229c61 Binary files /dev/null and b/TestPlugin/images/test.png differ diff --git a/TestPlugin/lang/TestPlugin_zh_CN.ts b/TestPlugin/lang/TestPlugin_zh_CN.ts new file mode 100644 index 0000000..1bce98e --- /dev/null +++ b/TestPlugin/lang/TestPlugin_zh_CN.ts @@ -0,0 +1,71 @@ + + + + + TestForm + + + TestForm + 测试窗体 + + + + Basic + 基础 + + + + Reader + 读取器 + + + + Controller + 控制器 + + + + MessageBox + 信息框 + + + + InputBox + 输入框 + + + + FileDialog + 文件框 + + + + ColorDialog + 颜色框 + + + + DataVisual + 数据可视化 + + + + TestPlugin + + + Test + 测试 + + + + + TestPlugin + 测试插件 + + + + A Test Plugin for WingHexExplorer2. + 一个用来测试羽云十六进制编辑器2的插件 + + + diff --git a/TestPlugin/resources.qrc b/TestPlugin/resources.qrc new file mode 100644 index 0000000..2399f70 --- /dev/null +++ b/TestPlugin/resources.qrc @@ -0,0 +1,6 @@ + + + images/btn.png + images/test.png + + diff --git a/TestPlugin/testform.cpp b/TestPlugin/testform.cpp new file mode 100644 index 0000000..a936add --- /dev/null +++ b/TestPlugin/testform.cpp @@ -0,0 +1,28 @@ +/*============================================================================== +** Copyright (C) 2024-2027 WingSummer +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** ============================================================================= +*/ + +#include "testform.h" +#include "ui_testform.h" + +TestForm::TestForm(QWidget *parent) : QWidget(parent), ui(new Ui::TestForm) { + ui->setupUi(this); +} + +TestForm::~TestForm() { delete ui; } diff --git a/TestPlugin/testform.h b/TestPlugin/testform.h new file mode 100644 index 0000000..353e19c --- /dev/null +++ b/TestPlugin/testform.h @@ -0,0 +1,41 @@ +/*============================================================================== +** Copyright (C) 2024-2027 WingSummer +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** ============================================================================= +*/ + +#ifndef TESTFORM_H +#define TESTFORM_H + +#include + +namespace Ui { +class TestForm; +} + +class TestForm : public QWidget { + Q_OBJECT + +public: + explicit TestForm(QWidget *parent = nullptr); + ~TestForm(); + +private: + Ui::TestForm *ui; +}; + +#endif // TESTFORM_H diff --git a/TestPlugin/testform.ui b/TestPlugin/testform.ui new file mode 100644 index 0000000..540c576 --- /dev/null +++ b/TestPlugin/testform.ui @@ -0,0 +1,74 @@ + + + TestForm + + + + 0 + 0 + 600 + 400 + + + + + 600 + 400 + + + + TestForm + + + + + + 0 + + + + Basic + + + + + Reader + + + + + Controller + + + + + MessageBox + + + + + InputBox + + + + + FileDialog + + + + + ColorDialog + + + + + DataVisual + + + + + + + + + diff --git a/TestPlugin/testplugin.cpp b/TestPlugin/testplugin.cpp new file mode 100644 index 0000000..b8100f5 --- /dev/null +++ b/TestPlugin/testplugin.cpp @@ -0,0 +1,116 @@ +/*============================================================================== +** Copyright (C) 2024-2027 WingSummer +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** ============================================================================= +*/ + +#include "testplugin.h" +#include "testform.h" + +TestPlugin::TestPlugin() : puid(QStringLiteral("TestPlugin2")) { + // 在构造函数中,所有的 API 都无法调用。插件的翻译文件也不会自动加载。 + // 在构造函数中,仅适合做一些为初始化准备的操作。 + + // 插件的语言文件会在初始化前自动加载,如果初始化失败则会被卸载 + // 初始化会传递一个配置类,插件系统会统一管理放到统一的地方,使用 INI 保存 + // 你可以自行管理,但不建议,统一管理方便使用者备份和转移插件配置 +} + +TestPlugin::~TestPlugin() {} + +int TestPlugin::sdkVersion() const { return WingHex::SDKVERSION; } + +const QString TestPlugin::signature() const { return WingHex::WINGSUMMER; } + +bool TestPlugin::init(const QSettings &set) { + auto v = set.value("Test", 0).toInt(); + // 如果你之前启动过且正常推出,这个值一定是 5 + qDebug() << v; + + // 和日志与 UI 相关的接口此时可用,剩余的 API 初始化成功才可用 + _tform = emit createDialog(new TestForm); + + using TBInfo = WingHex::WingRibbonToolBoxInfo; + TBInfo::RibbonCatagories cats; + auto tb = new QToolButton; + + // 这里有一个约定,对于含有图片的,前缀应为:/images/插件的 PUID + // 其他资源,前缀应为:/res/插件的 PUID 或 /resources/插件的 PUID + // 如果想省劲,可以统一为 /插件的 PUID,但别一个 / 完事。 + // 这很容易发生资源文件冲突!!! + tb->setIcon(QIcon(QStringLiteral(":/images/TestPlugin/images/test.png"))); + tb->setText(tr("Test")); + connect(tb, &QToolButton::clicked, this, [=] { + if (_tform) { + _tform->show(); + _tform->raise(); + } + }); + _rtbinfo = {WingHex::WingRibbonToolBoxInfo{ + .catagory = cats.PLUGIN, + .toolboxs = { + TBInfo::Toolbox{.name = tr("TestPlugin"), .tools = {tb}}}}}; + + return true; +} + +void TestPlugin::unload(QSettings &set) { + // 设个数字,那就是 5 测试一下配置是否正常工作 + set.setValue("Test", 5); + + _tform->close(); + _tform->deleteLater(); +} + +const QString TestPlugin::pluginName() const { return tr("TestPlugin"); } + +const QString TestPlugin::pluginAuthor() const { return WingHex::WINGSUMMER; } + +uint TestPlugin::pluginVersion() const { return WingHex::SDKVERSION; } + +const QString TestPlugin::pluginComment() const { + return tr("A Test Plugin for WingHexExplorer2."); +} + +QList TestPlugin::registeredDockWidgets() const { + return {}; +} + +QMenu *TestPlugin::registeredHexContextMenu() const { return _tmenu; } + +QList +TestPlugin::registeredRibbonTools() const { + return _rtbinfo; +} + +QHash TestPlugin::registeredSettingPages() const { + return {}; +} + +QList TestPlugin::registeredPages() const { return {}; } + +QList +TestPlugin::registeredEditorViewWidgets() const { + return {}; +} + +QString TestPlugin::getPuid() const { return puid; } + +QHash +TestPlugin::registeredScriptFn() { + return {}; +} diff --git a/TestPlugin/testplugin.h b/TestPlugin/testplugin.h new file mode 100644 index 0000000..96b5745 --- /dev/null +++ b/TestPlugin/testplugin.h @@ -0,0 +1,82 @@ +/*============================================================================== +** Copyright (C) 2024-2027 WingSummer +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to deal +** in the Software without restriction, including without limitation the rights +** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +** copies of the Software, and to permit persons to whom the Software is +** furnished to do so. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +** OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +** THE SOFTWARE. +** ============================================================================= +*/ + +#ifndef TESTPLUGIN_H +#define TESTPLUGIN_H + +#include "iwingplugin.h" + +class TestPlugin final : public WingHex::IWingPlugin { + Q_OBJECT + + // 可选:用于自定义插件唯一标帜,请不要随意取名,否则很可能会和其他插件冲突导致无法加载 + // 如果不注明,则是插件的类名作为唯一标帜,所以也不要随意给插件类起名字 + // 当然,你可以在构造函数中,使用 setProperty("puid", yourID) 来实现自定义 + Q_PROPERTY(QString puid READ getPuid CONSTANT FINAL) + + // 这两行是必须,只有后面的 "TestPlugin.json" 你根据需要修改, + // 具体可见 QT 文档,剩下直接 CV 大法 + Q_PLUGIN_METADATA(IID "com.wingsummer.iwingplugin" FILE "TestPlugin.json") + Q_INTERFACES(WingHex::IWingPlugin) + +public: + explicit TestPlugin(); + + virtual ~TestPlugin(); + + // IWingPlugin interface (必须) +public: + virtual int sdkVersion() const override; + virtual const QString signature() const override; + virtual bool init(const QSettings &set) override; + virtual void unload(QSettings &set) override; + virtual const QString pluginName() const override; + virtual const QString pluginAuthor() const override; + virtual uint pluginVersion() const override; + virtual const QString pluginComment() const override; + + // IWingPlugin interface (可选) +public: + // 有关注册开头(register 开头)的函数,插件系统不保证有且只调用一次 + // 所以最好不要为了偷懒,在实现里面 new 和做相应操作 + virtual QList + registeredDockWidgets() const override; + virtual QMenu *registeredHexContextMenu() const override; + virtual QList + registeredRibbonTools() const override; + virtual QHash + registeredSettingPages() const override; + virtual QList registeredPages() const override; + virtual QList + registeredEditorViewWidgets() const override; + virtual QHash registeredScriptFn() override; + +private: + QString getPuid() const; + +private: + QDialog *_tform = nullptr; + QMenu *_tmenu = nullptr; + const QString puid; + + QList _rtbinfo; +}; + +#endif // TESTPLUGIN_H diff --git a/lang/zh_CN/winghex.ts b/lang/zh_CN/winghex.ts index 23a49d4..b388e05 100644 --- a/lang/zh_CN/winghex.ts +++ b/lang/zh_CN/winghex.ts @@ -1966,62 +1966,62 @@ PluginSystem - + LoadingPlugin 正在加载插件: - + ErrLoadPluginSign 插件加载失败:非法插件签名! - + ErrLoadPluginSDKVersion 插件加载失败:非法插件 SDK 版本! - + ErrLoadPluginNoName 插件加载失败:非法插件名称! - + ErrLoadLoadedPlugin 插件加载失败:重复加载插件! - + ErrLoadInitPlugin 插件加载失败:初始化插件失败! - + PluginName : 插件名: - + PluginAuthor : 插件作者: - + PluginWidgetRegister 注册插件对象中…… - + Creating UI widget is not allowed in non-UI thread 不能够在非用户界面线程创建用户界面组件 - + FoundPluginCount 总计发现插件数目: - + PluginLoadingFinished 加载插件完毕! @@ -5007,17 +5007,17 @@ Do you wish to keep up to date by reloading the file? WingAngelAPI - + AngelScriptService AngelScript 服务 - + A internal plugin that provides AngelScript scripts with the ability to call the host API. 为 AngelScript 脚本提供调用主机 API 能力的内部插件。 - + NotSupportedQMetaType: 不支持的 QT 数据元类型: diff --git a/main.cpp b/main.cpp index 92283d0..9c9a12e 100644 --- a/main.cpp +++ b/main.cpp @@ -1,6 +1,8 @@ #include "class/appmanager.h" #include "class/settingmanager.h" +#include "define.h" + int main(int argc, char *argv[]) { /* 有关对在 QT5 的 Win 平台禁用高 dpi 支持 * 的原因说明: @@ -41,7 +43,7 @@ int main(int argc, char *argv[]) { } return a.exec(); - } catch (int errCode) { - return errCode; + } catch (CrashCode errCode) { + return int(errCode); } } diff --git a/src/class/languagemanager.cpp b/src/class/languagemanager.cpp index 2204b46..dcf2688 100644 --- a/src/class/languagemanager.cpp +++ b/src/class/languagemanager.cpp @@ -18,6 +18,7 @@ #include "languagemanager.h" #include "class/settingmanager.h" +#include "define.h" #include "wingmessagebox.h" #include @@ -174,11 +175,28 @@ void LanguageManager::abortAndExit() { "solve the problem.")); } - throw(-1); + throw CrashCode::LanguageFile; } QLocale LanguageManager::defaultLocale() const { return _defaultLocale; } +QTranslator *LanguageManager::try2LoadPluginLang(const QString &plgID) { + QDir langDir(QStringLiteral(":/PLGLANG")); + if (!langDir.cd(plgID)) { + return nullptr; + } + auto translator = new QTranslator(this); + if (translator->load(_defaultLocale, plgID, QStringLiteral("_"), + langDir.absolutePath())) { + if (qApp->installTranslator(translator)) { + return translator; + } + } + + translator->deleteLater(); + return nullptr; +} + LanguageManager::LanguageData LanguageManager::data() const { return _data; } QStringList LanguageManager::langsDisplay() const { return m_langsDisplay; } diff --git a/src/class/languagemanager.h b/src/class/languagemanager.h index 3220443..96dd633 100644 --- a/src/class/languagemanager.h +++ b/src/class/languagemanager.h @@ -22,6 +22,7 @@ #include #include #include +#include class LanguageManager : public QObject { Q_OBJECT @@ -42,6 +43,8 @@ public: QLocale defaultLocale() const; + QTranslator *try2LoadPluginLang(const QString &plgID); + private: LanguageManager(); diff --git a/src/class/wingangelapi.cpp b/src/class/wingangelapi.cpp index 0671e9f..4b76bd6 100644 --- a/src/class/wingangelapi.cpp +++ b/src/class/wingangelapi.cpp @@ -55,12 +55,15 @@ int WingAngelAPI::sdkVersion() const { return WingHex::SDKVERSION; } const QString WingAngelAPI::signature() const { return WingHex::WINGSUMMER; } -bool WingAngelAPI::init(const QList &loadedplugin) { - Q_UNUSED(loadedplugin); +bool WingAngelAPI::init(const QSettings &set) { + Q_UNUSED(set); return true; } -void WingAngelAPI::unload() { this->disconnect(); } +void WingAngelAPI::unload(QSettings &set) { + Q_UNUSED(set); + this->disconnect(); +} const QString WingAngelAPI::pluginName() const { return tr("AngelScriptService"); diff --git a/src/class/wingangelapi.h b/src/class/wingangelapi.h index ec6a476..652a159 100644 --- a/src/class/wingangelapi.h +++ b/src/class/wingangelapi.h @@ -39,9 +39,8 @@ public: public: virtual int sdkVersion() const override; virtual const QString signature() const override; - virtual bool - init(const QList &loadedplugin) override; - virtual void unload() override; + virtual bool init(const QSettings &set) override; + virtual void unload(QSettings &set) override; virtual const QString pluginName() const override; virtual const QString pluginAuthor() const override; virtual uint pluginVersion() const override; diff --git a/src/define.h b/src/define.h new file mode 100644 index 0000000..06d9a51 --- /dev/null +++ b/src/define.h @@ -0,0 +1,6 @@ +#ifndef DEFINE_H +#define DEFINE_H + +enum class CrashCode : int { LanguageFile = 1, PluginSetting }; + +#endif // DEFINE_H diff --git a/src/plugin/iwingplugin.h b/src/plugin/iwingplugin.h index 4cdfe60..5844fb5 100644 --- a/src/plugin/iwingplugin.h +++ b/src/plugin/iwingplugin.h @@ -45,7 +45,7 @@ namespace WingHex { -Q_DECL_UNUSED constexpr auto SDKVERSION = 13; +Q_DECL_UNUSED constexpr auto SDKVERSION = 14; Q_DECL_UNUSED static QString PLUGINDIR() { return QCoreApplication::applicationDirPath() + QStringLiteral("/plugin"); @@ -147,8 +147,8 @@ signals: bool isLocked(); qsizetype documentLines(); qsizetype documentBytes(); - HexPosition currentPos(); - HexPosition selectionPos(); + WingHex::HexPosition currentPos(); + WingHex::HexPosition selectionPos(); qsizetype currentRow(); qsizetype currentColumn(); qsizetype currentOffset(); @@ -191,23 +191,19 @@ signals: // metadata bool lineHasMetadata(qsizetype line); - QList getMetadatas(qsizetype offset); + QList getMetadatas(qsizetype offset); // bookmark bool lineHasBookMark(qsizetype line); QList getsBookmarkPos(qsizetype line); - BookMark bookMark(qsizetype pos); + WingHex::BookMark bookMark(qsizetype pos); QString bookMarkComment(qsizetype pos); - QList getBookMarks(); + QList getBookMarks(); bool existBookMark(qsizetype pos); // extension QStringList getSupportedEncodings(); QString currentEncoding(); - - // not available for AngelScript - // only for plugin UI extenstion - QDialog *createDialog(QWidget *content); }; class Controller : public QObject { @@ -283,21 +279,22 @@ signals: bool setMetaCommentVisible(bool b); // mainwindow - ErrFile newFile(); - ErrFile openFile(const QString &filename); - ErrFile openRegionFile(const QString &filename, qsizetype start = 0, - qsizetype length = 1024); - ErrFile openDriver(const QString &driver); - ErrFile closeFile(int handle, bool force = false); - ErrFile saveFile(int handle, bool ignoreMd5 = false); - ErrFile exportFile(int handle, const QString &savename, - bool ignoreMd5 = false); + WingHex::ErrFile newFile(); + WingHex::ErrFile openFile(const QString &filename); + WingHex::ErrFile openRegionFile(const QString &filename, + qsizetype start = 0, + qsizetype length = 1024); + WingHex::ErrFile openDriver(const QString &driver); + WingHex::ErrFile closeFile(int handle, bool force = false); + WingHex::ErrFile saveFile(int handle, bool ignoreMd5 = false); + WingHex::ErrFile exportFile(int handle, const QString &savename, + bool ignoreMd5 = false); bool exportFileGUI(); - ErrFile saveAsFile(int handle, const QString &savename, - bool ignoreMd5 = false); + WingHex::ErrFile saveAsFile(int handle, const QString &savename, + bool ignoreMd5 = false); bool saveAsFileGUI(); - ErrFile closeCurrentFile(bool force = false); - ErrFile saveCurrentFile(bool ignoreMd5 = false); + WingHex::ErrFile closeCurrentFile(bool force = false); + WingHex::ErrFile saveCurrentFile(bool ignoreMd5 = false); bool openFileGUI(); bool openRegionFileGUI(); bool openDriverGUI(); @@ -313,7 +310,7 @@ signals: bool clearBookMark(); // workspace - ErrFile openWorkSpace(const QString &filename); + WingHex::ErrFile openWorkSpace(const QString &filename); bool setCurrentEncoding(const QString &encoding); }; @@ -426,14 +423,6 @@ signals: } // namespace WingPlugin -struct WingPluginInfo { - QString pluginName; - QString pluginAuthor; - uint pluginVersion; - QString puid; - QString pluginComment; -}; - const auto WINGSUMMER = QStringLiteral("wingsummer"); struct WingDockWidgetInfo { @@ -534,8 +523,8 @@ public: virtual const QString signature() const = 0; virtual ~IWingPlugin() = default; - virtual bool init(const QList &loadedplugin) = 0; - virtual void unload() = 0; + virtual bool init(const QSettings &set) = 0; + virtual void unload(QSettings &set) = 0; virtual const QString pluginName() const = 0; virtual const QString pluginAuthor() const = 0; virtual uint pluginVersion() const = 0; @@ -569,6 +558,10 @@ signals: void error(const QString &message); void info(const QString &message); + // not available for AngelScript + // only for plugin UI extenstion + QDialog *createDialog(QWidget *content); + public: WingPlugin::Reader reader; WingPlugin::Controller controller; @@ -581,8 +574,6 @@ public: } // namespace WingHex -constexpr auto IWINGPLUGIN_INTERFACE_IID = "com.wingsummer.iwingplugin"; - -Q_DECLARE_INTERFACE(WingHex::IWingPlugin, IWINGPLUGIN_INTERFACE_IID) +Q_DECLARE_INTERFACE(WingHex::IWingPlugin, "com.wingsummer.iwingplugin") #endif // IWINGPLUGIN_H diff --git a/src/plugin/pluginsystem.cpp b/src/plugin/pluginsystem.cpp index 3d7760d..40d8f6f 100644 --- a/src/plugin/pluginsystem.cpp +++ b/src/plugin/pluginsystem.cpp @@ -18,13 +18,16 @@ #include "pluginsystem.h" #include "QJsonModel/include/QJsonModel.hpp" #include "Qt-Advanced-Docking-System/src/DockAreaWidget.h" +#include "class/languagemanager.h" #include "class/logger.h" #include "class/settingmanager.h" #include "class/wingfiledialog.h" #include "class/winginputdialog.h" #include "class/wingmessagebox.h" #include "control/toast.h" +#include "define.h" #include "dialog/colorpickerdialog.h" +#include "dialog/framelessdialogbase.h" #include "dialog/mainwindow.h" #include "model/qjsontablemodel.h" @@ -37,8 +40,17 @@ PluginSystem::PluginSystem(QObject *parent) : QObject(parent) {} PluginSystem::~PluginSystem() { + QDir udir(Utilities::getAppDataPath()); + auto plgset = QStringLiteral("plgset"); + udir.mkdir(plgset); + if (!udir.cd(plgset)) { + qApp->exit(int(CrashCode::PluginSetting)); + } + for (auto &item : loadedplgs) { - item->unload(); + QSettings set(udir.absoluteFilePath(item->metaObject()->className()), + QSettings::Format::IniFormat); + item->unload(set); delete item; } } @@ -49,7 +61,7 @@ const IWingPlugin *PluginSystem::plugin(qsizetype index) const { return loadedplgs.at(index); } -void PluginSystem::loadPlugin(QFileInfo fileinfo) { +void PluginSystem::loadPlugin(const QFileInfo &fileinfo, const QDir &setdir) { Q_ASSERT(_win); if (fileinfo.exists()) { @@ -59,7 +71,7 @@ void PluginSystem::loadPlugin(QFileInfo fileinfo) { if (Q_UNLIKELY(p == nullptr)) { Logger::critical(loader.errorString()); } else { - if (!loadPlugin(p)) { + if (!loadPlugin(p, setdir)) { loader.unload(); } } @@ -267,8 +279,19 @@ QString PluginSystem::getScriptFnSig(const QString &fnName, return sig + params.join(',') + QStringLiteral(")"); } -bool PluginSystem::loadPlugin(IWingPlugin *p) { - QList loadedplginfos; +QString PluginSystem::getPUID(IWingPlugin *p) { + if (p) { + auto prop = p->property("puid").toString().trimmed(); + auto pid = QString(p->metaObject()->className()); + return prop.isEmpty() ? pid : prop; + } else { + return {}; + } +} + +bool PluginSystem::loadPlugin(IWingPlugin *p, const QDir &setdir) { + QTranslator *p_tr = nullptr; + try { if (p->signature() != WINGSUMMER) { throw tr("ErrLoadPluginSign"); @@ -282,34 +305,41 @@ bool PluginSystem::loadPlugin(IWingPlugin *p) { throw tr("ErrLoadPluginNoName"); } - auto prop = p->property("puid").toString().trimmed(); - auto puid = - prop.isEmpty() ? QString(p->metaObject()->className()) : prop; + auto puid = getPUID(p); if (loadedpuid.contains(puid)) { throw tr("ErrLoadLoadedPlugin"); } emit pluginLoading(p->pluginName()); - if (!p->init(loadedplginfos)) { - throw tr("ErrLoadInitPlugin"); + auto pid = QString(p->metaObject()->className()); + p_tr = LanguageManager::instance().try2LoadPluginLang(pid); + + connectLoadingInterface(p); + + QSettings *setp; + if (setdir == QDir()) { + setp = new QSettings; + } else { + setp = new QSettings( + setdir.absoluteFilePath(p->metaObject()->className()), + QSettings::Format::IniFormat); } - WingPluginInfo info; - info.puid = puid; - info.pluginName = p->pluginName(); - info.pluginAuthor = p->pluginAuthor(); - info.pluginComment = p->pluginComment(); - info.pluginVersion = p->pluginVersion(); + if (!p->init(*setp)) { + setp->deleteLater(); + throw tr("ErrLoadInitPlugin"); + } + setp->deleteLater(); - loadedplginfos.push_back(info); loadedplgs.push_back(p); loadedpuid << puid; - Logger::warning(tr("PluginName :") + info.pluginName); - Logger::warning(tr("PluginAuthor :") + info.pluginAuthor); + Logger::warning(tr("PluginName :") + p->pluginName()); + Logger::warning(tr("PluginAuthor :") + p->pluginAuthor()); Logger::warning(tr("PluginWidgetRegister")); + // ensure call only once auto ribbonToolboxInfos = p->registeredRibbonTools(); if (!ribbonToolboxInfos.isEmpty()) { for (auto &item : ribbonToolboxInfos) { @@ -328,7 +358,14 @@ bool PluginSystem::loadPlugin(IWingPlugin *p) { } } else { if (!item.toolboxs.isEmpty()) { - auto cat = _win->m_ribbon->addTab(item.displayName); + bool isSys = false; + RibbonTabContent *cat; + if (_win->m_ribbonMaps.contains(item.catagory)) { + isSys = true; + cat = _win->m_ribbonMaps.value(item.catagory); + } else { + cat = new RibbonTabContent; + } bool hasAdded = false; for (auto &group : item.toolboxs) { if (group.tools.isEmpty()) { @@ -340,10 +377,13 @@ bool PluginSystem::loadPlugin(IWingPlugin *p) { } hasAdded = true; } - if (hasAdded) { - _win->m_ribbonMaps[item.catagory] = cat; - } else { - _win->m_ribbon->removeTab(cat); + if (!isSys) { + if (hasAdded) { + _win->m_ribbon->addTab(cat, item.displayName); + _win->m_ribbonMaps[item.catagory] = cat; + } else { + delete cat; + } } } } @@ -430,20 +470,26 @@ bool PluginSystem::loadPlugin(IWingPlugin *p) { } registerFns(p); - connectInterface(p); + m_plgviewMap.insert(p, nullptr); } catch (const QString &error) { Logger::critical(error); + if (p_tr) { + p_tr->deleteLater(); + } return false; } return true; } void PluginSystem::connectInterface(IWingPlugin *plg) { - connectBaseInterface(plg); connectReaderInterface(plg); connectControllerInterface(plg); +} + +void PluginSystem::connectLoadingInterface(IWingPlugin *plg) { + connectBaseInterface(plg); connectUIInterface(plg); } @@ -472,6 +518,18 @@ void PluginSystem::connectBaseInterface(IWingPlugin *plg) { Logger::critical( packLogMessage(plg->metaObject()->className(), message)); }); + connect(plg, &IWingPlugin::createDialog, this, + [=](QWidget *w) -> QDialog * { + if (w) { + auto d = new FramelessDialogBase; + d->buildUpContent(w); + d->setWindowTitle(w->windowTitle()); + d->setWindowIcon(w->windowIcon()); + return d; + } else { + return nullptr; + } + }); } void PluginSystem::connectReaderInterface(IWingPlugin *plg) { @@ -1799,7 +1857,7 @@ void PluginSystem::LoadPlugin() { _angelplg = new WingAngelAPI; - auto ret = loadPlugin(_angelplg); + auto ret = loadPlugin(_angelplg, QDir()); Q_ASSERT(ret); Q_UNUSED(ret); @@ -1828,8 +1886,15 @@ void PluginSystem::LoadPlugin() { auto plgs = plugindir.entryInfoList(); Logger::info(tr("FoundPluginCount") + QString::number(plgs.count())); + QDir udir(Utilities::getAppDataPath()); + auto plgset = QStringLiteral("plgset"); + udir.mkdir(plgset); + if (!udir.cd(plgset)) { + throw CrashCode::PluginSetting; + } + for (auto &item : plgs) { - loadPlugin(item); + loadPlugin(item, udir); } Logger::info(tr("PluginLoadingFinished")); diff --git a/src/plugin/pluginsystem.h b/src/plugin/pluginsystem.h index 609160b..9bb55b9 100644 --- a/src/plugin/pluginsystem.h +++ b/src/plugin/pluginsystem.h @@ -101,7 +101,7 @@ public: const QList &plugins() const; const IWingPlugin *plugin(qsizetype index) const; - void loadPlugin(QFileInfo filename); + void loadPlugin(const QFileInfo &filename, const QDir &setdir); WingAngelAPI *angelApi() const; @@ -123,10 +123,13 @@ private: QString getScriptFnSig(const QString &fnName, const IWingPlugin::ScriptFnInfo &fninfo); + QString getPUID(IWingPlugin *p); + private: - bool loadPlugin(IWingPlugin *p); + bool loadPlugin(IWingPlugin *p, const QDir &setdir); void connectInterface(IWingPlugin *plg); + void connectLoadingInterface(IWingPlugin *plg); void connectBaseInterface(IWingPlugin *plg); void connectReaderInterface(IWingPlugin *plg); void connectControllerInterface(IWingPlugin *plg);