refactor: 实现第二批 Manager(LogManager/ConfigManager/DialogService/TrainingModeManager)
- log_manager.py: 日志区+进度条+清空按钮封装,内部订阅 LogMessage/ProgressUpdate
- config_manager.py: 配置读写(new/load/save/get_current_config),懒加载安全(未加载面板返回 {})
- dialog_service.py: 纯展示弹窗封装(Pipeline状态/关于/AI设置)
- training_mode_manager.py: 训练模式切换,发布 TrainingModeChanged 事件
- water_quality_gui_v2.py: 725→605 行,菜单回调全部委托给 Manager,移除 _create_log_panel/_create_progress_panel/_on_log_message/_on_progress_update
This commit is contained in:
161
src/gui/core/config_manager.py
Normal file
161
src/gui/core/config_manager.py
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
配置管理器
|
||||||
|
|
||||||
|
接管主窗口中所有配置读写逻辑:
|
||||||
|
- new_config() 清空所有面板配置
|
||||||
|
- load_config(file_path) 从 JSON 文件加载配置并回填面板
|
||||||
|
- save_config(file_path) 将当前配置保存为 JSON 文件
|
||||||
|
- get_current_config() 遍历 PanelFactory 收集配置(懒加载安全)
|
||||||
|
|
||||||
|
懒加载兼容原则:
|
||||||
|
- get_current_config() 仅遍历已加载面板,未加载面板返回空字典 {}
|
||||||
|
- 绝不为了拿配置而强行唤醒/渲染所有 Panel
|
||||||
|
- 如需全量配置(如保存),调用方应先执行 panel_factory.preload_all()
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject
|
||||||
|
from PyQt5.QtWidgets import QMessageBox, QFileDialog
|
||||||
|
|
||||||
|
from src.gui.core.event_bus import global_event_bus
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigManager(QObject):
|
||||||
|
"""配置管理器。
|
||||||
|
|
||||||
|
用法::
|
||||||
|
|
||||||
|
cfg_mgr = ConfigManager(panel_factory, parent=self)
|
||||||
|
cfg_mgr.new_config() # 清空配置
|
||||||
|
cfg_mgr.load_config(path) # 加载 JSON
|
||||||
|
cfg_mgr.save_config(path) # 保存 JSON
|
||||||
|
config = cfg_mgr.get_current_config() # 收集当前配置
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, panel_factory, parent=None):
|
||||||
|
"""
|
||||||
|
Args:
|
||||||
|
panel_factory: PanelFactory 实例
|
||||||
|
parent: 父 QObject(用于弹窗定位)
|
||||||
|
"""
|
||||||
|
super().__init__(parent)
|
||||||
|
self._panel_factory = panel_factory
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
# 公开 API
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def new_config(self):
|
||||||
|
"""清空所有面板配置(需用户确认)。"""
|
||||||
|
reply = QMessageBox.question(
|
||||||
|
self.parent(), "新建配置", "是否清空当前配置?",
|
||||||
|
QMessageBox.Yes | QMessageBox.No
|
||||||
|
)
|
||||||
|
if reply != QMessageBox.Yes:
|
||||||
|
return
|
||||||
|
|
||||||
|
for panel in self._panel_factory.get_loaded_panels().values():
|
||||||
|
if hasattr(panel, 'clear_config'):
|
||||||
|
panel.clear_config()
|
||||||
|
|
||||||
|
global_event_bus.publish('LogMessage', {
|
||||||
|
'message': '已清空配置',
|
||||||
|
'level': 'info',
|
||||||
|
})
|
||||||
|
|
||||||
|
def load_config(self, file_path: str = None):
|
||||||
|
"""从 JSON 文件加载配置并回填面板。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: JSON 文件路径。若为 None,弹出文件选择对话框。
|
||||||
|
"""
|
||||||
|
if file_path is None:
|
||||||
|
file_path, _ = QFileDialog.getOpenFileName(
|
||||||
|
self.parent(), "加载配置", "",
|
||||||
|
"JSON Files (*.json);;All Files (*.*)"
|
||||||
|
)
|
||||||
|
if not file_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
|
config = json.load(f)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(
|
||||||
|
self.parent(), "加载失败",
|
||||||
|
f"无法读取配置文件:\n{file_path}\n\n错误: {e}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# 回填已加载面板
|
||||||
|
loaded_count = 0
|
||||||
|
for step_id, panel in self._panel_factory.get_loaded_panels().items():
|
||||||
|
if step_id in config and hasattr(panel, 'set_config'):
|
||||||
|
try:
|
||||||
|
panel.set_config(config[step_id])
|
||||||
|
loaded_count += 1
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
global_event_bus.publish('LogMessage', {
|
||||||
|
'message': f'已加载配置: {file_path}(回填 {loaded_count} 个面板)',
|
||||||
|
'level': 'info',
|
||||||
|
})
|
||||||
|
|
||||||
|
def save_config(self, file_path: str = None):
|
||||||
|
"""将当前配置保存为 JSON 文件。
|
||||||
|
|
||||||
|
注意:保存前会强制加载所有面板(preload_all),确保配置完整。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path: 目标 JSON 文件路径。若为 None,弹出保存对话框。
|
||||||
|
"""
|
||||||
|
if file_path is None:
|
||||||
|
file_path, _ = QFileDialog.getSaveFileName(
|
||||||
|
self.parent(), "保存配置", "config.json",
|
||||||
|
"JSON Files (*.json);;All Files (*.*)"
|
||||||
|
)
|
||||||
|
if not file_path:
|
||||||
|
return
|
||||||
|
|
||||||
|
# 保存前强制加载所有面板,确保配置完整
|
||||||
|
self._panel_factory.preload_all()
|
||||||
|
config = self.get_current_config()
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(config, f, indent=2, ensure_ascii=False)
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(
|
||||||
|
self.parent(), "保存失败",
|
||||||
|
f"无法保存配置文件:\n{file_path}\n\n错误: {e}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
global_event_bus.publish('LogMessage', {
|
||||||
|
'message': f'已保存配置: {file_path}',
|
||||||
|
'level': 'info',
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_current_config(self) -> Dict[str, dict]:
|
||||||
|
"""收集当前所有步骤的配置。
|
||||||
|
|
||||||
|
懒加载安全:仅遍历已加载面板,未加载面板返回空字典 {}。
|
||||||
|
绝不为了拿配置而强行唤醒/渲染所有 Panel。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
{step_id: panel_config_dict}
|
||||||
|
"""
|
||||||
|
config = {}
|
||||||
|
for step_id, panel in self._panel_factory.get_loaded_panels().items():
|
||||||
|
if hasattr(panel, 'get_config'):
|
||||||
|
try:
|
||||||
|
config[step_id] = panel.get_config()
|
||||||
|
except Exception:
|
||||||
|
config[step_id] = {}
|
||||||
|
return config
|
||||||
67
src/gui/core/dialog_service.py
Normal file
67
src/gui/core/dialog_service.py
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
对话框服务
|
||||||
|
|
||||||
|
封装纯展示类弹窗,从主窗口中彻底剥离 UI 对话框逻辑。
|
||||||
|
|
||||||
|
职责:
|
||||||
|
- show_pipeline_status() 显示 Pipeline 模块加载状态
|
||||||
|
- show_about() 显示"关于"对话框
|
||||||
|
- show_ai_settings() 显示 AI 引擎配置对话框
|
||||||
|
"""
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject
|
||||||
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
|
|
||||||
|
class DialogService(QObject):
|
||||||
|
"""对话框服务。
|
||||||
|
|
||||||
|
用法::
|
||||||
|
|
||||||
|
dlg_svc = DialogService(parent=self)
|
||||||
|
dlg_svc.show_about()
|
||||||
|
dlg_svc.show_pipeline_status()
|
||||||
|
dlg_svc.show_ai_settings()
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
# 公开 API
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def show_pipeline_status(self):
|
||||||
|
"""显示 Pipeline 模块加载状态。"""
|
||||||
|
from src.gui.core.worker_thread import PIPELINE_AVAILABLE, PIPELINE_ERROR_INFO
|
||||||
|
|
||||||
|
if PIPELINE_AVAILABLE:
|
||||||
|
QMessageBox.information(
|
||||||
|
self.parent(), "Pipeline状态",
|
||||||
|
"Pipeline模块: 正常加载"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
detail = "\n".join(PIPELINE_ERROR_INFO)
|
||||||
|
QMessageBox.warning(
|
||||||
|
self.parent(), "Pipeline状态",
|
||||||
|
f"Pipeline模块: 加载失败\n\n{detail}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_about(self):
|
||||||
|
"""显示"关于"对话框。"""
|
||||||
|
QMessageBox.about(
|
||||||
|
self.parent(), "关于",
|
||||||
|
"MegaCube-Water Quality V1.2\n\n"
|
||||||
|
"一个完整的水质参数反演工作流程工具\n\n"
|
||||||
|
"公司:北京依锐思遥感技术有限公司\n"
|
||||||
|
"地址:北京市海淀区清河安宁庄东路18号5号楼二层205\n"
|
||||||
|
"电话:010-51292601\n"
|
||||||
|
"邮箱:hanshanlong@iris-rs.cn"
|
||||||
|
)
|
||||||
|
|
||||||
|
def show_ai_settings(self):
|
||||||
|
"""显示 AI 引擎配置对话框。"""
|
||||||
|
from src.gui.dialogs import AISettingsDialog
|
||||||
|
AISettingsDialog(self.parent()).exec_()
|
||||||
187
src/gui/core/log_manager.py
Normal file
187
src/gui/core/log_manager.py
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
日志与进度管理器
|
||||||
|
|
||||||
|
将主窗口中的日志区(QTextEdit)、进度条(QProgressBar)和"清空日志"按钮
|
||||||
|
的 UI 创建与控制逻辑完全封装。
|
||||||
|
|
||||||
|
职责:
|
||||||
|
- create_log_panel() → 返回组装好的 QWidget(日志 + 进度条)
|
||||||
|
- 内部订阅 LogMessage / ProgressUpdate 事件,自动更新 UI
|
||||||
|
- 主窗口无需再关心日志/进度的状态同步
|
||||||
|
|
||||||
|
订阅的事件:
|
||||||
|
LogMessage → {message, level} 写入日志区
|
||||||
|
ProgressUpdate → {percentage, message} 更新进度条
|
||||||
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject
|
||||||
|
from PyQt5.QtGui import QTextCursor
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
|
QGroupBox, QTextEdit, QProgressBar, QPushButton,
|
||||||
|
)
|
||||||
|
|
||||||
|
from src.gui.core.event_bus import global_event_bus
|
||||||
|
|
||||||
|
|
||||||
|
class LogManager(QObject):
|
||||||
|
"""日志与进度管理器。
|
||||||
|
|
||||||
|
用法::
|
||||||
|
|
||||||
|
log_mgr = LogManager(parent=self)
|
||||||
|
log_panel = log_mgr.create_log_panel()
|
||||||
|
layout.addWidget(log_panel)
|
||||||
|
# 之后所有日志/进度更新由 EventBus 自动驱动,无需手动操作
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._log_text: QTextEdit = None
|
||||||
|
self._progress_bar: QProgressBar = None
|
||||||
|
|
||||||
|
# 订阅事件
|
||||||
|
global_event_bus.subscribe('LogMessage', self._on_log_message)
|
||||||
|
global_event_bus.subscribe('ProgressUpdate', self._on_progress_update)
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
# 公开 API
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def create_log_panel(self) -> QWidget:
|
||||||
|
"""创建并返回日志+进度面板的组装 Widget。
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
QWidget: 包含日志区(QGroupBox)和进度条(QGroupBox)的垂直布局容器
|
||||||
|
"""
|
||||||
|
from src.gui.styles import ModernStylesheet
|
||||||
|
|
||||||
|
container = QWidget()
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
layout.setSpacing(10)
|
||||||
|
|
||||||
|
# ── 日志区 ──
|
||||||
|
log_group = QGroupBox("执行日志")
|
||||||
|
log_group.setStyleSheet(f"""
|
||||||
|
QGroupBox {{
|
||||||
|
background-color: {ModernStylesheet.COLORS['panel_bg']};
|
||||||
|
border: 1px solid {ModernStylesheet.COLORS['border_light']};
|
||||||
|
border-radius: 5px; margin-top: 8px; padding-top: 15px;
|
||||||
|
padding-left: 9px; padding-right: 9px; padding-bottom: 9px;
|
||||||
|
}}
|
||||||
|
QGroupBox::title {{
|
||||||
|
subcontrol-origin: margin; subcontrol-position: top left;
|
||||||
|
padding: 0 5px; font-weight: bold;
|
||||||
|
color: {ModernStylesheet.COLORS['text_primary']};
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
log_layout = QVBoxLayout()
|
||||||
|
log_layout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
|
||||||
|
self._log_text = QTextEdit()
|
||||||
|
self._log_text.setReadOnly(True)
|
||||||
|
self._log_text.setMaximumHeight(200)
|
||||||
|
self._log_text.setStyleSheet(f"""
|
||||||
|
QTextEdit {{
|
||||||
|
background-color: {ModernStylesheet.COLORS['panel_bg']};
|
||||||
|
color: {ModernStylesheet.COLORS['text_primary']};
|
||||||
|
border: 1px solid {ModernStylesheet.COLORS['border']};
|
||||||
|
border-radius: 4px; padding: 5px;
|
||||||
|
font-family: 'Courier New', monospace; font-size: 10px;
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
log_layout.addWidget(self._log_text)
|
||||||
|
|
||||||
|
clear_btn = QPushButton("清空日志")
|
||||||
|
clear_btn.setMaximumWidth(100)
|
||||||
|
clear_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('normal'))
|
||||||
|
clear_btn.clicked.connect(self.clear_log)
|
||||||
|
|
||||||
|
btn_row = QHBoxLayout()
|
||||||
|
btn_row.addWidget(clear_btn)
|
||||||
|
btn_row.addStretch()
|
||||||
|
log_layout.addLayout(btn_row)
|
||||||
|
|
||||||
|
log_group.setLayout(log_layout)
|
||||||
|
layout.addWidget(log_group, 1)
|
||||||
|
|
||||||
|
# ── 进度条 ──
|
||||||
|
progress_group = QGroupBox("执行进度")
|
||||||
|
progress_group.setStyleSheet(f"""
|
||||||
|
QGroupBox {{
|
||||||
|
background-color: {ModernStylesheet.COLORS['panel_bg']};
|
||||||
|
border: 1px solid {ModernStylesheet.COLORS['border_light']};
|
||||||
|
border-radius: 5px; margin-top: 8px; padding-top: 10px;
|
||||||
|
padding-left: 9px; padding-right: 9px; padding-bottom: 9px;
|
||||||
|
}}
|
||||||
|
QGroupBox::title {{
|
||||||
|
subcontrol-origin: margin; subcontrol-position: top left;
|
||||||
|
padding: 0 5px; font-weight: bold;
|
||||||
|
color: {ModernStylesheet.COLORS['text_primary']};
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
progress_layout = QVBoxLayout()
|
||||||
|
progress_layout.setContentsMargins(5, 5, 5, 5)
|
||||||
|
|
||||||
|
self._progress_bar = QProgressBar()
|
||||||
|
self._progress_bar.setValue(0)
|
||||||
|
self._progress_bar.setStyleSheet(f"""
|
||||||
|
QProgressBar {{
|
||||||
|
background-color: {ModernStylesheet.COLORS['panel_bg']};
|
||||||
|
border: 1px solid {ModernStylesheet.COLORS['border']};
|
||||||
|
border-radius: 4px; padding: 2px; text-align: center; height: 20px;
|
||||||
|
}}
|
||||||
|
QProgressBar::chunk {{
|
||||||
|
background-color: {ModernStylesheet.COLORS['success']}; border-radius: 3px;
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
progress_layout.addWidget(self._progress_bar)
|
||||||
|
progress_group.setLayout(progress_layout)
|
||||||
|
layout.addWidget(progress_group, 0)
|
||||||
|
|
||||||
|
container.setLayout(layout)
|
||||||
|
return container
|
||||||
|
|
||||||
|
def clear_log(self):
|
||||||
|
"""清空日志区。"""
|
||||||
|
if self._log_text is not None:
|
||||||
|
self._log_text.clear()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def progress_bar(self) -> QProgressBar:
|
||||||
|
return self._progress_bar
|
||||||
|
|
||||||
|
@property
|
||||||
|
def log_text(self) -> QTextEdit:
|
||||||
|
return self._log_text
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
# EventBus 订阅回调
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
def _on_log_message(self, data: dict):
|
||||||
|
"""LogMessage 事件回调:写入日志区。"""
|
||||||
|
if self._log_text is None:
|
||||||
|
return
|
||||||
|
message = data.get('message', '')
|
||||||
|
level = data.get('level', 'info')
|
||||||
|
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
color_map = {'error': 'red', 'warning': 'orange'}
|
||||||
|
color = color_map.get(level, 'black')
|
||||||
|
formatted = f'<span style="color: {color};">[{timestamp}] {message}</span>'
|
||||||
|
self._log_text.append(formatted)
|
||||||
|
cursor = self._log_text.textCursor()
|
||||||
|
cursor.movePosition(QTextCursor.End)
|
||||||
|
self._log_text.setTextCursor(cursor)
|
||||||
|
|
||||||
|
def _on_progress_update(self, data: dict):
|
||||||
|
"""ProgressUpdate 事件回调:更新进度条。"""
|
||||||
|
if self._progress_bar is None:
|
||||||
|
return
|
||||||
|
percentage = data.get('percentage', 0)
|
||||||
|
self._progress_bar.setValue(percentage)
|
||||||
68
src/gui/core/training_mode_manager.py
Normal file
68
src/gui/core/training_mode_manager.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
训练模式管理器
|
||||||
|
|
||||||
|
管理"有训练数据模式"的勾选状态。
|
||||||
|
切换状态时通过 global_event_bus 发布 TrainingModeChanged 事件,
|
||||||
|
让需要响应的面板(如机器学习训练面板)自行订阅并刷新。
|
||||||
|
|
||||||
|
发布的事件:
|
||||||
|
TrainingModeChanged → {training_mode: bool} 面板订阅:根据模式启用/禁用控件
|
||||||
|
"""
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject
|
||||||
|
|
||||||
|
from src.gui.core.event_bus import global_event_bus
|
||||||
|
|
||||||
|
|
||||||
|
class TrainingModeManager(QObject):
|
||||||
|
"""训练模式管理器。
|
||||||
|
|
||||||
|
用法::
|
||||||
|
|
||||||
|
mode_mgr = TrainingModeManager(parent=self)
|
||||||
|
mode_mgr.toggle(True) # 切换到有训练数据模式
|
||||||
|
mode_mgr.toggle(False) # 切换到无训练数据模式
|
||||||
|
is_training = mode_mgr.is_training_mode
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._training_mode: bool = True # 默认:有训练数据
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
# 公开 API
|
||||||
|
# ═══════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_training_mode(self) -> bool:
|
||||||
|
return self._training_mode
|
||||||
|
|
||||||
|
def toggle(self, checked: bool):
|
||||||
|
"""切换训练模式。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
checked: True=有训练数据模式, False=无训练数据模式
|
||||||
|
"""
|
||||||
|
self._training_mode = checked
|
||||||
|
|
||||||
|
global_event_bus.publish('LogMessage', {
|
||||||
|
'message': f'切换到{"有训练数据" if checked else "无训练数据"}模式',
|
||||||
|
'level': 'info',
|
||||||
|
})
|
||||||
|
|
||||||
|
global_event_bus.publish('TrainingModeChanged', {
|
||||||
|
'training_mode': checked,
|
||||||
|
})
|
||||||
|
|
||||||
|
def get_action_text(self, checked: bool) -> str:
|
||||||
|
"""返回菜单 Action 的显示文本。
|
||||||
|
|
||||||
|
Args:
|
||||||
|
checked: 当前勾选状态
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
"有训练数据模式" 或 "无训练数据模式"
|
||||||
|
"""
|
||||||
|
return "有训练数据模式" if checked else "无训练数据模式"
|
||||||
@ -10,12 +10,10 @@ WaterQualityGUI 只负责窗口框架(标题栏、菜单栏、状态栏、QTab
|
|||||||
- PanelFactory → 面板懒加载与生命周期
|
- PanelFactory → 面板懒加载与生命周期
|
||||||
- PipelineExecutor → Pipeline 执行/停止/回调(通过 EventBus 发布状态)
|
- PipelineExecutor → Pipeline 执行/停止/回调(通过 EventBus 发布状态)
|
||||||
- WorkspaceInitializer → 工作目录选择 + 自动回填(通过 EventBus 广播)
|
- WorkspaceInitializer → 工作目录选择 + 自动回填(通过 EventBus 广播)
|
||||||
|
- LogManager → 日志区 + 进度条(内部订阅 LogMessage/ProgressUpdate)
|
||||||
待实现的 Manager(下一批):
|
- ConfigManager → 配置读写(new/load/save/get_current_config)
|
||||||
- ConfigManager → 配置读写
|
- DialogService → 纯展示类弹窗(Pipeline状态/关于/AI设置)
|
||||||
- LogManager → 日志 + 进度条
|
- TrainingModeManager → 训练模式切换(发布 TrainingModeChanged 事件)
|
||||||
- TrainingModeManager → 训练模式切换
|
|
||||||
- DialogService → 各类对话框
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import osgeo # noqa: F401
|
import osgeo # noqa: F401
|
||||||
@ -118,6 +116,10 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
from src.gui.core.panel_registry import PANEL_REGISTRY
|
from src.gui.core.panel_registry import PANEL_REGISTRY
|
||||||
from src.gui.core.workspace_initializer import WorkspaceInitializer
|
from src.gui.core.workspace_initializer import WorkspaceInitializer
|
||||||
from src.gui.core.pipeline_executor import PipelineExecutor
|
from src.gui.core.pipeline_executor import PipelineExecutor
|
||||||
|
from src.gui.core.log_manager import LogManager
|
||||||
|
from src.gui.core.config_manager import ConfigManager
|
||||||
|
from src.gui.core.dialog_service import DialogService
|
||||||
|
from src.gui.core.training_mode_manager import TrainingModeManager
|
||||||
from src.gui.core.event_bus import global_event_bus
|
from src.gui.core.event_bus import global_event_bus
|
||||||
|
|
||||||
self._panel_factory = PanelFactory(
|
self._panel_factory = PanelFactory(
|
||||||
@ -137,6 +139,17 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
parent=self,
|
parent=self,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._log_manager = LogManager(parent=self)
|
||||||
|
|
||||||
|
self._config_manager = ConfigManager(
|
||||||
|
panel_factory=self._panel_factory,
|
||||||
|
parent=self,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._dialog_service = DialogService(parent=self)
|
||||||
|
|
||||||
|
self._training_mode_manager = TrainingModeManager(parent=self)
|
||||||
|
|
||||||
self._event_bus = global_event_bus
|
self._event_bus = global_event_bus
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
@ -283,8 +296,7 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
self._tab_widget.currentChanged.connect(self._on_tab_changed)
|
self._tab_widget.currentChanged.connect(self._on_tab_changed)
|
||||||
right_layout.addWidget(self._tab_widget, 3)
|
right_layout.addWidget(self._tab_widget, 3)
|
||||||
|
|
||||||
right_layout.addWidget(self._create_log_panel(), 1)
|
right_layout.addWidget(self._log_manager.create_log_panel(), 1)
|
||||||
right_layout.addWidget(self._create_progress_panel(), 0)
|
|
||||||
|
|
||||||
right_widget.setLayout(right_layout)
|
right_widget.setLayout(right_layout)
|
||||||
main_layout.addWidget(right_widget, 4)
|
main_layout.addWidget(right_widget, 4)
|
||||||
@ -365,89 +377,6 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
)
|
)
|
||||||
return nav_widget
|
return nav_widget
|
||||||
|
|
||||||
def _create_log_panel(self):
|
|
||||||
from src.gui.styles import ModernStylesheet
|
|
||||||
|
|
||||||
log_group = QGroupBox("执行日志")
|
|
||||||
log_group.setStyleSheet(f"""
|
|
||||||
QGroupBox {{
|
|
||||||
background-color: {ModernStylesheet.COLORS['panel_bg']};
|
|
||||||
border: 1px solid {ModernStylesheet.COLORS['border_light']};
|
|
||||||
border-radius: 5px; margin-top: 8px; padding-top: 15px;
|
|
||||||
padding-left: 9px; padding-right: 9px; padding-bottom: 9px;
|
|
||||||
}}
|
|
||||||
QGroupBox::title {{
|
|
||||||
subcontrol-origin: margin; subcontrol-position: top left;
|
|
||||||
padding: 0 5px; font-weight: bold;
|
|
||||||
color: {ModernStylesheet.COLORS['text_primary']};
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
log_layout = QVBoxLayout()
|
|
||||||
log_layout.setContentsMargins(5, 5, 5, 5)
|
|
||||||
|
|
||||||
self._log_text = QTextEdit()
|
|
||||||
self._log_text.setReadOnly(True)
|
|
||||||
self._log_text.setMaximumHeight(200)
|
|
||||||
self._log_text.setStyleSheet(f"""
|
|
||||||
QTextEdit {{
|
|
||||||
background-color: {ModernStylesheet.COLORS['panel_bg']};
|
|
||||||
color: {ModernStylesheet.COLORS['text_primary']};
|
|
||||||
border: 1px solid {ModernStylesheet.COLORS['border']};
|
|
||||||
border-radius: 4px; padding: 5px;
|
|
||||||
font-family: 'Courier New', monospace; font-size: 10px;
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
log_layout.addWidget(self._log_text)
|
|
||||||
|
|
||||||
clear_btn = QPushButton("清空日志")
|
|
||||||
clear_btn.setMaximumWidth(100)
|
|
||||||
clear_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('normal'))
|
|
||||||
clear_btn.clicked.connect(self._log_text.clear)
|
|
||||||
|
|
||||||
btn_row = QHBoxLayout()
|
|
||||||
btn_row.addWidget(clear_btn)
|
|
||||||
btn_row.addStretch()
|
|
||||||
log_layout.addLayout(btn_row)
|
|
||||||
|
|
||||||
log_group.setLayout(log_layout)
|
|
||||||
return log_group
|
|
||||||
|
|
||||||
def _create_progress_panel(self):
|
|
||||||
from src.gui.styles import ModernStylesheet
|
|
||||||
|
|
||||||
progress_group = QGroupBox("执行进度")
|
|
||||||
progress_group.setStyleSheet(f"""
|
|
||||||
QGroupBox {{
|
|
||||||
background-color: {ModernStylesheet.COLORS['panel_bg']};
|
|
||||||
border: 1px solid {ModernStylesheet.COLORS['border_light']};
|
|
||||||
border-radius: 5px; margin-top: 8px; padding-top: 10px;
|
|
||||||
padding-left: 9px; padding-right: 9px; padding-bottom: 9px;
|
|
||||||
}}
|
|
||||||
QGroupBox::title {{
|
|
||||||
subcontrol-origin: margin; subcontrol-position: top left;
|
|
||||||
padding: 0 5px; font-weight: bold;
|
|
||||||
color: {ModernStylesheet.COLORS['text_primary']};
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
progress_layout = QVBoxLayout()
|
|
||||||
progress_layout.setContentsMargins(5, 5, 5, 5)
|
|
||||||
|
|
||||||
self._progress_bar = QProgressBar()
|
|
||||||
self._progress_bar.setValue(0)
|
|
||||||
self._progress_bar.setStyleSheet(f"""
|
|
||||||
QProgressBar {{
|
|
||||||
background-color: {ModernStylesheet.COLORS['panel_bg']};
|
|
||||||
border: 1px solid {ModernStylesheet.COLORS['border']};
|
|
||||||
border-radius: 4px; padding: 2px; text-align: center; height: 20px;
|
|
||||||
}}
|
|
||||||
QProgressBar::chunk {{
|
|
||||||
background-color: {ModernStylesheet.COLORS['success']}; border-radius: 3px;
|
|
||||||
}}
|
|
||||||
""")
|
|
||||||
progress_layout.addWidget(self._progress_bar)
|
|
||||||
progress_group.setLayout(progress_layout)
|
|
||||||
return progress_group
|
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# EventBus 连线(UI 状态由事件驱动)
|
# EventBus 连线(UI 状态由事件驱动)
|
||||||
# ================================================================
|
# ================================================================
|
||||||
@ -456,15 +385,13 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
self._event_bus.subscribe('PipelineStarted', self._on_pipeline_started)
|
self._event_bus.subscribe('PipelineStarted', self._on_pipeline_started)
|
||||||
self._event_bus.subscribe('PipelineFinished', self._on_pipeline_finished)
|
self._event_bus.subscribe('PipelineFinished', self._on_pipeline_finished)
|
||||||
self._event_bus.subscribe('PipelineStopped', self._on_pipeline_stopped)
|
self._event_bus.subscribe('PipelineStopped', self._on_pipeline_stopped)
|
||||||
self._event_bus.subscribe('LogMessage', self._on_log_message)
|
|
||||||
self._event_bus.subscribe('ProgressUpdate', self._on_progress_update)
|
|
||||||
self._event_bus.subscribe('NavigateToTab', self._on_navigate_to_tab)
|
self._event_bus.subscribe('NavigateToTab', self._on_navigate_to_tab)
|
||||||
self._event_bus.subscribe('WorkspaceChanged', self._on_workspace_changed)
|
self._event_bus.subscribe('WorkspaceChanged', self._on_workspace_changed)
|
||||||
|
|
||||||
def _on_pipeline_started(self, data):
|
def _on_pipeline_started(self, data):
|
||||||
self._run_all_btn.setEnabled(False)
|
self._run_all_btn.setEnabled(False)
|
||||||
self._stop_btn.setEnabled(True)
|
self._stop_btn.setEnabled(True)
|
||||||
self._progress_bar.setValue(0)
|
self._log_manager.progress_bar.setValue(0)
|
||||||
|
|
||||||
def _on_pipeline_finished(self, data):
|
def _on_pipeline_finished(self, data):
|
||||||
self._run_all_btn.setEnabled(True)
|
self._run_all_btn.setEnabled(True)
|
||||||
@ -472,7 +399,7 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
success = data.get('success', False)
|
success = data.get('success', False)
|
||||||
message = data.get('message', '')
|
message = data.get('message', '')
|
||||||
if success:
|
if success:
|
||||||
self._progress_bar.setValue(100)
|
self._log_manager.progress_bar.setValue(100)
|
||||||
QMessageBox.information(self, "完成", "流程执行成功!\n\n请查看工作目录中的结果文件。")
|
QMessageBox.information(self, "完成", "流程执行成功!\n\n请查看工作目录中的结果文件。")
|
||||||
else:
|
else:
|
||||||
QMessageBox.critical(self, "失败", f"流程执行失败:\n\n{message[:200]}")
|
QMessageBox.critical(self, "失败", f"流程执行失败:\n\n{message[:200]}")
|
||||||
@ -481,24 +408,6 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
self._run_all_btn.setEnabled(True)
|
self._run_all_btn.setEnabled(True)
|
||||||
self._stop_btn.setEnabled(False)
|
self._stop_btn.setEnabled(False)
|
||||||
|
|
||||||
def _on_log_message(self, data):
|
|
||||||
message = data.get('message', '')
|
|
||||||
level = data.get('level', 'info')
|
|
||||||
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
|
|
||||||
color_map = {'error': 'red', 'warning': 'orange'}
|
|
||||||
color = color_map.get(level, 'black')
|
|
||||||
formatted = f'<span style="color: {color};">[{timestamp}] {message}</span>'
|
|
||||||
self._log_text.append(formatted)
|
|
||||||
cursor = self._log_text.textCursor()
|
|
||||||
cursor.movePosition(QTextCursor.End)
|
|
||||||
self._log_text.setTextCursor(cursor)
|
|
||||||
|
|
||||||
def _on_progress_update(self, data):
|
|
||||||
percentage = data.get('percentage', 0)
|
|
||||||
message = data.get('message', '')
|
|
||||||
self._progress_bar.setValue(percentage)
|
|
||||||
self.statusBar().showMessage(message)
|
|
||||||
|
|
||||||
def _on_navigate_to_tab(self, data):
|
def _on_navigate_to_tab(self, data):
|
||||||
tab_index = data.get('tab_index', 0)
|
tab_index = data.get('tab_index', 0)
|
||||||
if 0 <= tab_index < self._tab_widget.count():
|
if 0 <= tab_index < self._tab_widget.count():
|
||||||
@ -593,56 +502,32 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
cb.wheelEvent = lambda e, c=cb: None
|
cb.wheelEvent = lambda e, c=cb: None
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# 菜单回调(待提取到对应 Manager 的临时占位)
|
# 菜单回调(全部委托给对应 Manager)
|
||||||
# ================================================================
|
# ================================================================
|
||||||
|
|
||||||
def _on_new_config(self):
|
def _on_new_config(self):
|
||||||
reply = QMessageBox.question(self, "新建配置", "是否清空当前配置?",
|
self._config_manager.new_config()
|
||||||
QMessageBox.Yes | QMessageBox.No)
|
|
||||||
if reply == QMessageBox.Yes:
|
|
||||||
self._event_bus.publish('LogMessage', {'message': '已清空配置', 'level': 'info'})
|
|
||||||
|
|
||||||
def _on_load_config(self):
|
def _on_load_config(self):
|
||||||
file_path, _ = QFileDialog.getOpenFileName(
|
self._config_manager.load_config()
|
||||||
self, "加载配置", "", "JSON Files (*.json);;All Files (*.*)")
|
|
||||||
if file_path:
|
|
||||||
self._event_bus.publish('LogMessage',
|
|
||||||
{'message': f'已加载配置: {file_path}', 'level': 'info'})
|
|
||||||
|
|
||||||
def _on_save_config(self):
|
def _on_save_config(self):
|
||||||
file_path, _ = QFileDialog.getSaveFileName(
|
self._config_manager.save_config()
|
||||||
self, "保存配置", "config.json", "JSON Files (*.json);;All Files (*.*)")
|
|
||||||
if file_path:
|
|
||||||
self._event_bus.publish('LogMessage',
|
|
||||||
{'message': f'已保存配置: {file_path}', 'level': 'info'})
|
|
||||||
|
|
||||||
def _on_ai_settings(self):
|
def _on_ai_settings(self):
|
||||||
from src.gui.dialogs import AISettingsDialog
|
self._dialog_service.show_ai_settings()
|
||||||
AISettingsDialog(self).exec_()
|
|
||||||
|
|
||||||
def _on_toggle_training_mode(self, checked):
|
def _on_toggle_training_mode(self, checked):
|
||||||
self._event_bus.publish('LogMessage', {
|
self._training_mode_manager.toggle(checked)
|
||||||
'message': f'切换到{"有训练数据" if checked else "无训练数据"}模式',
|
self._training_mode_action.setText(
|
||||||
'level': 'info',
|
self._training_mode_manager.get_action_text(checked)
|
||||||
})
|
)
|
||||||
self._training_mode_action.setText("有训练数据模式" if checked else "无训练数据模式")
|
|
||||||
|
|
||||||
def _on_show_pipeline_status(self):
|
def _on_show_pipeline_status(self):
|
||||||
from src.gui.core.worker_thread import PIPELINE_AVAILABLE, PIPELINE_ERROR_INFO
|
self._dialog_service.show_pipeline_status()
|
||||||
if PIPELINE_AVAILABLE:
|
|
||||||
QMessageBox.information(self, "Pipeline状态", "Pipeline模块: 正常加载")
|
|
||||||
else:
|
|
||||||
detail = "\n".join(PIPELINE_ERROR_INFO)
|
|
||||||
QMessageBox.warning(self, "Pipeline状态", f"Pipeline模块无法加载\n\n{detail}")
|
|
||||||
|
|
||||||
def _on_show_about(self):
|
def _on_show_about(self):
|
||||||
QMessageBox.about(self, "关于",
|
self._dialog_service.show_about()
|
||||||
"MegaCube-Water Quality V1.2\n\n"
|
|
||||||
"一个完整的水质参数反演工作流程工具\n\n"
|
|
||||||
"公司:北京依锐思遥感技术有限公司\n"
|
|
||||||
"地址:北京市海淀区清河安宁庄东路18号5号楼二层205\n"
|
|
||||||
"电话:010-51292601\n"
|
|
||||||
"邮箱:hanshanlong@iris-rs.cn")
|
|
||||||
|
|
||||||
# ================================================================
|
# ================================================================
|
||||||
# 向后兼容属性
|
# 向后兼容属性
|
||||||
@ -675,11 +560,7 @@ class WaterQualityGUI(QMainWindow):
|
|||||||
self._event_bus.publish('ProgressUpdate', {'percentage': percentage, 'message': message})
|
self._event_bus.publish('ProgressUpdate', {'percentage': percentage, 'message': message})
|
||||||
|
|
||||||
def get_current_config(self):
|
def get_current_config(self):
|
||||||
config = {}
|
return self._config_manager.get_current_config()
|
||||||
for step_id, panel in self._panel_factory.get_loaded_panels().items():
|
|
||||||
if hasattr(panel, 'get_config'):
|
|
||||||
config[step_id] = panel.get_config()
|
|
||||||
return config
|
|
||||||
|
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user