""" Model Team Tools - 主程序入口 应用程序主窗口的初始化和配置模块 使用 PySide6 构建的桌面应用程序框架 主要功能: - 初始化 Qt 主窗口 - 加载 Qt Material 样式表 - 配置菜单栏和状态栏 - 提供统一的日志记录接口 """ import os import sys import logging import core import traceback import importlib from pathlib import Path from typing import Optional from PySide6.QtCore import QProcess, QTimer from PySide6.QtGui import QIcon,QAction from PySide6.QtWidgets import QApplication, QMainWindow, QMenuBar, QStatusBar from qt_material import apply_stylesheet from main_ui import Ui_MainWindow # 获取模块日志记录器 logger = logging.getLogger(__name__) # 版本号常量 MAJOR_VER: int = 0 # 主版本号 MINOR_VER: int = 0 # 次版本号 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): """ 主窗口类 继承自 QMainWindow(Qt 主窗口基类)和 Ui_MainWindow(Qt Designer 生成的 UI 界面) 负责应用程序主窗口的初始化、样式配置和界面布局 """ def __init__(self) -> None: super().__init__() self.setupUi(self) self.plugin_registry = core.PluginRegistry() self.plugins = core.Plugins(self.plugin_registry) self.plugin_registry.plugins_loader_signal.connect(self.on_plugins_loader) self.plugin_registry.uninstall_completed_signal.connect(self.on_plugin_uninstalled) self.tool_actions: dict = {} self.plugin_widgets: dict = {} self.initUI() def initUI(self) -> None: """ 初始化用户界面组件 执行以下初始化操作: - 加载 Qt 样式表和自定义样式 - 设置窗口标题 - 配置菜单栏 - 配置状态栏 - 记录初始化日志 注意:此方法在主窗口创建后调用 """ self.load_stylesheet() self.setWindowTitle(APP_NAME) self.setWindowIcon(QIcon('resources/main.ico')) self.menubar: Optional[QMenuBar] = self.menuBar() self.statusbar: QStatusBar = self.statusBar() self.statusbar.showMessage(f"Version: {APP_VERSION}") self.action_tools.triggered.connect(self.on_tools) 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 on_tools(self) -> None: """打开工具箱窗口 创建并显示工具箱窗口实例。包含完整的错误处理机制, 确保任何异常都不会导致主程序崩溃。 异常处理: - 捕获所有异常并记录到日志 - 显示详细的错误堆栈信息 - 不影响主窗口的正常使用 日志记录: - 成功打开工具箱时记录 INFO 级别日志 - 打开失败时记录 ERROR 级别日志 """ try: 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(): action = QAction(name,self) self.toolBar.addAction(action) action.triggered.connect(lambda checked, action=action: self.on_action_triggered(action)) widget = plugins[name]['obj'] self.stackedWidget.addWidget(widget) self.tool_actions[name] = action self.plugin_widgets[name] = widget def on_action_triggered(self, action:QAction): print(action.text()) self.stackedWidget.setCurrentWidget(self.plugin_registry.plugins[action.text()]['obj']) def on_plugin_uninstalled(self, name: str) -> None: """卸载完成后清理主窗口工具栏与堆栈窗口中的对应项,并自动重启。 .pyd 已被进程加载,运行时无法删除(Windows 文件锁),因此重启进程: 新进程启动时会先执行待删清单,在 import 之前删除 .pyd 文件。 """ action = self.tool_actions.pop(name, None) if action is not None: self.toolBar.removeAction(action) action.deleteLater() widget = self.plugin_widgets.pop(name, None) if widget is not None: self.stackedWidget.removeWidget(widget) widget.deleteLater() self.statusbar.showMessage(f"{name} 已卸载,应用即将重启以完成清理...", 1500) QTimer.singleShot(1000, self._restart_app) def _restart_app(self) -> None: """启动新进程并退出当前进程。 sys.executable + sys.argv 同时兼容开发模式(python.exe main.py) 与打包模式(main.exe)。 """ work_dir = os.path.dirname(os.path.abspath(sys.argv[0])) # 【重要】添加防递归标记,防止新进程启动后再次触发重启逻辑 args = sys.argv[:] if '--restarting' not in args: args.append('--restarting') # 启动新进程 pid = QProcess.startDetached(sys.executable, args, work_dir) if pid: print(f"新进程启动成功 (PID: {pid}),准备退出当前进程...") # ✅ 关键修复:延迟 300ms 退出,确保新进程彻底“断奶” QTimer.singleShot(300, QApplication.quit) else: print("重启失败,请检查路径或权限") if __name__ == '__main__': app = QApplication(sys.argv) main: MainWindow = MainWindow() app.aboutToQuit.connect(main.plugin_registry.shutdown) main.show() sys.exit(app.exec())