refactor: 实现纯壳主窗口 + 第一批 Manager(PanelFactory/PipelineExecutor/WorkspaceInitializer)

- water_quality_gui_v2.py: 纯壳主窗口(725行),依赖注入链 + EventBus 驱动,7个菜单连线

- panel_factory.py: PanelFactory 懒加载工厂(占位页替换 + 邻接预加载)

- pipeline_executor.py: PipelineExecutor 核心调度(接管 run_full_pipeline/run_single_step/stop_pipeline)

- workspace_initializer.py: WorkspaceInitializer 环境初始化(接管 init_workspace/set_work_directory/auto_populate_all)
This commit is contained in:
DXC
2026-06-17 17:18:15 +08:00
parent bb5c2a50f8
commit 19c86e6e44
4 changed files with 1631 additions and 0 deletions

View File

@ -0,0 +1,724 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
水质参数反演分析系统 - 图形用户界面(重构版:纯壳模式)
WaterQualityGUI 只负责窗口框架标题栏、菜单栏、状态栏、QTabWidget
所有业务逻辑委托给独立的 Manager 类。
已实现的 Manager
- PanelFactory → 面板懒加载与生命周期
- PipelineExecutor → Pipeline 执行/停止/回调(通过 EventBus 发布状态)
- WorkspaceInitializer → 工作目录选择 + 自动回填(通过 EventBus 广播)
待实现的 Manager下一批
- ConfigManager → 配置读写
- LogManager → 日志 + 进度条
- TrainingModeManager → 训练模式切换
- DialogService → 各类对话框
"""
import osgeo # noqa: F401
from osgeo import gdal, ogr # noqa: F401
import os
import sys
import ctypes
import traceback
import multiprocessing
from datetime import datetime
from PyQt5.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QPushButton, QLabel, QTabWidget, QToolBar, QSizePolicy,
QListWidget, QListWidgetItem, QGroupBox,
QTextEdit, QProgressBar, QMessageBox, QFileDialog,
)
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QIcon, QFont, QPixmap, QColor, QTextCursor
if multiprocessing.current_process().name == 'MainProcess':
if not QApplication.instance():
try:
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
except Exception:
pass
_global_app = QApplication(sys.argv)
def get_resource_path(relative_path: str) -> str:
if hasattr(sys, '_MEIPASS'):
return os.path.join(sys._MEIPASS, relative_path)
return os.path.abspath(
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), relative_path)
)
def global_exception_handler(exc_type, exc_value, exc_traceback):
err_lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
err_msg = "".join(err_lines)
dump_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "crash_dump.txt")
try:
with open(dump_path, "a", encoding="utf-8") as f:
f.write(f"\n{'='*60}\n[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}]\n")
f.write(err_msg)
except Exception:
pass
try:
QMessageBox.critical(None, "程序崩溃",
f"错误类型: {exc_type.__name__}\n错误信息: {exc_value}\n详细信息已写入: {dump_path}")
except Exception:
pass
sys.excepthook = global_exception_handler
class WaterQualityGUI(QMainWindow):
"""水质参数反演分析系统主窗口 —— 纯壳模式。
职责边界:
- 窗口框架(标题栏、菜单栏、状态栏、导航栏)
- QTabWidget 托管(通过 PanelFactory 懒加载)
- 日志区 + 进度条UI 控件归 shell内容由 EventBus 驱动)
- 各 Manager 的创建与 EventBus 连线
"""
def __init__(self):
my_appid = u'mycompany.megacube.waterquality.v1'
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(my_appid)
super().__init__()
icon_path = get_resource_path("data/icons-1/uitubiao.ico")
self.setWindowIcon(QIcon(icon_path))
# 第一步:创建各 Manager纯连线不执行业务
self._init_managers()
# 第二步:构建窗口壳
self._init_shell()
# 第三步:订阅 EventBus 事件 → 驱动 UI 更新
self._wire_event_bus()
# 第四步:应用样式 + 禁用滚轮
self._apply_stylesheet()
self._disable_wheel_for_all_spinboxes()
# 第五步:延迟启动工作目录选择
QTimer.singleShot(100, self._workspace_initializer.run)
# ================================================================
# Manager 初始化
# ================================================================
def _init_managers(self):
from src.gui.core.panel_factory import PanelFactory
from src.gui.core.panel_registry import PANEL_REGISTRY
from src.gui.core.workspace_initializer import WorkspaceInitializer
from src.gui.core.pipeline_executor import PipelineExecutor
from src.gui.core.event_bus import global_event_bus
self._panel_factory = PanelFactory(
registry=PANEL_REGISTRY,
main_window=self,
preload_window=1,
)
self._workspace_initializer = WorkspaceInitializer(
panel_factory=self._panel_factory,
parent=self,
)
self._pipeline_executor = PipelineExecutor(
panel_factory=self._panel_factory,
workspace_initializer=self._workspace_initializer,
parent=self,
)
self._event_bus = global_event_bus
# ================================================================
# 窗口壳构建
# ================================================================
def _init_shell(self):
self.setWindowTitle("MegaCube-Water Quality V1.2")
screen_geometry = QApplication.primaryScreen().availableGeometry()
screen_width = screen_geometry.width()
screen_height = screen_geometry.height()
self.resize(1200, screen_height)
self.move((screen_width - 1200) // 2, 0)
self.setMinimumSize(600, 400)
self._create_title_bar()
self._create_banner()
self._create_central_layout()
self.statusBar().showMessage("就绪")
def _create_title_bar(self):
title_widget = QWidget()
title_layout = QHBoxLayout()
title_layout.setContentsMargins(8, 4, 8, 4)
title_layout.setSpacing(0)
logo_label = QLabel()
logo_label.setFixedSize(180, 48)
logo_label.setAlignment(Qt.AlignCenter)
logo_label.setStyleSheet(
"background-color: #f8f9fa;"
"border-top-left-radius: 4px; border-bottom-left-radius: 4px;"
)
logo_path = get_resource_path("data/icons/logo.png")
logo_pixmap = QPixmap(logo_path)
if not logo_pixmap.isNull():
logo_label.setPixmap(logo_pixmap.scaledToHeight(38, Qt.SmoothTransformation))
else:
logo_label.setText("Logo")
title_layout.addWidget(logo_label)
menubar = self.menuBar()
menubar.setStyleSheet("""
QMenuBar { background-color: #f8f9fa; border: none; padding: 4px 8px; }
QMenuBar::item { padding: 6px 12px; font-size: 13px; }
QMenuBar::item:selected { background-color: #e6f0ff; border-radius: 3px; }
""")
self._build_menus(menubar)
title_layout.addWidget(menubar)
title_widget.setLayout(title_layout)
title_widget.setStyleSheet(
"background-color: #f8f9fa; border-bottom: 1px solid #d0d0d0;"
)
self.setMenuWidget(title_widget)
def _build_menus(self, menubar):
file_menu = menubar.addMenu("文件")
file_menu.addAction("新建配置").triggered.connect(self._on_new_config)
file_menu.addAction("打开配置").triggered.connect(self._on_load_config)
file_menu.addAction("保存配置").triggered.connect(self._on_save_config)
file_menu.addSeparator()
file_menu.addAction("退出").triggered.connect(self.close)
tools_menu = menubar.addMenu("工具")
tools_menu.addAction("设置工作目录").triggered.connect(
self._workspace_initializer.set_work_directory
)
tools_menu.addAction("打开工作目录").triggered.connect(
self._workspace_initializer.open_work_directory
)
tools_menu.addSeparator()
tools_menu.addAction("AI 引擎配置...").triggered.connect(self._on_ai_settings)
tools_menu.addSeparator()
tools_menu.addAction("自动填充所有输入路径").triggered.connect(
self._workspace_initializer.auto_populate_all
)
self._training_mode_action = tools_menu.addAction("有训练数据模式")
self._training_mode_action.setCheckable(True)
self._training_mode_action.setChecked(True)
self._training_mode_action.triggered.connect(self._on_toggle_training_mode)
help_menu = menubar.addMenu("帮助")
help_menu.addAction("检查Pipeline状态").triggered.connect(self._on_show_pipeline_status)
help_menu.addSeparator()
help_menu.addAction("关于").triggered.connect(self._on_show_about)
def _create_banner(self):
banner_widget = QWidget()
banner_layout = QHBoxLayout()
banner_layout.setContentsMargins(0, 0, 0, 0)
banner_layout.setSpacing(0)
self._banner_label = QLabel()
self._banner_label.setMinimumHeight(140)
self._banner_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
self._banner_label.setScaledContents(False)
banner_path = get_resource_path("data/icons/Mega Water 1.0.jpg")
self._banner_pixmap = QPixmap(banner_path)
if not self._banner_pixmap.isNull():
QTimer.singleShot(50, self._update_banner_image)
banner_layout.addWidget(self._banner_label)
self._banner_title_label = QLabel("MegaCube-Water Quality V1.2", self._banner_label)
self._banner_title_label.setStyleSheet("""
QLabel {
background: transparent; color: white; font-size: 48px;
font-family: "Times New Roman", "Georgia", serif;
}
""")
self._banner_title_label.setAttribute(Qt.WA_TransparentForMouseEvents)
self._banner_title_label.show()
self._banner_title_label.raise_()
banner_widget.setLayout(banner_layout)
banner_toolbar = QToolBar()
banner_toolbar.setMovable(False)
banner_toolbar.setFloatable(False)
banner_toolbar.addWidget(banner_widget)
banner_toolbar.setStyleSheet(
"QToolBar { background: white; border: none; padding: 0px; margin: 0px; }"
)
self.addToolBar(Qt.TopToolBarArea, banner_toolbar)
def _create_central_layout(self):
central_widget = QWidget()
main_layout = QHBoxLayout()
main_layout.setContentsMargins(0, 0, 0, 0)
main_layout.setSpacing(0)
main_layout.addWidget(self._create_navigation(), 1)
right_widget = QWidget()
right_layout = QVBoxLayout()
right_layout.setContentsMargins(15, 15, 15, 15)
right_layout.setSpacing(10)
self._tab_widget = self._panel_factory.create_tab_widget(icons_dir="data/icons")
self._tab_widget.currentChanged.connect(self._on_tab_changed)
right_layout.addWidget(self._tab_widget, 3)
right_layout.addWidget(self._create_log_panel(), 1)
right_layout.addWidget(self._create_progress_panel(), 0)
right_widget.setLayout(right_layout)
main_layout.addWidget(right_widget, 4)
central_widget.setLayout(main_layout)
self.setCentralWidget(central_widget)
def _create_navigation(self):
from src.gui.core.panel_registry import build_stage_groups
from src.gui.styles import ModernStylesheet
nav_widget = QWidget()
nav_layout = QVBoxLayout()
nav_layout.setContentsMargins(10, 15, 10, 15)
nav_layout.setSpacing(10)
title = QLabel("流程步骤")
title.setFont(QFont("Arial", 13, QFont.Bold))
title.setAlignment(Qt.AlignCenter)
title.setStyleSheet(f"color: {ModernStylesheet.COLORS['text_primary']}; padding: 10px;")
nav_layout.addWidget(title)
self._step_list = QListWidget()
self._step_list.setStyleSheet(ModernStylesheet.get_sidebar_stylesheet())
process_stages = build_stage_groups()
stage_names = list(process_stages.keys())
for stage_idx, (stage_name, steps) in enumerate(process_stages.items()):
stage_item = QListWidgetItem(stage_name)
stage_font = QFont("Arial", 11, QFont.Bold)
stage_item.setFont(stage_font)
stage_item.setForeground(QColor(ModernStylesheet.COLORS.get('accent', '#0078D4')))
stage_item.setFlags(stage_item.flags() & ~Qt.ItemIsSelectable)
stage_item.setFlags(stage_item.flags() & ~Qt.ItemIsEnabled)
stage_item.setData(Qt.UserRole, "stage_header")
self._step_list.addItem(stage_item)
for step_id, step_display in steps:
item = QListWidgetItem(f" └─ {step_display}")
item.setData(Qt.UserRole, step_id)
step_font = QFont("Arial", 10)
item.setFont(step_font)
item.setForeground(QColor(ModernStylesheet.COLORS.get('text_secondary', '#666666')))
self._step_list.addItem(item)
if stage_idx < len(stage_names) - 1:
sep = QListWidgetItem("")
sep.setFlags(sep.flags() & ~Qt.ItemIsSelectable)
sep.setFlags(sep.flags() & ~Qt.ItemIsEnabled)
self._step_list.addItem(sep)
self._step_list.currentRowChanged.connect(self._on_step_list_changed)
nav_layout.addWidget(self._step_list)
btn_layout = QVBoxLayout()
btn_layout.setSpacing(8)
self._run_all_btn = QPushButton("> 运行完整流程")
self._run_all_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('success'))
self._run_all_btn.setMinimumHeight(35)
self._run_all_btn.clicked.connect(self._pipeline_executor.run_full_pipeline)
btn_layout.addWidget(self._run_all_btn)
self._stop_btn = QPushButton("⏹ 停止")
self._stop_btn.setEnabled(False)
self._stop_btn.setMinimumHeight(35)
self._stop_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('danger'))
self._stop_btn.clicked.connect(self._pipeline_executor.stop_pipeline)
btn_layout.addWidget(self._stop_btn)
nav_layout.addLayout(btn_layout)
nav_widget.setLayout(nav_layout)
nav_widget.setMaximumWidth(280)
nav_widget.setStyleSheet(
f"background-color: {ModernStylesheet.COLORS['panel_bg']};"
f"border-right: 1px solid {ModernStylesheet.COLORS['border_light']};"
)
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 状态由事件驱动)
# ================================================================
def _wire_event_bus(self):
self._event_bus.subscribe('PipelineStarted', self._on_pipeline_started)
self._event_bus.subscribe('PipelineFinished', self._on_pipeline_finished)
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('WorkspaceChanged', self._on_workspace_changed)
def _on_pipeline_started(self, data):
self._run_all_btn.setEnabled(False)
self._stop_btn.setEnabled(True)
self._progress_bar.setValue(0)
def _on_pipeline_finished(self, data):
self._run_all_btn.setEnabled(True)
self._stop_btn.setEnabled(False)
success = data.get('success', False)
message = data.get('message', '')
if success:
self._progress_bar.setValue(100)
QMessageBox.information(self, "完成", "流程执行成功!\n\n请查看工作目录中的结果文件。")
else:
QMessageBox.critical(self, "失败", f"流程执行失败:\n\n{message[:200]}")
def _on_pipeline_stopped(self, data):
self._run_all_btn.setEnabled(True)
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):
tab_index = data.get('tab_index', 0)
if 0 <= tab_index < self._tab_widget.count():
self._tab_widget.setCurrentIndex(tab_index)
def _on_workspace_changed(self, data):
work_dir = data.get('work_dir', '')
self.statusBar().showMessage(f"工作目录: {work_dir}")
# ================================================================
# 导航 ↔ Tab 双向同步(纯 UI 路由)
# ================================================================
def _on_step_list_changed(self, index):
if index < 0:
return
item = self._step_list.item(index)
if not item:
return
item_data = item.data(Qt.UserRole)
if item_data == "stage_header" or item_data is None:
return
from src.gui.core.panel_registry import get_tab_index
tab_index = get_tab_index(item_data)
if tab_index >= 0:
self._tab_widget.setCurrentIndex(tab_index)
def _on_tab_changed(self, index):
if index < 0:
return
from src.gui.core.panel_registry import get_step_id_by_tab_index
target_step_id = get_step_id_by_tab_index(index)
if target_step_id is None:
return
for row in range(self._step_list.count()):
item = self._step_list.item(row)
if not item:
continue
if item.data(Qt.UserRole) == target_step_id:
self._step_list.setCurrentRow(row)
break
# ================================================================
# 横幅自适应
# ================================================================
def _update_banner_image(self):
if not hasattr(self, '_banner_pixmap') or self._banner_pixmap.isNull():
return
TARGET_HEIGHT = 140
target_width = self.width()
orig_w = self._banner_pixmap.width()
orig_h = self._banner_pixmap.height()
scale_factor = max(target_width / orig_w, TARGET_HEIGHT / orig_h)
new_w = int(orig_w * scale_factor)
new_h = int(orig_h * scale_factor)
scaled = self._banner_pixmap.scaled(new_w, new_h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
crop_x = (new_w - target_width) // 2
crop_y = (new_h - TARGET_HEIGHT) // 2
final = scaled.copy(crop_x, crop_y, target_width, TARGET_HEIGHT)
self._banner_label.setFixedHeight(TARGET_HEIGHT)
self._banner_label.setFixedWidth(target_width)
self._banner_label.setPixmap(final)
if hasattr(self, '_banner_title_label'):
title_x = 160
title_y = max(0, (TARGET_HEIGHT - 60) // 2)
self._banner_title_label.move(title_x, title_y)
self._banner_title_label.resize(target_width - title_x - 20, 60)
def resizeEvent(self, event):
super().resizeEvent(event)
self._update_banner_image()
# ================================================================
# 样式与 UX
# ================================================================
def _apply_stylesheet(self):
from src.gui.styles import ModernStylesheet
self.setStyleSheet(ModernStylesheet.get_main_stylesheet())
def _disable_wheel_for_all_spinboxes(self):
from PyQt5.QtWidgets import QSpinBox, QDoubleSpinBox, QComboBox
for sb in self.findChildren(QSpinBox):
sb.setFocusPolicy(Qt.StrongFocus)
sb.wheelEvent = lambda e, s=sb: None
for sb in self.findChildren(QDoubleSpinBox):
sb.setFocusPolicy(Qt.StrongFocus)
sb.wheelEvent = lambda e, s=sb: None
for cb in self.findChildren(QComboBox):
cb.setFocusPolicy(Qt.StrongFocus)
cb.wheelEvent = lambda e, c=cb: None
# ================================================================
# 菜单回调(待提取到对应 Manager 的临时占位)
# ================================================================
def _on_new_config(self):
reply = QMessageBox.question(self, "新建配置", "是否清空当前配置?",
QMessageBox.Yes | QMessageBox.No)
if reply == QMessageBox.Yes:
self._event_bus.publish('LogMessage', {'message': '已清空配置', 'level': 'info'})
def _on_load_config(self):
file_path, _ = QFileDialog.getOpenFileName(
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):
file_path, _ = QFileDialog.getSaveFileName(
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):
from src.gui.dialogs import AISettingsDialog
AISettingsDialog(self).exec_()
def _on_toggle_training_mode(self, checked):
self._event_bus.publish('LogMessage', {
'message': f'切换到{"有训练数据" if checked else "无训练数据"}模式',
'level': 'info',
})
self._training_mode_action.setText("有训练数据模式" if checked else "无训练数据模式")
def _on_show_pipeline_status(self):
from src.gui.core.worker_thread import PIPELINE_AVAILABLE, PIPELINE_ERROR_INFO
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):
QMessageBox.about(self, "关于",
"MegaCube-Water Quality V1.2\n\n"
"一个完整的水质参数反演工作流程工具\n\n"
"公司:北京依锐思遥感技术有限公司\n"
"地址北京市海淀区清河安宁庄东路18号5号楼二层205\n"
"电话010-51292601\n"
"邮箱hanshanlong@iris-rs.cn")
# ================================================================
# 向后兼容属性
# ================================================================
@property
def panels(self):
return self._panel_factory.get_loaded_panels()
@property
def work_dir(self):
return self._workspace_initializer.work_dir
@work_dir.setter
def work_dir(self, value):
self._workspace_initializer.work_dir = value
@property
def pipeline(self):
return None
@property
def worker(self):
return self._pipeline_executor.worker
def log_message(self, message, level='info'):
self._event_bus.publish('LogMessage', {'message': message, 'level': level})
def update_progress(self, percentage, message):
self._event_bus.publish('ProgressUpdate', {'percentage': percentage, 'message': message})
def get_current_config(self):
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
# ============================================================
def main():
if multiprocessing.current_process().name != 'MainProcess':
sys.exit(0)
app = QApplication.instance()
if not app:
app = QApplication(sys.argv)
app.setApplicationName("Mega Water")
app.setOrganizationName("WaterQuality")
try:
from src.auth.license_manager import verify_license
from src.auth.license_dialog import LicenseDialog
except ImportError:
_current_dir = os.path.dirname(os.path.abspath(__file__))
_project_root = os.path.abspath(os.path.join(_current_dir, '..', '..'))
if _project_root not in sys.path:
sys.path.insert(0, _project_root)
from src.auth.license_manager import verify_license
from src.auth.license_dialog import LicenseDialog
_is_license_valid, _license_msg = verify_license()
if not _is_license_valid:
LicenseDialog().exec_()
sys.exit(0)
window = WaterQualityGUI()
window.show()
sys.exit(app.exec_())
if __name__ == "__main__":
if multiprocessing.current_process().name != 'MainProcess':
sys.exit(0)
try:
multiprocessing.freeze_support()
except Exception:
pass
main()