gui.main 源代码

"""
GUI 应用主入口
"""
import sys
from pathlib import Path
from PySide6.QtWidgets import QApplication, QMainWindow, QFileDialog, QMessageBox
from PySide6.QtCore import Qt

# 添加项目根目录到路径
sys.path.insert(0, str(Path(__file__).parent.parent))

from gui.ui.main_window import MainWindowUI
from gui.controllers.scenario_controller import ScenarioController
from gui.controllers.param_controller import ParamController
from gui.controllers.resource_controller import ResourceController
from gui.utils.log_handler import QTextEditLogger
from gui.utils.styles import MODERN_STYLE
from core.config_manager import AppConfig
from core.logger import get_logger
import logging

logger = get_logger()


[文档] class MainWindow(QMainWindow): """主窗口类""" def __init__(self): super().__init__() # 设置 UI self.ui = MainWindowUI() self.ui.setup_ui(self) # 加载配置 self.config = self._load_config() # 初始化控制器 self.scenario_controller = ScenarioController(self, self.config) self.param_controller = ParamController(self, self.config) self.resource_controller = ResourceController(self, self.config) # 配置修改标记 self.config_modified = False self._last_tab_index = 0 # 设置日志处理器 self._setup_log_handler() # 初始化 UI 状态 self._init_ui_state() # 连接信号和槽 self._connect_signals() logger.info("GUI 应用已启动") def _load_config(self) -> AppConfig: """加载配置文件""" config_path = Path("config.yaml") if not config_path.exists(): logger.warning("配置文件不存在,使用默认配置") return AppConfig.from_dict({}) return AppConfig.from_file(config_path) def _setup_log_handler(self): """设置日志处理器,将日志输出到GUI""" from core.logger import ColoredFormatter formatter = ColoredFormatter( '%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S' ) # 为每个任务创建独立的日志处理器 self.scenario_log_handler = QTextEditLogger() self.scenario_log_handler.setLevel(logging.INFO) self.scenario_log_handler.setFormatter(formatter) self.scenario_log_handler.log_signal.connect(self.ui.scenario_log.append) self.param_log_handler = QTextEditLogger() self.param_log_handler.setLevel(logging.INFO) self.param_log_handler.setFormatter(formatter) self.param_log_handler.log_signal.connect(self.ui.param_log.append) self.resource_log_handler = QTextEditLogger() self.resource_log_handler.setLevel(logging.INFO) self.resource_log_handler.setFormatter(formatter) self.resource_log_handler.log_signal.connect(self.ui.resource_log.append) def _init_ui_state(self): """初始化 UI 状态""" # 初始化引擎下拉框(从注册表获取) from core.engine_registry import EngineRegistry engines = EngineRegistry.list_engines() engine_names = [meta.display_name for meta in engines.values()] self.ui.param_engine_combo.clear() self.ui.param_engine_combo.addItems(engine_names) # 设置引擎类型(从engine_name查找display_name) engine_type = self.config.engine.engine_type engine_display_name = None for name, meta in engines.items(): if name == engine_type: engine_display_name = meta.display_name break if engine_display_name: self.ui.scenario_engine_combo.setCurrentText(engine_display_name) self.ui.param_engine_combo.setCurrentText(engine_display_name) self.ui.config_engine_combo.setCurrentText(engine_display_name) # 设置路径 self.ui.scenario_input_edit.setText(str(self.config.paths.input_dir)) self.ui.scenario_output_edit.setText(str(self.config.paths.output_dir)) # 设置参数映射页面的值 self.ui.param_config_dir_edit.setText(str(self.config.paths.param_config_dir)) self._update_param_file_labels() # 设置资源管理页面的值 self.ui.resource_project_edit.setText(str(self.config.paths.input_dir)) # 设置配置页面的值 self.ui.config_input_edit.setText(str(self.config.paths.input_dir)) self.ui.config_output_edit.setText(str(self.config.paths.output_dir)) self.ui.config_param_edit.setText(str(self.config.paths.param_config_dir)) self.ui.config_log_edit.setText(str(self.config.paths.log_dir)) self.ui.config_ignore_check.setChecked(self.config.processing.ignore_mode) self.ui.config_ignore_edit.setText(", ".join(self.config.processing.ignore_words)) def _connect_signals(self): """连接信号和槽""" # === 脚本生成页面 === # 忽略模式复选框 self.ui.scenario_ignore_check.stateChanged.connect( lambda state: self.ui.scenario_ignore_edit.setEnabled(state == Qt.CheckState.Checked.value) ) # 浏览按钮 self.ui.scenario_input_btn.clicked.connect(self._browse_input_dir) self.ui.scenario_output_btn.clicked.connect(self._browse_output_dir) # 按钮 self.ui.scenario_reset_btn.clicked.connect(self._on_reset_scenario) self.ui.scenario_generate_btn.clicked.connect(self._on_generate_scenario) # 控制器信号 self.scenario_controller.worker_progress.connect(self._on_scenario_progress) self.scenario_controller.worker_finished.connect(self._on_scenario_finished) # === 参数映射页面 === self.ui.param_engine_combo.currentTextChanged.connect(self._update_param_file_labels) self.ui.param_config_dir_edit.textChanged.connect(self._update_param_file_labels) self.ui.param_config_dir_btn.clicked.connect(self._browse_param_config_dir) self.ui.param_reset_btn.clicked.connect(self._on_reset_param) self.ui.param_update_btn.clicked.connect(self._on_update_param) # 控制器信号 self.param_controller.worker_progress.connect(self._on_param_progress) self.param_controller.worker_finished.connect(self._on_param_finished) # === 资源管理页面 === self.ui.resource_project_btn.clicked.connect(self._browse_resource_project) self.ui.resource_library_btn.clicked.connect(self._browse_resource_library) self.ui.resource_reset_btn.clicked.connect(self._on_reset_resource) self.ui.resource_validate_btn.clicked.connect(self._on_validate_resources) self.ui.resource_sync_btn.clicked.connect(self._on_sync_resources) # 控制器信号 self.resource_controller.validate_progress.connect(self._on_resource_validate_progress) self.resource_controller.validate_finished.connect(self._on_resource_validate_finished) self.resource_controller.sync_progress.connect(self._on_resource_sync_progress) self.resource_controller.sync_finished.connect(self._on_resource_sync_finished) # === 配置页面 === # 忽略模式复选框 self.ui.config_ignore_check.stateChanged.connect( lambda state: self.ui.config_ignore_edit.setEnabled(state == Qt.CheckState.Checked.value) ) self.ui.config_input_btn.clicked.connect(self._browse_config_input) self.ui.config_output_btn.clicked.connect(self._browse_config_output) self.ui.config_param_btn.clicked.connect(self._browse_config_param) self.ui.config_log_btn.clicked.connect(self._browse_config_log) self.ui.config_save_btn.clicked.connect(self._on_save_config) # 监听配置修改 self.ui.config_input_edit.textChanged.connect(self._mark_config_modified) self.ui.config_output_edit.textChanged.connect(self._mark_config_modified) self.ui.config_param_edit.textChanged.connect(self._mark_config_modified) self.ui.config_log_edit.textChanged.connect(self._mark_config_modified) self.ui.config_ignore_edit.textChanged.connect(self._mark_config_modified) self.ui.config_ignore_check.stateChanged.connect(self._mark_config_modified) self.ui.config_engine_combo.currentTextChanged.connect(self._mark_config_modified) # 监听选项卡切换 self.ui.tab_widget.currentChanged.connect(self._on_tab_changed) # === 脚本生成相关方法 === def _on_reset_scenario(self): """恢复脚本生成选项卡的默认配置""" from core.engine_registry import EngineRegistry engines = EngineRegistry.list_engines() for name, meta in engines.items(): if name == self.config.engine.engine_type: self.ui.scenario_engine_combo.setCurrentText(meta.display_name) break self.ui.scenario_input_edit.setText(str(self.config.paths.input_dir)) self.ui.scenario_output_edit.setText(str(self.config.paths.output_dir)) def _browse_input_dir(self): """浏览输入目录""" dir_path = QFileDialog.getExistingDirectory(self, "选择输入目录", self.ui.scenario_input_edit.text()) if dir_path: self.ui.scenario_input_edit.setText(dir_path) def _browse_output_dir(self): """浏览输出目录""" dir_path = QFileDialog.getExistingDirectory(self, "选择输出目录", self.ui.scenario_output_edit.text()) if dir_path: self.ui.scenario_output_edit.setText(dir_path) def _on_generate_scenario(self): """生成脚本""" from core.engine_registry import EngineRegistry from copy import deepcopy # 创建配置副本 temp_config = deepcopy(self.config) # 更新临时配置的引擎 engine_display_name = self.ui.scenario_engine_combo.currentText() engines = EngineRegistry.list_engines() for name, meta in engines.items(): if meta.display_name == engine_display_name: temp_config.engine = meta.config_class(engine_type=name) break temp_config.paths.input_dir = Path(self.ui.scenario_input_edit.text()) temp_config.paths.output_dir = Path(self.ui.scenario_output_edit.text()) # 清空日志 self.ui.scenario_log.clear() self.ui.scenario_progress.setValue(0) # 禁用按钮 self.ui.scenario_generate_btn.setEnabled(False) # 使用临时配置开始生成 self.scenario_controller.generate_scripts(temp_config) def _on_scenario_progress(self, message: str): """脚本生成进度更新""" self.ui.scenario_log.append(message) self.ui.status_bar.showMessage(message) def _on_scenario_finished(self, success: bool, message: str): """脚本生成完成""" self.ui.scenario_progress.setValue(100 if success else 0) self.ui.scenario_log.append(f"\n{'成功' if success else '失败'}: {message}") self.ui.scenario_generate_btn.setEnabled(True) # 显示消息框 if success: QMessageBox.information(self, "成功", message) else: QMessageBox.critical(self, "失败", message) # === 参数映射相关方法 === def _on_reset_param(self): """恢复参数映射选项卡的默认配置""" from core.engine_registry import EngineRegistry engines = EngineRegistry.list_engines() for name, meta in engines.items(): if name == self.config.engine.engine_type: self.ui.param_engine_combo.setCurrentText(meta.display_name) break self.ui.param_config_dir_edit.setText(str(self.config.paths.param_config_dir)) def _browse_param_config_dir(self): """浏览参数配置目录""" dir_path = QFileDialog.getExistingDirectory(self, "选择参数配置目录", self.ui.param_config_dir_edit.text()) if dir_path: self.ui.param_config_dir_edit.setText(dir_path) def _update_param_file_labels(self): """实时更新参数文件和差分文件的label""" from core.engine_registry import EngineRegistry # 获取当前引擎和参数配置目录 engine_display_name = self.ui.param_engine_combo.currentText() param_dir = self.ui.param_config_dir_edit.text() # 从注册表查找引擎名称 engines = EngineRegistry.list_engines() engine_name = None for name, meta in engines.items(): if meta.display_name == engine_display_name: engine_name = name break # 根据引擎名称确定参数文件名 if engine_name: param_file = f"param_data_{engine_name}.xlsx" else: param_file = "param_data.xlsx" # 更新label显示 self.ui.param_file_label.setText(f"{param_dir}/{param_file}") self.ui.param_varient_label.setText(f"{param_dir}/varient_data.xlsx") def _on_update_param(self): """更新参数映射""" from core.engine_registry import EngineRegistry from copy import deepcopy # 创建配置副本 temp_config = deepcopy(self.config) # 从UI更新临时配置 temp_config.paths.param_config_dir = Path(self.ui.param_config_dir_edit.text()) # 更新引擎配置(从display_name转换为engine_name) engine_display_name = self.ui.param_engine_combo.currentText() engines = EngineRegistry.list_engines() for name, meta in engines.items(): if meta.display_name == engine_display_name: temp_config.engine = meta.config_class(engine_type=name) break # 清空日志 self.ui.param_log.clear() self.ui.param_progress.setValue(0) # 禁用按钮 self.ui.param_update_btn.setEnabled(False) # 使用临时配置开始更新 self.param_controller.update_param_mappings(temp_config) def _on_param_progress(self, message: str): """参数映射进度更新""" self.ui.param_log.append(message) self.ui.status_bar.showMessage(message) def _on_param_finished(self, success: bool, message: str): """参数映射完成""" self.ui.param_progress.setValue(100 if success else 0) self.ui.param_log.append(f"\n{'成功' if success else '失败'}: {message}") self.ui.param_update_btn.setEnabled(True) # 显示消息框 if success: QMessageBox.information(self, "成功", message) else: QMessageBox.critical(self, "失败", message) # === 资源管理相关方法 === def _on_reset_resource(self): """恢复资源管理选项卡的默认配置""" self.ui.resource_project_edit.setText(str(self.config.paths.input_dir)) def _browse_resource_project(self): """浏览项目目录""" dir_path = QFileDialog.getExistingDirectory(self, "选择项目目录", self.ui.resource_project_edit.text()) if dir_path: self.ui.resource_project_edit.setText(dir_path) def _browse_resource_library(self): """浏览资源库目录""" dir_path = QFileDialog.getExistingDirectory(self, "选择资源库目录", self.ui.resource_library_edit.text()) if dir_path: self.ui.resource_library_edit.setText(dir_path) def _on_validate_resources(self): """验证资源""" from copy import deepcopy # 创建配置副本 temp_config = deepcopy(self.config) # 从UI更新临时配置 temp_config.paths.input_dir = Path(self.ui.resource_project_edit.text()) # 清空日志 self.ui.resource_log.clear() self.ui.resource_progress.setValue(0) # 禁用按钮 self.ui.resource_validate_btn.setEnabled(False) # 使用临时配置开始验证 self.resource_controller.validate_resources(temp_config) def _on_resource_validate_progress(self, message: str): """资源验证进度更新""" self.ui.resource_log.append(message) self.ui.status_bar.showMessage(message) def _on_resource_validate_finished(self, success: bool, message: str, missing_files: dict): """资源验证完成""" self.ui.resource_progress.setValue(100 if success else 0) self.ui.resource_log.append(f"\n{message}") self.ui.resource_validate_btn.setEnabled(True) # 验证成功后启用同步按钮 if success: self.ui.resource_sync_btn.setEnabled(True) self.ui.resource_log.append(f"\n验证完成,可以点击'同步资源'按钮进行资源同步") else: self.ui.resource_sync_btn.setEnabled(False) def _on_sync_resources(self): """同步资源""" # 检查资源库路径 library_path = self.ui.resource_library_edit.text() if not library_path: QMessageBox.warning(self, "警告", "请先设置资源库目录") return # 清空日志 self.ui.resource_log.clear() self.ui.resource_progress.setValue(0) # 禁用按钮 self.ui.resource_sync_btn.setEnabled(False) # 获取干跑模式状态 dry_run = self.ui.resource_dry_run_check.isChecked() # 开始同步 self.resource_controller.sync_resources(Path(library_path), dry_run) def _on_resource_sync_progress(self, message: str): """资源同步进度更新""" self.ui.resource_log.append(message) self.ui.status_bar.showMessage(message) def _on_resource_sync_finished(self, success: bool, message: str): """资源同步完成""" self.ui.resource_progress.setValue(100 if success else 0) self.ui.resource_log.append(f"\n{'成功' if success else '失败'}: {message}") self.ui.resource_sync_btn.setEnabled(True) # 显示消息框 if success: QMessageBox.information(self, "成功", message) else: QMessageBox.critical(self, "失败", message) # === 配置相关方法 === def _reload_config_tab_ui(self): """重新加载配置选项卡的UI""" self.ui.config_input_edit.setText(str(self.config.paths.input_dir)) self.ui.config_output_edit.setText(str(self.config.paths.output_dir)) self.ui.config_param_edit.setText(str(self.config.paths.param_config_dir)) self.ui.config_log_edit.setText(str(self.config.paths.log_dir)) self.ui.config_ignore_check.setChecked(self.config.processing.ignore_mode) self.ui.config_ignore_edit.setText(", ".join(self.config.processing.ignore_words)) from core.engine_registry import EngineRegistry engines = EngineRegistry.list_engines() for name, meta in engines.items(): if name == self.config.engine.engine_type: self.ui.config_engine_combo.setCurrentText(meta.display_name) break def _browse_config_input(self): """浏览配置输入目录""" dir_path = QFileDialog.getExistingDirectory(self, "选择输入目录", self.ui.config_input_edit.text()) if dir_path: self.ui.config_input_edit.setText(dir_path) def _browse_config_output(self): """浏览配置输出目录""" dir_path = QFileDialog.getExistingDirectory(self, "选择输出目录", self.ui.config_output_edit.text()) if dir_path: self.ui.config_output_edit.setText(dir_path) def _browse_config_param(self): """浏览参数配置目录""" dir_path = QFileDialog.getExistingDirectory(self, "选择参数配置目录", self.ui.config_param_edit.text()) if dir_path: self.ui.config_param_edit.setText(dir_path) def _browse_config_log(self): """浏览日志目录""" dir_path = QFileDialog.getExistingDirectory(self, "选择日志目录", self.ui.config_log_edit.text()) if dir_path: self.ui.config_log_edit.setText(dir_path) def _on_save_config(self): """保存配置""" try: import yaml from core.engine_registry import EngineRegistry # 从UI读取配置 engine_display_name = self.ui.config_engine_combo.currentText() ignore_words = [w.strip() for w in self.ui.config_ignore_edit.text().split(",")] # 从display_name转换为engine_name engines = EngineRegistry.list_engines() engine_name = None for name, meta in engines.items(): if meta.display_name == engine_display_name: engine_name = name break config_dict = { "paths": { "input_dir": self.ui.config_input_edit.text(), "output_dir": self.ui.config_output_edit.text(), "param_config_dir": self.ui.config_param_edit.text(), "log_dir": self.ui.config_log_edit.text(), }, "processing": { "ignore_mode": self.ui.config_ignore_check.isChecked(), "ignore_words": ignore_words, "batch_size": 100, "enable_progress_bar": True, }, "engine": { "engine_type": engine_name if engine_name else "renpy", } } # 保存到文件 with open("config.yaml", "w", encoding="utf-8") as f: yaml.dump(config_dict, f, allow_unicode=True, default_flow_style=False) # 重新加载配置 self.config = self._load_config() self._init_ui_state() QMessageBox.information(self, "成功", "配置已保存") self.config_modified = False except Exception as e: QMessageBox.critical(self, "失败", f"保存配置失败: {str(e)}") def _mark_config_modified(self): """标记配置已修改""" self.config_modified = True def _on_tab_changed(self, index): """选项卡切换时检查配置是否保存""" # 如果从配置选项卡切换到其他选项卡,且配置已修改 if self.config_modified and hasattr(self, '_last_tab_index') and self._last_tab_index == 3 and index != 3: # 先切回配置选项卡,阻止信号避免递归 self.ui.tab_widget.blockSignals(True) self.ui.tab_widget.setCurrentIndex(3) self.ui.tab_widget.blockSignals(False) # 弹出对话框 reply = QMessageBox.question( self, "未保存的配置", "当前配置未保存,是否保存?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No | QMessageBox.StandardButton.Cancel ) if reply == QMessageBox.StandardButton.Yes: self._on_save_config() # 保存后切换到目标选项卡 self.ui.tab_widget.blockSignals(True) self.ui.tab_widget.setCurrentIndex(index) self.ui.tab_widget.blockSignals(False) self._last_tab_index = index elif reply == QMessageBox.StandardButton.No: # 不保存,重新加载配置并切换 self.config_modified = False self._init_ui_state() self.ui.tab_widget.blockSignals(True) self.ui.tab_widget.setCurrentIndex(index) self.ui.tab_widget.blockSignals(False) self._last_tab_index = index # Cancel: 什么都不做,已经在配置选项卡了 return # 进入配置选项卡时,只重新加载配置选项卡的UI if index == 3: self._reload_config_tab_ui() self.config_modified = False # 记录当前选项卡索引 self._last_tab_index = index
[文档] def main(): """主函数""" app = QApplication(sys.argv) # 设置应用样式 app.setStyle("Fusion") app.setStyleSheet(MODERN_STYLE) # 创建并显示主窗口 window = MainWindow() window.show() sys.exit(app.exec())
if __name__ == "__main__": main()