步骤一页面修改
This commit is contained in:
@ -1,14 +1,14 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
"""
|
"""
|
||||||
Step1 面板 - 水域掩膜生成
|
Step1 面板 - 水域掩膜生成 (完美对齐无跳动重构)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
# 路径归一化 helper(与 pipeline.get_step_output_dir 互为表里)
|
# 路径归一化 helper
|
||||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||||
if _HERE not in sys.path:
|
if _HERE not in sys.path:
|
||||||
sys.path.insert(0, _HERE)
|
sys.path.insert(0, _HERE)
|
||||||
@ -16,231 +16,202 @@ from _step_path_resolver import resolve_subdir
|
|||||||
|
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel,
|
QWidget, QVBoxLayout, QHBoxLayout, QGroupBox, QLabel,
|
||||||
QDoubleSpinBox, QCheckBox, QPushButton, QFormLayout, QRadioButton,
|
QCheckBox, QPushButton, QRadioButton, QLineEdit,
|
||||||
QMessageBox,
|
QMessageBox, QStackedWidget, QSizePolicy
|
||||||
)
|
)
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
|
from PyQt5.QtGui import QDoubleValidator
|
||||||
|
|
||||||
# 从公共组件库导入
|
|
||||||
from src.gui.components.custom_widgets import FileSelectWidget
|
from src.gui.components.custom_widgets import FileSelectWidget
|
||||||
from src.gui.styles import ModernStylesheet
|
from src.gui.styles import ModernStylesheet
|
||||||
|
|
||||||
|
|
||||||
class Step1Panel(QWidget):
|
class Step1Panel(QWidget):
|
||||||
"""1. 水域掩膜生成"""
|
"""1. 水域掩膜生成"""
|
||||||
|
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.init_ui()
|
self.init_ui()
|
||||||
|
|
||||||
def init_ui(self):
|
def init_ui(self):
|
||||||
layout = QVBoxLayout()
|
self.setStyleSheet(ModernStylesheet.get_main_stylesheet())
|
||||||
|
|
||||||
# 标题
|
main_layout = QVBoxLayout()
|
||||||
|
main_layout.setContentsMargins(24, 24, 24, 24)
|
||||||
|
main_layout.setSpacing(20)
|
||||||
|
|
||||||
|
# ==========================================
|
||||||
|
# 卡片 1:掩膜生成策略
|
||||||
|
# ==========================================
|
||||||
|
method_group = QGroupBox("📁 掩膜生成策略")
|
||||||
|
method_layout = QHBoxLayout()
|
||||||
|
method_layout.setSpacing(24)
|
||||||
|
method_layout.setContentsMargins(20, 24, 20, 20)
|
||||||
|
|
||||||
# 掩膜生成方式选择
|
|
||||||
method_group = QGroupBox("掩膜生成方式")
|
|
||||||
method_layout = QVBoxLayout()
|
|
||||||
|
|
||||||
# 使用现有掩膜文件
|
|
||||||
self.use_existing_radio = QRadioButton("使用现有掩膜文件")
|
self.use_existing_radio = QRadioButton("使用现有掩膜文件")
|
||||||
self.use_existing_radio.setChecked(True)
|
self.use_existing_radio.setChecked(True)
|
||||||
|
# 【核心修复】:保留原生圆圈,但直接强制给予足够大(200x35)的安全盒模型,杜绝高DPI下任何形式的截断!
|
||||||
|
self.use_existing_radio.setMinimumSize(200, 35)
|
||||||
|
|
||||||
|
self.use_ndwi_radio = QRadioButton("使用 NDWI 自动生成")
|
||||||
|
self.use_ndwi_radio.setMinimumSize(200, 35)
|
||||||
|
|
||||||
method_layout.addWidget(self.use_existing_radio)
|
method_layout.addWidget(self.use_existing_radio)
|
||||||
|
|
||||||
# 使用NDWI自动生成
|
|
||||||
self.use_ndwi_radio = QRadioButton("使用NDWI自动生成")
|
|
||||||
method_layout.addWidget(self.use_ndwi_radio)
|
method_layout.addWidget(self.use_ndwi_radio)
|
||||||
|
method_layout.addStretch()
|
||||||
# 应用QRadioButton样式(实心选中点)
|
|
||||||
radio_style = """
|
|
||||||
QRadioButton::indicator {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border-radius: 8px;
|
|
||||||
border: 2px solid #999;
|
|
||||||
}
|
|
||||||
QRadioButton::indicator:checked {
|
|
||||||
background-color: #0078D7;
|
|
||||||
border: 2px solid #0078D7;
|
|
||||||
}
|
|
||||||
QRadioButton::indicator:unchecked {
|
|
||||||
background-color: white;
|
|
||||||
border: 2px solid #999;
|
|
||||||
}
|
|
||||||
QRadioButton::indicator:hover {
|
|
||||||
border: 2px solid #0078D7;
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
self.use_existing_radio.setStyleSheet(radio_style)
|
|
||||||
self.use_ndwi_radio.setStyleSheet(radio_style)
|
|
||||||
|
|
||||||
method_group.setLayout(method_layout)
|
method_group.setLayout(method_layout)
|
||||||
layout.addWidget(method_group)
|
main_layout.addWidget(method_group)
|
||||||
|
|
||||||
# 掩膜文件选择
|
# ==========================================
|
||||||
|
# 卡片 2:文件与参数配置 (绝对像素级对齐)
|
||||||
|
# ==========================================
|
||||||
|
config_group = QGroupBox("⚙️ 文件与参数配置")
|
||||||
|
config_layout = QVBoxLayout()
|
||||||
|
config_layout.setSpacing(16)
|
||||||
|
config_layout.setContentsMargins(20, 24, 20, 20)
|
||||||
|
|
||||||
|
hint = QLabel(
|
||||||
|
"💡 提示: 若掩膜文件是 Shapefile(.shp),需提供参考影像用于栅格化;若使用 NDWI 自动生成,只需提供参考影像即可。")
|
||||||
|
hint.setWordWrap(True)
|
||||||
|
hint.setStyleSheet(f"""
|
||||||
|
QLabel {{
|
||||||
|
color: {ModernStylesheet.COLORS['primary']};
|
||||||
|
background-color: {ModernStylesheet.COLORS['selected']};
|
||||||
|
border: 1px solid {ModernStylesheet.COLORS['border_light']};
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}}
|
||||||
|
""")
|
||||||
|
config_layout.addWidget(hint)
|
||||||
|
|
||||||
|
self.stack = QStackedWidget()
|
||||||
|
|
||||||
|
# 第 0 页:现有掩膜文件
|
||||||
self.mask_file = FileSelectWidget(
|
self.mask_file = FileSelectWidget(
|
||||||
"掩膜文件:",
|
"现有掩膜文件:",
|
||||||
"Shapefiles (*.shp);;Raster Files (*.dat *.tif);;All Files (*.*)"
|
"Shapefiles (*.shp);;Raster Files (*.dat *.tif);;All Files (*.*)"
|
||||||
)
|
)
|
||||||
layout.addWidget(self.mask_file)
|
self.mask_file.label.setMinimumWidth(100)
|
||||||
|
self.stack.addWidget(self.mask_file)
|
||||||
|
|
||||||
# 影像文件选择(用于shp栅格化或NDWI生成)
|
# 第 1 页:NDWI 阈值参数
|
||||||
|
self.ndwi_widget = QWidget()
|
||||||
|
ndwi_layout = QHBoxLayout(self.ndwi_widget)
|
||||||
|
ndwi_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
ndwi_layout.setSpacing(8)
|
||||||
|
|
||||||
|
ndwi_label = QLabel("NDWI 阈值:")
|
||||||
|
ndwi_label.setMinimumWidth(100)
|
||||||
|
ndwi_layout.addWidget(ndwi_label)
|
||||||
|
|
||||||
|
self.ndwi_threshold = QLineEdit()
|
||||||
|
validator = QDoubleValidator(0.0, 1.0, 2, self)
|
||||||
|
validator.setNotation(QDoubleValidator.StandardNotation)
|
||||||
|
self.ndwi_threshold.setValidator(validator)
|
||||||
|
self.ndwi_threshold.setText("0.40")
|
||||||
|
self.ndwi_threshold.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||||||
|
ndwi_layout.addWidget(self.ndwi_threshold)
|
||||||
|
|
||||||
|
self.stack.addWidget(self.ndwi_widget)
|
||||||
|
config_layout.addWidget(self.stack)
|
||||||
|
|
||||||
|
# 参考影像
|
||||||
self.img_file = FileSelectWidget(
|
self.img_file = FileSelectWidget(
|
||||||
"参考影像:",
|
"输入参考影像:",
|
||||||
"Image Files (*.bsq *.dat *.tif);;All Files (*.*)"
|
"Image Files (*.bsq *.dat *.tif);;All Files (*.*)"
|
||||||
)
|
)
|
||||||
layout.addWidget(self.img_file)
|
self.img_file.label.setMinimumWidth(100)
|
||||||
|
config_layout.addWidget(self.img_file)
|
||||||
|
|
||||||
# NDWI参数设置
|
config_group.setLayout(config_layout)
|
||||||
self.ndwi_group = QGroupBox("NDWI参数设置")
|
main_layout.addWidget(config_group)
|
||||||
ndwi_layout = QVBoxLayout()
|
|
||||||
|
|
||||||
# NDWI阈值
|
# ==========================================
|
||||||
threshold_layout = QHBoxLayout()
|
# 卡片 3:输出与执行
|
||||||
threshold_layout.addWidget(QLabel("NDWI阈值:"))
|
# ==========================================
|
||||||
self.ndwi_threshold = QDoubleSpinBox()
|
output_group = QGroupBox("🚀 输出与执行")
|
||||||
self.ndwi_threshold.setRange(0.0, 1.0)
|
output_layout = QVBoxLayout()
|
||||||
self.ndwi_threshold.setSingleStep(0.05)
|
output_layout.setSpacing(16)
|
||||||
self.ndwi_threshold.setValue(0.4)
|
output_layout.setContentsMargins(20, 24, 20, 20)
|
||||||
self.ndwi_threshold.setDecimals(2)
|
|
||||||
threshold_layout.addWidget(self.ndwi_threshold)
|
|
||||||
threshold_layout.addStretch()
|
|
||||||
ndwi_layout.addLayout(threshold_layout)
|
|
||||||
|
|
||||||
self.ndwi_group.setLayout(ndwi_layout)
|
|
||||||
layout.addWidget(self.ndwi_group)
|
|
||||||
|
|
||||||
# 输出文件路径(使用save模式)
|
|
||||||
self.output_file = FileSelectWidget(
|
self.output_file = FileSelectWidget(
|
||||||
"输出掩膜:",
|
"结果保存至:",
|
||||||
"Mask Files (*.dat *.tif);;All Files (*.*)",
|
"Mask Files (*.dat *.tif);;All Files (*.*)",
|
||||||
mode="save"
|
mode="save"
|
||||||
)
|
)
|
||||||
|
self.output_file.label.setMinimumWidth(100)
|
||||||
self.output_file.line_edit.setPlaceholderText("water_mask.dat")
|
self.output_file.line_edit.setPlaceholderText("water_mask.dat")
|
||||||
layout.addWidget(self.output_file)
|
output_layout.addWidget(self.output_file)
|
||||||
|
|
||||||
# 提示信息 - 专业的 Info Alert 样式
|
action_layout = QHBoxLayout()
|
||||||
hint = QLabel("💡 提示: 如果掩膜文件是Shapefile(.shp),需要提供参考影像用于栅格化;如果使用NDWI自动生成,只需要提供参考影像")
|
|
||||||
hint.setWordWrap(True) # 允许自动换行
|
|
||||||
hint.setStyleSheet("""
|
|
||||||
QLabel {
|
|
||||||
color: #0055D4;
|
|
||||||
font-size: 13px;
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: #E8F4FF;
|
|
||||||
border: 2px solid #0055D4;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 12px 16px;
|
|
||||||
margin: 8px 0px;
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
layout.addWidget(hint)
|
|
||||||
|
|
||||||
# 启用步骤
|
|
||||||
self.enable_checkbox = QCheckBox("启用此步骤")
|
self.enable_checkbox = QCheckBox("启用此步骤")
|
||||||
self.enable_checkbox.setChecked(True)
|
self.enable_checkbox.setChecked(True)
|
||||||
layout.addWidget(self.enable_checkbox)
|
self.enable_checkbox.setMinimumHeight(24)
|
||||||
|
action_layout.addWidget(self.enable_checkbox)
|
||||||
|
action_layout.addStretch()
|
||||||
|
|
||||||
# 独立运行按钮
|
self.run_btn = QPushButton("独立运行步骤")
|
||||||
self.run_btn = QPushButton("独立运行此步骤")
|
self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('primary'))
|
||||||
self.run_btn.setStyleSheet(ModernStylesheet.get_button_stylesheet('success'))
|
self.run_btn.setMinimumWidth(140)
|
||||||
self.run_btn.clicked.connect(self._on_run_single_clicked)
|
self.run_btn.clicked.connect(self._on_run_single_clicked)
|
||||||
layout.addWidget(self.run_btn)
|
action_layout.addWidget(self.run_btn)
|
||||||
|
|
||||||
|
output_layout.addLayout(action_layout)
|
||||||
|
output_group.setLayout(output_layout)
|
||||||
|
main_layout.addWidget(output_group)
|
||||||
|
|
||||||
# 连接信号
|
|
||||||
self.use_existing_radio.toggled.connect(self.update_ui_state)
|
self.use_existing_radio.toggled.connect(self.update_ui_state)
|
||||||
self.use_ndwi_radio.toggled.connect(self.update_ui_state)
|
main_layout.addStretch()
|
||||||
|
self.setLayout(main_layout)
|
||||||
|
|
||||||
layout.addStretch()
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
# 初始UI状态
|
|
||||||
self.update_ui_state()
|
self.update_ui_state()
|
||||||
|
|
||||||
def update_ui_state(self):
|
def update_ui_state(self):
|
||||||
"""根据选择的掩膜生成方式更新UI状态(使用显示/隐藏控制)"""
|
|
||||||
use_ndwi = self.use_ndwi_radio.isChecked()
|
|
||||||
|
|
||||||
# 动态显示/隐藏组件
|
|
||||||
if use_ndwi:
|
|
||||||
# 使用NDWI模式:隐藏掩膜文件,显示NDWI参数和输出掩膜
|
|
||||||
self.mask_file.setVisible(False)
|
|
||||||
self.ndwi_group.setVisible(True)
|
|
||||||
self.output_file.setVisible(True) # 显示输出掩膜路径
|
|
||||||
|
|
||||||
# 当切换到NDWI模式时,如果工作目录已设置,自动填充输出路径
|
|
||||||
if hasattr(self, 'work_dir') and self.work_dir:
|
|
||||||
self._auto_fill_output_path()
|
|
||||||
else:
|
|
||||||
# 使用现有掩膜模式:显示掩膜文件,隐藏NDWI参数和输出掩膜
|
|
||||||
self.mask_file.setVisible(True)
|
|
||||||
self.ndwi_group.setVisible(False)
|
|
||||||
self.output_file.setVisible(False) # 隐藏输出掩膜路径
|
|
||||||
|
|
||||||
# 参考影像在两种模式下都显示
|
|
||||||
self.img_file.setVisible(True)
|
|
||||||
|
|
||||||
def update_work_directory(self, work_dir):
|
|
||||||
"""
|
|
||||||
保存工作目录引用,用于后续自动填充路径
|
|
||||||
|
|
||||||
Args:
|
|
||||||
work_dir: 工作目录路径
|
|
||||||
"""
|
|
||||||
if not work_dir:
|
|
||||||
return
|
|
||||||
|
|
||||||
# 保存工作目录引用
|
|
||||||
self.work_dir = work_dir
|
|
||||||
|
|
||||||
# 如果当前选中的是NDWI模式,立即填充输出路径
|
|
||||||
if self.use_ndwi_radio.isChecked():
|
if self.use_ndwi_radio.isChecked():
|
||||||
|
self.stack.setCurrentWidget(self.ndwi_widget)
|
||||||
|
else:
|
||||||
|
self.stack.setCurrentWidget(self.mask_file)
|
||||||
|
|
||||||
|
if hasattr(self, 'work_dir') and self.work_dir:
|
||||||
self._auto_fill_output_path()
|
self._auto_fill_output_path()
|
||||||
|
|
||||||
|
def update_work_directory(self, work_dir):
|
||||||
|
if not work_dir:
|
||||||
|
return
|
||||||
|
self.work_dir = work_dir
|
||||||
|
self._auto_fill_output_path()
|
||||||
|
|
||||||
def _auto_fill_output_path(self):
|
def _auto_fill_output_path(self):
|
||||||
"""
|
|
||||||
自动填充输出掩膜路径(仅在NDWI模式下)
|
|
||||||
确保路径使用正斜杠,避免斜杠混用
|
|
||||||
"""
|
|
||||||
if not hasattr(self, 'work_dir') or not self.work_dir:
|
if not hasattr(self, 'work_dir') or not self.work_dir:
|
||||||
return
|
return
|
||||||
|
|
||||||
# 生成输出掩膜的完整路径(用 resolve_subdir 归一化,消除硬编码)
|
|
||||||
output_dir = resolve_subdir(self.work_dir, 'water_mask')
|
output_dir = resolve_subdir(self.work_dir, 'water_mask')
|
||||||
os.makedirs(output_dir, exist_ok=True) # 确保目录存在
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
# 统一使用正斜杠,避免 \ 和 / 混用
|
|
||||||
default_output_path = os.path.join(output_dir, "water_mask_out.dat").replace('\\', '/')
|
default_output_path = os.path.join(output_dir, "water_mask_out.dat").replace('\\', '/')
|
||||||
self.output_file.set_path(default_output_path)
|
self.output_file.set_path(default_output_path)
|
||||||
|
|
||||||
def get_config(self):
|
def get_config(self):
|
||||||
"""获取配置"""
|
|
||||||
use_ndwi = self.use_ndwi_radio.isChecked()
|
use_ndwi = self.use_ndwi_radio.isChecked()
|
||||||
|
try:
|
||||||
|
ndwi_val = float(self.ndwi_threshold.text().strip())
|
||||||
|
except ValueError:
|
||||||
|
ndwi_val = 0.4
|
||||||
|
|
||||||
config = {
|
config = {
|
||||||
'mask_path': None if use_ndwi else self.mask_file.get_path(),
|
'mask_path': None if use_ndwi else self.mask_file.get_path(),
|
||||||
'use_ndwi': use_ndwi,
|
'use_ndwi': use_ndwi,
|
||||||
'ndwi_threshold': self.ndwi_threshold.value()
|
'ndwi_threshold': ndwi_val
|
||||||
}
|
}
|
||||||
|
|
||||||
# 参考影像路径(两种模式都可能需要)
|
|
||||||
img_path = self.img_file.get_path()
|
img_path = self.img_file.get_path()
|
||||||
if img_path:
|
if img_path:
|
||||||
config['img_path'] = img_path
|
config['img_path'] = img_path
|
||||||
|
|
||||||
# 输出路径:仅在NDWI模式下有效
|
output_path = self.output_file.get_path()
|
||||||
if use_ndwi:
|
if output_path:
|
||||||
output_path = self.output_file.get_path()
|
config['output_path'] = output_path
|
||||||
if output_path:
|
|
||||||
config['output_path'] = output_path
|
|
||||||
else:
|
|
||||||
# 使用现有掩膜时,不传递output_path,避免底层错误尝试保存文件
|
|
||||||
config['output_path'] = None
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
def set_config(self, config):
|
def set_config(self, config):
|
||||||
"""设置配置"""
|
|
||||||
if 'mask_path' in config:
|
if 'mask_path' in config:
|
||||||
self.mask_file.set_path(config['mask_path'])
|
self.mask_file.set_path(config['mask_path'])
|
||||||
if 'img_path' in config:
|
if 'img_path' in config:
|
||||||
@ -253,20 +224,13 @@ class Step1Panel(QWidget):
|
|||||||
else:
|
else:
|
||||||
self.use_existing_radio.setChecked(True)
|
self.use_existing_radio.setChecked(True)
|
||||||
if 'ndwi_threshold' in config:
|
if 'ndwi_threshold' in config:
|
||||||
self.ndwi_threshold.setValue(config['ndwi_threshold'])
|
self.ndwi_threshold.setText(f"{config['ndwi_threshold']:.2f}")
|
||||||
|
|
||||||
self.update_ui_state()
|
self.update_ui_state()
|
||||||
|
|
||||||
def _on_run_single_clicked(self):
|
def _on_run_single_clicked(self):
|
||||||
"""通过 EventBus 发布单步执行请求(解耦面板与 PipelineExecutor)。
|
|
||||||
|
|
||||||
替代旧有的 parent 链上溯查找 run_single_step 的紧耦合方式。
|
|
||||||
PipelineExecutor 在 __init__ 中订阅 RequestRunSingleStep 事件,
|
|
||||||
收到后调用 run_single_step(step_name, config) 统一处理预检/工作目录/执行。
|
|
||||||
"""
|
|
||||||
from src.gui.core.event_bus import global_event_bus
|
from src.gui.core.event_bus import global_event_bus
|
||||||
|
|
||||||
# 验证输入(与旧 run_step 逻辑一致)
|
|
||||||
if self.use_ndwi_radio.isChecked():
|
if self.use_ndwi_radio.isChecked():
|
||||||
img_path = self.img_file.get_path()
|
img_path = self.img_file.get_path()
|
||||||
if not img_path:
|
if not img_path:
|
||||||
@ -287,36 +251,4 @@ class Step1Panel(QWidget):
|
|||||||
global_event_bus.publish('RequestRunSingleStep', {
|
global_event_bus.publish('RequestRunSingleStep', {
|
||||||
'step_name': 'step1',
|
'step_name': 'step1',
|
||||||
'config': config,
|
'config': config,
|
||||||
})
|
})
|
||||||
|
|
||||||
def run_step(self):
|
|
||||||
"""独立运行步骤1(旧版 parent 链上溯方式,保留兼容)。"""
|
|
||||||
# 验证输入
|
|
||||||
if self.use_ndwi_radio.isChecked():
|
|
||||||
# NDWI模式:需要影像文件
|
|
||||||
img_path = self.img_file.get_path()
|
|
||||||
if not img_path:
|
|
||||||
QMessageBox.warning(self, "输入错误", "请选择参考影像文件!")
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
# 现有掩膜模式:需要掩膜文件
|
|
||||||
mask_path = self.mask_file.get_path()
|
|
||||||
if not mask_path:
|
|
||||||
QMessageBox.warning(self, "输入错误", "请选择掩膜文件!")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 如果是shp文件,还需要影像文件
|
|
||||||
if mask_path.lower().endswith('.shp'):
|
|
||||||
img_path = self.img_file.get_path()
|
|
||||||
if not img_path:
|
|
||||||
QMessageBox.warning(self, "输入错误", "当使用shp文件时,需要提供参考影像用于栅格化!")
|
|
||||||
return
|
|
||||||
|
|
||||||
# 获取父窗口并运行步骤
|
|
||||||
parent = self.parent()
|
|
||||||
while parent and not hasattr(parent, 'run_single_step'):
|
|
||||||
parent = parent.parent()
|
|
||||||
|
|
||||||
if parent and hasattr(parent, 'run_single_step'):
|
|
||||||
config = {'step1': self.get_config()}
|
|
||||||
parent.run_single_step("step1", config)
|
|
||||||
@ -3,13 +3,14 @@
|
|||||||
全局样式与设计系统 (Design System) - 向后兼容增强版
|
全局样式与设计系统 (Design System) - 向后兼容增强版
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
class ModernStylesheet:
|
class ModernStylesheet:
|
||||||
"""现代化样式生成器"""
|
"""现代化样式生成器"""
|
||||||
|
|
||||||
# 1. 增强版色板 (Design Tokens)
|
# 1. 增强版色板 (Design Tokens)
|
||||||
COLORS = {
|
COLORS = {
|
||||||
'primary': '#0078D4',
|
'primary': '#0078D4',
|
||||||
'accent': '#0078D4', # ⚠️ 历史兼容:保留全局 30+ 处对 accent 的引用
|
'accent': '#0078D4', # ⚠️ 历史兼容:保留全局 30+ 处对 accent 的引用
|
||||||
'primary_hover': '#106EBE',
|
'primary_hover': '#106EBE',
|
||||||
'primary_pressed': '#005A9E',
|
'primary_pressed': '#005A9E',
|
||||||
'primary_light': '#E8F4FD',
|
'primary_light': '#E8F4FD',
|
||||||
@ -52,13 +53,20 @@ class ModernStylesheet:
|
|||||||
def get_main_stylesheet(cls) -> str:
|
def get_main_stylesheet(cls) -> str:
|
||||||
"""全局基础样式表"""
|
"""全局基础样式表"""
|
||||||
return f"""
|
return f"""
|
||||||
|
/* 全局默认透明背景,防止嵌套组件出现“灰色残影” */
|
||||||
QWidget {{
|
QWidget {{
|
||||||
background-color: {cls.COLORS['main_bg']};
|
|
||||||
color: {cls.COLORS['text_primary']};
|
color: {cls.COLORS['text_primary']};
|
||||||
font-family: {cls.FONTS['family']};
|
font-family: {cls.FONTS['family']};
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
background-color: transparent;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
/* 仅对最底层的窗口设置实体浅灰底色 */
|
||||||
|
QMainWindow, QDialog {{
|
||||||
|
background-color: {cls.COLORS['main_bg']};
|
||||||
|
}}
|
||||||
|
|
||||||
|
/* 阻断继承:让卡片内的元素保持纯白 */
|
||||||
QGroupBox, QFrame, QScrollArea, QListWidget, QTreeWidget, QTableWidget {{
|
QGroupBox, QFrame, QScrollArea, QListWidget, QTreeWidget, QTableWidget {{
|
||||||
background-color: {cls.COLORS['panel_bg']};
|
background-color: {cls.COLORS['panel_bg']};
|
||||||
}}
|
}}
|
||||||
@ -78,10 +86,13 @@ class ModernStylesheet:
|
|||||||
color: {cls.COLORS['primary']};
|
color: {cls.COLORS['primary']};
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
background-color: {cls.COLORS['main_bg']};
|
background-color: {cls.COLORS['main_bg']};
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
/* 【核心修复】彻底删除了 QRadioButton 的全部 CSS 代码,让其 100% 保持 Windows 原生样式 */
|
||||||
|
|
||||||
|
/* --- 纯净版:输入框 & 数字框 --- */
|
||||||
QLineEdit, QSpinBox, QDoubleSpinBox {{
|
QLineEdit, QSpinBox, QDoubleSpinBox {{
|
||||||
background-color: {cls.COLORS['panel_bg']};
|
background-color: {cls.COLORS['panel_bg']};
|
||||||
border: 1px solid {cls.COLORS['border']};
|
border: 1px solid {cls.COLORS['border']};
|
||||||
@ -103,6 +114,19 @@ class ModernStylesheet:
|
|||||||
border: 1px solid {cls.COLORS['border_light']};
|
border: 1px solid {cls.COLORS['border_light']};
|
||||||
}}
|
}}
|
||||||
|
|
||||||
|
QSpinBox > QLineEdit, QDoubleSpinBox > QLineEdit {{
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
padding: 0px;
|
||||||
|
min-height: 0px;
|
||||||
|
}}
|
||||||
|
|
||||||
|
QSpinBox::up-button, QSpinBox::down-button, QDoubleSpinBox::up-button, QDoubleSpinBox::down-button {{
|
||||||
|
width: 0px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
}}
|
||||||
|
|
||||||
QComboBox {{
|
QComboBox {{
|
||||||
border: 1px solid {cls.COLORS['border']};
|
border: 1px solid {cls.COLORS['border']};
|
||||||
border-radius: {cls.VARS['radius_md']};
|
border-radius: {cls.VARS['radius_md']};
|
||||||
@ -118,7 +142,7 @@ class ModernStylesheet:
|
|||||||
border-left: none;
|
border-left: none;
|
||||||
}}
|
}}
|
||||||
QComboBox::down-arrow {{
|
QComboBox::down-arrow {{
|
||||||
image: none;
|
image: none;
|
||||||
border-left: 4px solid transparent;
|
border-left: 4px solid transparent;
|
||||||
border-right: 4px solid transparent;
|
border-right: 4px solid transparent;
|
||||||
border-top: 5px solid {cls.COLORS['text_secondary']};
|
border-top: 5px solid {cls.COLORS['text_secondary']};
|
||||||
@ -137,7 +161,7 @@ class ModernStylesheet:
|
|||||||
QCheckBox::indicator:checked {{
|
QCheckBox::indicator:checked {{
|
||||||
background-color: {cls.COLORS['primary']};
|
background-color: {cls.COLORS['primary']};
|
||||||
border: 1px solid {cls.COLORS['primary']};
|
border: 1px solid {cls.COLORS['primary']};
|
||||||
image: none;
|
image: none;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
QScrollBar:vertical {{
|
QScrollBar:vertical {{
|
||||||
|
|||||||
Reference in New Issue
Block a user