diff --git a/core/__init__.py b/core/__init__.py index 8ce62a7..59c4d89 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -12,7 +12,8 @@ Version: 0.0.1 """ from .logging.log import FrameLog -from .tools.tools import ToolsWindow # FromTools → ToolsWindow +from .plugin.plugins import Plugins +from .plugin.plugin_registry import PluginRegistry -__all__ = ["FrameLog", "ToolsWindow"] +__all__ = ["FrameLog", "ToolsWindow","PluginRegistry"] diff --git a/core/plugin/plugin_registry.py b/core/plugin/plugin_registry.py new file mode 100644 index 0000000..55c0cea --- /dev/null +++ b/core/plugin/plugin_registry.py @@ -0,0 +1,127 @@ +import os +import sys +import time +import logging +import importlib +import threading + + +from pathlib import Path +from enum import Enum, auto +from PySide6.QtCore import QObject,Signal +from dataclasses import dataclass, field, fields + +LOCAL_PLUGINS_PATH = Path("./plugins") +REMOTE_PLUGINS_PATH = Path('Y:/SE/xufeifei/plugins') + +logger = logging.getLogger(__name__) + + +class Event(Enum): + Update = auto() + Install = auto() + Uninstall = auto() + +class PluginRegistry(QObject): + """插件注册中心。""" + plugins_loader_signal = Signal(dict) + update_plugins_card_signal = Signal(dict) + def __init__(self, parent = None) -> None: + super().__init__(parent) + + self.plugins = dict() + + self.start_discover_local(LOCAL_PLUGINS_PATH, self.plugins) + self.start_discover_remote(REMOTE_PLUGINS_PATH, self.plugins) + + def start_discover_local(self,plugins_dir: Path, plugins:dict): + self.thread_local = threading.Thread(target= self.discover_local_event,args=(plugins_dir, plugins,)) + self.thread_local.start() + + def discover_local_event(self, plugins_dir: Path, plugins:dict): + if not os.path.exists(plugins_dir): + os.mkdir(plugins_dir) + return + + sys.path.append(str(plugins_dir)) + for tool in Path(plugins_dir).glob("*.pyd"): + tool_name = tool.stem + try: + module = importlib.import_module(tool_name) + obj = module.create_plugin() + name = module.read_plugin_name() + version = module.read_plugin_version() + if name in plugins.keys(): + pass + else: + plugins[name] = {} + plugins[name]['obj'] = obj + plugins[name]['local version'] = version + except Exception as e: + logger.error(f"插件{tool_name}注册失败,{str(e)}") + sys.path.remove(str(LOCAL_PLUGINS_PATH)) + self.plugins_loader_signal.emit(plugins) + + + def start_discover_remote(self,plugins_dir: Path, plugins:dict): + self.thread_remote = threading.Thread(target= self.discover_remote_event,args=(plugins_dir, plugins,)) + self.thread_remote.start() + + def discover_remote_event(self, plugins_dir: Path, plugins:dict): + if not os.path.exists(plugins_dir): + logger.error("服务器链接错误!") + return + #读取服务器工具信息 + time.sleep(0.01) + sys.path.append(str(plugins_dir)) + for tool in Path(plugins_dir).glob("*.pyd"): + tool_name = tool.stem + try: + module = importlib.import_module(tool_name) + + name = module.read_plugin_name() + version = module.read_plugin_version() + description = module.read_plugin_description() + if name in plugins.keys(): + pass + else: + print("discover_remote_event") + plugins[name] = {} + plugins[name]["tool_name"] = tool_name + plugins[name]["local version"] = None + plugins[name]["local description"] = None + plugins[name]["remote version"] = version + plugins[name]["remote description"] = description + plugins[name]['remote path'] = plugins_dir + except Exception as e: + logger.error(f"服务器{tool_name}无法加载:{str(e)}") + sys.path.remove(str(plugins_dir)) + self.update_plugins_card_signal.emit(plugins) + + def start_plugins_event(self, event:Event, name:str): + if event == Event.Install: + local_path = str(LOCAL_PLUGINS_PATH) + '\\' + f"{self.plugins[name]['tool_name']}.pyd" + remote_path = str(REMOTE_PLUGINS_PATH) + '\\' + f"{self.plugins[name]['tool_name']}.pyd" + + self.sub_thread = threading.Thread(target= self.install_plugins_event,args=(local_path,remote_path,)) + self.sub_thread.start() + pass + + def install_plugins_event(self, local_path, remote_path): + try: + total_size = os.path.getsize(remote_path) + copied_size = 0 + + with open(remote_path, 'rb') as fsrc, open(local_path, 'wb') as fdst: + while True: + buf = fsrc.read(1024*1024) + if not buf: + break + fdst.write(buf) + copied_size += len(buf) + logger.info(f"安装进度: {copied_size/1024/1024:.2f} MB / {total_size/1024/1024:.2f} MB") + time.sleep(0.1) + logger.info(f"{local_path}安装成功!!!") + except Exception as e: + logger.error(f"{remote_path}安装失败{e.args}") + return \ No newline at end of file diff --git a/core/plugin/plugins.py b/core/plugin/plugins.py new file mode 100644 index 0000000..4913230 --- /dev/null +++ b/core/plugin/plugins.py @@ -0,0 +1,150 @@ +""" +工具箱模块 + +提供工具箱主窗口,包含各种实用工具的入口。 +使用 PySide6 构建的窗口框架。 + +主要功能: +- 工具箱主窗口界面 +- 窗口居中显示功能 +- 工具分类和导航 + +Author: Model Team +Version: 0.0.1 +""" +import os +import sys +import logging +import threading +import importlib + +from qt_material import apply_stylesheet + +from pathlib import Path +from .plugins_ui import Ui_FormPlugins +from PySide6.QtGui import QIcon + +from PySide6.QtWidgets import QWidget, QListWidgetItem +from PySide6.QtCore import QSize,Signal + +from .plugins_card import PluginsCard +from .plugin_registry import PluginRegistry + +logger = logging.getLogger(__name__) + +LOCAL_PLUGINS_PATH = Path("./plugins") +REMOTE_PLUGINS_PATH = Path('Y:/SE/xufeifei/plugins') + +class Plugins(QWidget, Ui_FormPlugins): + """工具箱主窗口类 + + 继承自 QWidget(Qt 窗口基类)和 Ui_FormTools(Qt Designer 生成的 UI 界面) + 负责显示工具箱的主界面,提供工具的分类和导航功能。 + + 窗口特性: + - 默认尺寸: 400x293 像素 + - 支持窗口居中显示 + - 可作为独立窗口或嵌入其他窗口使用 + + Attributes: + parent: 父窗口对象,默认为 None(顶级窗口) + """ + update_tool_card = Signal(dict) + + def __init__(self, plugin_registry:PluginRegistry, parent: QWidget = None) -> None: + """初始化工具箱窗口 + + 创建工具箱窗口实例,初始化 UI 界面。 + + Args: + parent: 父窗口对象,用于建立父子关系。 + 默认为 None,表示这是顶级窗口。 + """ + super().__init__(parent) + self.setupUi(self) + + self.plugin_registry = plugin_registry + + self.initUI() + self.load_stylesheet() + self.plugin_registry.update_plugins_card_signal.connect(self.on_update_plugins_card) + + def initUI(self) -> None: + """ + 初始化用户界面 + + 配置窗口标题、图标、大小和居中显示。 + """ + self.setWindowTitle("工具箱") + self.setWindowIcon(QIcon('resources/tools.ico')) + + # self.update_tool_card.connect(self.on_update_tool_card) + + def load_stylesheet(self) -> None: + """ + 加载 Qt 样式表和自定义样式文件 + + 使用 qt_material 库加载暗色主题,并应用自定义 QSS 样式表 + 失败时记录错误日志,但不影响应用继续运行(降级策略) + + 异常处理: + - FileNotFoundError: 样式文件不存在 + - RuntimeError: 主题加载失败 + - 其他异常: 记录警告但不中断应用 + """ + try: + apply_stylesheet(self, style=None,theme='dark_teal.xml', css_file='resources/my.qss') + except FileNotFoundError as e: + logger.warning(f"样式文件未找到,使用默认主题: {e}") + except RuntimeError as e: + logger.warning(f"主题加载失败,使用默认主题: {e}") + except Exception as e: + logger.warning(f"样式加载失败,使用默认主题: {e}") + + # def start_load_tools(self): + # self.sub_thread = threading.Thread(target= self.load_tool_list,args=(self.tools,)) + # self.sub_thread.start() + + # def load_tool_list(self, tools:dict): + # if not os.path.exists(REMOTE_PLUGINS_PATH): + # logger.error("服务器链接错误!") + # return + # #读取服务器工具信息 + # sys.path.append(str(REMOTE_PLUGINS_PATH)) + # for tool in Path(REMOTE_PLUGINS_PATH).glob("*.pyd"): + # tool_name = tool.stem + # try: + # module = importlib.import_module(tool_name) + + # name = module.read_tool_name() + # version = module.read_tool_version() + # description = module.read_tool_description() + # if name in tools.keys(): + # pass + # else: + # tools[name] = {} + # tools[name]["name"] = tool_name + # tools[name]["local version"] = None + # tools[name]["local description"] = None + # tools[name]["remote version"] = version + # tools[name]["remote description"] = description + # tools[name]['remote path'] = REMOTE_PLUGINS_PATH + # except Exception as e: + # logger.error(f"服务器{tool_name}无法加载:{str(e)}") + # sys.path.remove(str(REMOTE_PLUGINS_PATH)) + # self.update_tool_card.emit(tools) + + def on_update_plugins_card(self, plugins:dict): + # print(plugins) + for name in plugins.keys(): + item = QListWidgetItem(self.listWidget) + plugin_card = PluginsCard(name, plugins[name]) + plugin_card.send_event_signal.connect(self.plugin_registry.start_plugins_event) + + + item.setSizeHint(QSize(plugin_card.sizeHint().width(), 200)) + self.listWidget.addItem(item) + self.listWidget.setCurrentItem(item) + self.listWidget.setItemWidget(item, plugin_card) + pass + \ No newline at end of file diff --git a/core/tools/tools.ui b/core/plugin/plugins.ui similarity index 89% rename from core/tools/tools.ui rename to core/plugin/plugins.ui index a7d278c..baae2b0 100644 --- a/core/tools/tools.ui +++ b/core/plugin/plugins.ui @@ -1,7 +1,7 @@ - FormTools - + FormPlugins + 0 diff --git a/core/plugin/plugins_card.py b/core/plugin/plugins_card.py new file mode 100644 index 0000000..7a1f493 --- /dev/null +++ b/core/plugin/plugins_card.py @@ -0,0 +1,52 @@ +import os +import time +import logging +import threading + +from .plugin_registry import Event +from PySide6.QtWidgets import QFrame +from PySide6.QtCore import Signal +from .plugins_card_ui import Ui_FramePluginsCard + +logger = logging.getLogger(__name__) + +class PluginsCard(QFrame, Ui_FramePluginsCard): + """工具卡片类 + + 继承自 QFrame(Qt 帧基类)和 Ui_FrameToolCard(Qt Designer 生成的 UI 界面) + 负责显示工具的卡片界面,包含工具的图标、名称和描述。 + """ + send_event_signal = Signal(Event,str) + def __init__(self, name: str, info:dict, parent=None) -> None: + super().__init__(parent) + self.setupUi(self) + + self.name = name + self.info = info + + self.initUI() + + def initUI(self): + self.textBrowser.setReadOnly(True) + self.textBrowser.setMarkdown(self.to_card()) + if self.info["local version"] is None: + self.pushButtonUpdate.setEnabled(False) + self.pushButtonUninstall.setEnabled(False) + else: + self.pushButtonInstall.setEnabled(False) + if self.info['remote version'] <= self.info['local version']: + self.pushButtonUpdate.setEnabled(False) + self.pushButtonInstall.clicked.connect(self.on_intsall_event) + + def to_card(self): + card = f"# {self.name}\n\n" + card += f"**最新:** V{self.info['remote version']}\n\n" + if self.info['local version'] is None: + card += f"**当前:** 未安装!\n\n" + else: + card += f"**当前:** V{self.info['local version']}\n\n" + card += f"**描述:** {self.info['remote description']}" + return card + + def on_intsall_event(self): + self.send_event_signal.emit(Event.Install, self.name) \ No newline at end of file diff --git a/core/tools/tool_card.ui b/core/plugin/plugins_card.ui similarity index 64% rename from core/tools/tool_card.ui rename to core/plugin/plugins_card.ui index a58118b..384c3fc 100644 --- a/core/tools/tool_card.ui +++ b/core/plugin/plugins_card.ui @@ -1,48 +1,34 @@ - FrameToolCard - + FramePluginsCard + 0 0 - 491 - 64 + 357 + 120 + + + 0 + 0 + + Frame + + true + + + + - - - - - 0 - 0 - - - - Name - - - - - - - - 0 - 0 - - - - 安装 - - - - + @@ -55,7 +41,7 @@ - + @@ -68,21 +54,34 @@ + + + + + 0 + 0 + + + + 安装 + + + + + + + Qt::Orientation::Vertical + + + + 20 + 40 + + + + - - - - - 0 - 0 - - - - TextLabel - - - diff --git a/core/plugin/plugins_card_ui.py b/core/plugin/plugins_card_ui.py new file mode 100644 index 0000000..29fe18f --- /dev/null +++ b/core/plugin/plugins_card_ui.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +################################################################################ +## Form generated from reading UI file 'plugins_card.ui' +## +## Created by: Qt User Interface Compiler version 6.11.1 +## +## WARNING! All changes made in this file will be lost when recompiling UI file! +################################################################################ + +from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, + QMetaObject, QObject, QPoint, QRect, + QSize, QTime, QUrl, Qt) +from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, + QFont, QFontDatabase, QGradient, QIcon, + QImage, QKeySequence, QLinearGradient, QPainter, + QPalette, QPixmap, QRadialGradient, QTransform) +from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QPushButton, + QSizePolicy, QSpacerItem, QTextBrowser, QWidget) + +class Ui_FramePluginsCard(object): + def setupUi(self, FramePluginsCard): + if not FramePluginsCard.objectName(): + FramePluginsCard.setObjectName(u"FramePluginsCard") + FramePluginsCard.resize(357, 120) + sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(FramePluginsCard.sizePolicy().hasHeightForWidth()) + FramePluginsCard.setSizePolicy(sizePolicy) + FramePluginsCard.setAutoFillBackground(True) + self.gridLayout_2 = QGridLayout(FramePluginsCard) + self.gridLayout_2.setObjectName(u"gridLayout_2") + self.textBrowser = QTextBrowser(FramePluginsCard) + self.textBrowser.setObjectName(u"textBrowser") + + self.gridLayout_2.addWidget(self.textBrowser, 0, 0, 1, 1) + + self.gridLayout = QGridLayout() + self.gridLayout.setObjectName(u"gridLayout") + self.pushButtonUninstall = QPushButton(FramePluginsCard) + self.pushButtonUninstall.setObjectName(u"pushButtonUninstall") + sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) + sizePolicy1.setHorizontalStretch(0) + sizePolicy1.setVerticalStretch(0) + sizePolicy1.setHeightForWidth(self.pushButtonUninstall.sizePolicy().hasHeightForWidth()) + self.pushButtonUninstall.setSizePolicy(sizePolicy1) + + self.gridLayout.addWidget(self.pushButtonUninstall, 2, 0, 1, 1) + + self.pushButtonUpdate = QPushButton(FramePluginsCard) + self.pushButtonUpdate.setObjectName(u"pushButtonUpdate") + sizePolicy1.setHeightForWidth(self.pushButtonUpdate.sizePolicy().hasHeightForWidth()) + self.pushButtonUpdate.setSizePolicy(sizePolicy1) + + self.gridLayout.addWidget(self.pushButtonUpdate, 3, 0, 1, 1) + + self.pushButtonInstall = QPushButton(FramePluginsCard) + self.pushButtonInstall.setObjectName(u"pushButtonInstall") + sizePolicy1.setHeightForWidth(self.pushButtonInstall.sizePolicy().hasHeightForWidth()) + self.pushButtonInstall.setSizePolicy(sizePolicy1) + + self.gridLayout.addWidget(self.pushButtonInstall, 1, 0, 1, 1) + + self.verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Policy.Minimum, QSizePolicy.Policy.Expanding) + + self.gridLayout.addItem(self.verticalSpacer, 0, 0, 1, 1) + + + self.gridLayout_2.addLayout(self.gridLayout, 0, 1, 1, 1) + + + self.retranslateUi(FramePluginsCard) + + QMetaObject.connectSlotsByName(FramePluginsCard) + # setupUi + + def retranslateUi(self, FramePluginsCard): + FramePluginsCard.setWindowTitle(QCoreApplication.translate("FramePluginsCard", u"Frame", None)) + self.pushButtonUninstall.setText(QCoreApplication.translate("FramePluginsCard", u"\u5378\u8f7d", None)) + self.pushButtonUpdate.setText(QCoreApplication.translate("FramePluginsCard", u"\u66f4\u65b0", None)) + self.pushButtonInstall.setText(QCoreApplication.translate("FramePluginsCard", u"\u5b89\u88c5", None)) + # retranslateUi + diff --git a/core/tools/tools_ui.py b/core/plugin/plugins_ui.py similarity index 62% rename from core/tools/tools_ui.py rename to core/plugin/plugins_ui.py index 385a34d..c605cd0 100644 --- a/core/tools/tools_ui.py +++ b/core/plugin/plugins_ui.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- ################################################################################ -## Form generated from reading UI file 'tools.ui' +## Form generated from reading UI file 'plugins.ui' ## ## Created by: Qt User Interface Compiler version 6.11.1 ## @@ -18,30 +18,30 @@ from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, from PySide6.QtWidgets import (QApplication, QGridLayout, QListWidget, QListWidgetItem, QSizePolicy, QWidget) -class Ui_FormTools(object): - def setupUi(self, FormTools): - if not FormTools.objectName(): - FormTools.setObjectName(u"FormTools") - FormTools.resize(810, 423) +class Ui_FormPlugins(object): + def setupUi(self, FormPlugins): + if not FormPlugins.objectName(): + FormPlugins.setObjectName(u"FormPlugins") + FormPlugins.resize(810, 423) sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(FormTools.sizePolicy().hasHeightForWidth()) - FormTools.setSizePolicy(sizePolicy) - self.gridLayout = QGridLayout(FormTools) + sizePolicy.setHeightForWidth(FormPlugins.sizePolicy().hasHeightForWidth()) + FormPlugins.setSizePolicy(sizePolicy) + self.gridLayout = QGridLayout(FormPlugins) self.gridLayout.setObjectName(u"gridLayout") - self.listWidget = QListWidget(FormTools) + self.listWidget = QListWidget(FormPlugins) self.listWidget.setObjectName(u"listWidget") self.gridLayout.addWidget(self.listWidget, 0, 0, 1, 1) - self.retranslateUi(FormTools) + self.retranslateUi(FormPlugins) - QMetaObject.connectSlotsByName(FormTools) + QMetaObject.connectSlotsByName(FormPlugins) # setupUi - def retranslateUi(self, FormTools): - FormTools.setWindowTitle(QCoreApplication.translate("FormTools", u"Form", None)) + def retranslateUi(self, FormPlugins): + FormPlugins.setWindowTitle(QCoreApplication.translate("FormPlugins", u"Form", None)) # retranslateUi diff --git a/core/tools/tool_card.py b/core/tools/tool_card.py deleted file mode 100644 index 80ae81d..0000000 --- a/core/tools/tool_card.py +++ /dev/null @@ -1,15 +0,0 @@ - -from PySide6.QtWidgets import QFrame -from .tool_card_ui import Ui_FrameToolCard - - -class ToolCard(QFrame, Ui_FrameToolCard): - """工具卡片类 - - 继承自 QFrame(Qt 帧基类)和 Ui_FrameToolCard(Qt Designer 生成的 UI 界面) - 负责显示工具的卡片界面,包含工具的图标、名称和描述。 - """ - def __init__(self, parent = None) -> None: - super().__init__(parent) - self.setupUi(self) - diff --git a/core/tools/tool_card_ui.py b/core/tools/tool_card_ui.py deleted file mode 100644 index 95a85e3..0000000 --- a/core/tools/tool_card_ui.py +++ /dev/null @@ -1,91 +0,0 @@ -# -*- coding: utf-8 -*- - -################################################################################ -## Form generated from reading UI file 'tool_card.ui' -## -## Created by: Qt User Interface Compiler version 6.11.1 -## -## WARNING! All changes made in this file will be lost when recompiling UI file! -################################################################################ - -from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, - QMetaObject, QObject, QPoint, QRect, - QSize, QTime, QUrl, Qt) -from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, - QFont, QFontDatabase, QGradient, QIcon, - QImage, QKeySequence, QLinearGradient, QPainter, - QPalette, QPixmap, QRadialGradient, QTransform) -from PySide6.QtWidgets import (QApplication, QFrame, QGridLayout, QLabel, - QPushButton, QSizePolicy, QWidget) - -class Ui_FrameToolCard(object): - def setupUi(self, FrameToolCard): - if not FrameToolCard.objectName(): - FrameToolCard.setObjectName(u"FrameToolCard") - FrameToolCard.resize(491, 64) - self.gridLayout_2 = QGridLayout(FrameToolCard) - self.gridLayout_2.setObjectName(u"gridLayout_2") - self.gridLayout = QGridLayout() - self.gridLayout.setObjectName(u"gridLayout") - self.labelName = QLabel(FrameToolCard) - self.labelName.setObjectName(u"labelName") - sizePolicy = QSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.labelName.sizePolicy().hasHeightForWidth()) - self.labelName.setSizePolicy(sizePolicy) - - self.gridLayout.addWidget(self.labelName, 0, 0, 1, 1) - - self.pushButtonInstall = QPushButton(FrameToolCard) - self.pushButtonInstall.setObjectName(u"pushButtonInstall") - sizePolicy1 = QSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) - sizePolicy1.setHorizontalStretch(0) - sizePolicy1.setVerticalStretch(0) - sizePolicy1.setHeightForWidth(self.pushButtonInstall.sizePolicy().hasHeightForWidth()) - self.pushButtonInstall.setSizePolicy(sizePolicy1) - - self.gridLayout.addWidget(self.pushButtonInstall, 0, 1, 1, 1) - - self.pushButtonUninstall = QPushButton(FrameToolCard) - self.pushButtonUninstall.setObjectName(u"pushButtonUninstall") - sizePolicy1.setHeightForWidth(self.pushButtonUninstall.sizePolicy().hasHeightForWidth()) - self.pushButtonUninstall.setSizePolicy(sizePolicy1) - - self.gridLayout.addWidget(self.pushButtonUninstall, 0, 2, 1, 1) - - self.pushButtonUpdate = QPushButton(FrameToolCard) - self.pushButtonUpdate.setObjectName(u"pushButtonUpdate") - sizePolicy1.setHeightForWidth(self.pushButtonUpdate.sizePolicy().hasHeightForWidth()) - self.pushButtonUpdate.setSizePolicy(sizePolicy1) - - self.gridLayout.addWidget(self.pushButtonUpdate, 0, 3, 1, 1) - - - self.gridLayout_2.addLayout(self.gridLayout, 0, 0, 1, 1) - - self.labelInfo = QLabel(FrameToolCard) - self.labelInfo.setObjectName(u"labelInfo") - sizePolicy2 = QSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Expanding) - sizePolicy2.setHorizontalStretch(0) - sizePolicy2.setVerticalStretch(0) - sizePolicy2.setHeightForWidth(self.labelInfo.sizePolicy().hasHeightForWidth()) - self.labelInfo.setSizePolicy(sizePolicy2) - - self.gridLayout_2.addWidget(self.labelInfo, 1, 0, 1, 1) - - - self.retranslateUi(FrameToolCard) - - QMetaObject.connectSlotsByName(FrameToolCard) - # setupUi - - def retranslateUi(self, FrameToolCard): - FrameToolCard.setWindowTitle(QCoreApplication.translate("FrameToolCard", u"Frame", None)) - self.labelName.setText(QCoreApplication.translate("FrameToolCard", u"Name", None)) - self.pushButtonInstall.setText(QCoreApplication.translate("FrameToolCard", u"\u5b89\u88c5", None)) - self.pushButtonUninstall.setText(QCoreApplication.translate("FrameToolCard", u"\u5378\u8f7d", None)) - self.pushButtonUpdate.setText(QCoreApplication.translate("FrameToolCard", u"\u66f4\u65b0", None)) - self.labelInfo.setText(QCoreApplication.translate("FrameToolCard", u"TextLabel", None)) - # retranslateUi - diff --git a/core/tools/tools.py b/core/tools/tools.py deleted file mode 100644 index 412beda..0000000 --- a/core/tools/tools.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -工具箱模块 - -提供工具箱主窗口,包含各种实用工具的入口。 -使用 PySide6 构建的窗口框架。 - -主要功能: -- 工具箱主窗口界面 -- 窗口居中显示功能 -- 工具分类和导航 - -Author: Model Team -Version: 0.0.1 -""" -import os -import sys -import logging -import importlib.util - -from qt_material import apply_stylesheet - -from pathlib import Path -from .tools_ui import Ui_FormTools -from PySide6.QtGui import QIcon - -from PySide6.QtWidgets import QWidget, QListWidgetItem - -logger = logging.getLogger(__name__) - -PLUGINS_PATH = Path('Y:/SE/xufeifei/plugins') - -class ToolsWindow(QWidget, Ui_FormTools): - """工具箱主窗口类 - - 继承自 QWidget(Qt 窗口基类)和 Ui_FormTools(Qt Designer 生成的 UI 界面) - 负责显示工具箱的主界面,提供工具的分类和导航功能。 - - 窗口特性: - - 默认尺寸: 400x293 像素 - - 支持窗口居中显示 - - 可作为独立窗口或嵌入其他窗口使用 - - Attributes: - parent: 父窗口对象,默认为 None(顶级窗口) - """ - def __init__(self, tools:dict, parent: QWidget = None) -> None: - """初始化工具箱窗口 - - 创建工具箱窗口实例,初始化 UI 界面。 - - Args: - parent: 父窗口对象,用于建立父子关系。 - 默认为 None,表示这是顶级窗口。 - """ - super().__init__(parent) - self.setupUi(self) - - self.tools = tools - - self.initUI() - self.init_tool_list() - self.load_stylesheet() - - def initUI(self) -> None: - """ - 初始化用户界面 - - 配置窗口标题、图标、大小和居中显示。 - """ - self.setWindowTitle("工具箱") - self.setWindowIcon(QIcon('resources/tools.ico')) - - - def load_stylesheet(self) -> None: - """ - 加载 Qt 样式表和自定义样式文件 - - 使用 qt_material 库加载暗色主题,并应用自定义 QSS 样式表 - 失败时记录错误日志,但不影响应用继续运行(降级策略) - - 异常处理: - - FileNotFoundError: 样式文件不存在 - - RuntimeError: 主题加载失败 - - 其他异常: 记录警告但不中断应用 - """ - try: - apply_stylesheet(self, style=None,theme='dark_teal.xml', css_file='resources/my.qss') - except FileNotFoundError as e: - logger.warning(f"样式文件未找到,使用默认主题: {e}") - except RuntimeError as e: - logger.warning(f"主题加载失败,使用默认主题: {e}") - except Exception as e: - logger.warning(f"样式加载失败,使用默认主题: {e}") - - def init_tool_list(self): - if not os.path.exists(PLUGINS_PATH): - logger.error("服务器链接错误!") - return - sys.path.append(str(PLUGINS_PATH)) - - for tool in Path(PLUGINS_PATH).glob("*.pyd"): - tool_name = tool.stem - try: - module = importlib.import_module(tool_name) - - name = module.read_tool_name() - version = module.read_tool_version() - description = module.read_tool_description() - - if name in self.tools.keys(): - pass - else: - self.tools[name] = {} - self.tools[name]["version"] = version - self.tools[name]["description"] = description - - except Exception as e: - logger.error(f"{tool_name}无法加载:{str(e)}") - # if - sys.path.remove(str(PLUGINS_PATH)) - - def on_update_tool_card(self): - # for ke - # item = QListWidgetItem() - - pass - \ No newline at end of file diff --git a/main.py b/main.py index a66dbe1..caeae50 100644 --- a/main.py +++ b/main.py @@ -10,10 +10,12 @@ Model Team Tools - 主程序入口 - 配置菜单栏和状态栏 - 提供统一的日志记录接口 """ +import os import sys import logging import core import traceback +import importlib from pathlib import Path @@ -33,6 +35,7 @@ MICRO_VER: int = 1 # 修订版本号 APP_NAME: str = "Model Team Tools" APP_VERSION: str = f"{MAJOR_VER}.{MINOR_VER}.{MICRO_VER}" +LOCAL_PLUGINS_PATH = Path("./plugins") class MainWindow(QMainWindow, Ui_MainWindow): """ @@ -45,9 +48,12 @@ class MainWindow(QMainWindow, Ui_MainWindow): def __init__(self) -> None: super().__init__() self.setupUi(self) - self.initUI() - self.tools = dict() + self.plugin_registry = core.PluginRegistry() + self.plugins = core.Plugins(self.plugin_registry) + self.plugin_registry.plugins_loader_signal.connect(self.on_plugins_loader) + + self.initUI() def initUI(self) -> None: """ @@ -108,13 +114,15 @@ class MainWindow(QMainWindow, Ui_MainWindow): - 打开失败时记录 ERROR 级别日志 """ try: - self.tool_window = core.ToolsWindow(self.tools) - self.tool_window.show() - # logger.info("工具箱窗口已打开") + self.plugins.show() except Exception as e: logger.error(f"无法打开工具箱窗口: {e}") logger.error(traceback.format_exc()) + def on_plugins_loader(self, plugins:dict): + for name in plugins.keys(): + self.stackedWidget.addWidget(plugins[name]['obj']) + if __name__ == '__main__': app = QApplication(sys.argv) diff --git a/mil.json b/mil.json new file mode 100644 index 0000000..1da7587 --- /dev/null +++ b/mil.json @@ -0,0 +1,8 @@ +{ + "DataPath": "", + "FilePath": "", + "AddTimeEn": true, + "GeratePath": true, + "CurrProject": "", + "ItemConfigs": {} +} \ No newline at end of file diff --git a/resources/mil.svg b/resources/mil.svg new file mode 100644 index 0000000..c4986b6 --- /dev/null +++ b/resources/mil.svg @@ -0,0 +1 @@ + \ No newline at end of file