ReportGeneratorLocal/info_core/MyQtClass.py

1050 lines
42 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from PySide6.QtWidgets import (QGroupBox, QVBoxLayout, QHBoxLayout, QCheckBox,
QPushButton, QDialog, QLineEdit, QFontComboBox,
QFileDialog, QMessageBox, QLabel, QWidget,
QTextBrowser, QApplication, QCompleter, QFrame,
QListWidgetItem, QListWidget, QAbstractItemView,
QStackedWidget, QStackedLayout)
from PySide6.QtCore import (QDateTime,QTimer,QDateTime,Signal,QSettings,
QSortFilterProxyModel, QSize)
from PySide6.QtGui import (QPixmap, QDragEnterEvent, QDropEvent, Qt, QIcon,
QFontMetrics)
import json, sys, os
from info_core.defines import *
class ConfigComboBoxGroup(QGroupBox):
"""可复用的配置下拉框组件(带完整样式)"""
def __init__(self, title, allow_add=True, parent=None, is_project = True):
super().__init__(title, parent)
self.allow_add = allow_add
self.is_project = is_project
self.config_dir = os.path.join(os.getcwd(), "config", title)
# 确保配置目录存在
os.makedirs(self.config_dir, exist_ok=True)
# 设置组框样式
self.setStyleSheet(GROUP_BOX_STYLE)
self.setMinimumSize(GROUP_BOX_MIN_WIDTH, GROUP_BOX_MIN_HEIGHT)
# 创建UI
self.init_ui()
self.load_config_files()
def init_ui(self):
"""初始化UI组件"""
layout = QVBoxLayout()
layout.setSpacing(GROUP_BOX_SPACING)
layout.setContentsMargins(*GROUP_BOX_MARGINS)
self.combo_box_init()
# 按钮布局
btn_layout = QHBoxLayout()
btn_layout.addStretch() # 将按钮推到右侧
self.modify_btn = self.create_button("修改")
self.modify_btn.clicked.connect(self.on_modify_clicked)
btn_layout.addWidget(self.modify_btn)
if self.allow_add:
self.add_btn = self.create_button("添加")
self.add_btn.clicked.connect(self.on_add_clicked)
btn_layout.addWidget(self.add_btn)
self.delete_btn = self.create_button("删除")
self.delete_btn.clicked.connect(self.on_delete_clicked)
btn_layout.addWidget(self.delete_btn)
layout.addWidget(self.combo_box)
layout.addLayout(btn_layout)
self.setLayout(layout)
def combo_box_init(self):
# 下拉框
self.combo_box = QFontComboBox()
self.combo_box.setEditable(True)
# 代理模型(支持中文模糊匹配)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setSourceModel(self.combo_box.model())
self.proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
# 补全器
self.completer = QCompleter(self.proxy_model, self.combo_box)
self.completer.setFilterMode(Qt.MatchContains)
self.combo_box.setCompleter(self.completer)
def create_button(self, text):
"""创建统一风格的按钮"""
btn = QPushButton(text)
btn.setStyleSheet(BUTTON_STYLE)
btn.setFixedSize(BUTTON_WIDTH, BUTTON_HEIGHT)
return btn
def load_config_files(self):
"""加载config文件夹中的JSON文件"""
self.combo_box.clear()
# 获取所有.json文件
config_files = [f for f in os.listdir(self.config_dir)
if f.endswith('.json')]
# 添加到下拉框(不带.json后缀
for file in config_files:
self.combo_box.addItem(file[:-5])
def on_modify_clicked(self):
"""修改按钮点击事件"""
if self.is_project:
project_widget = AddProjectDialog(self.config_dir, self, os.path.join(self.config_dir, self.combo_box.currentText() + ".json"))
project_widget.exec()
else:
person_widget = AddPersonnelDialog(self.config_dir, self, os.path.join(self.config_dir, self.combo_box.currentText() + ".json"))
person_widget.exec()
self.load_config_files()
def on_add_clicked(self):
"""添加按钮点击,创建默认ConfigEditDialog"""
if self.is_project:
project_widget = AddProjectDialog(self.config_dir, self)
project_widget.exec()
else:
person_widget = AddPersonnelDialog(self.config_dir, self)
person_widget.exec()
self.load_config_files()
def on_delete_clicked(self):
"""删除选中的项目"""
# 获取当前选中的项目名称
selected_project_name = self.combo_box.currentText()
if selected_project_name:
# 确认删除操作
confirm_message = f"你确定要删除项目 '{selected_project_name}' 吗?此操作不可恢复!"
reply = QMessageBox.question(self, '警告', confirm_message, QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
# 构建项目文件的路径
project_file_path = os.path.join(self.config_dir, selected_project_name + ".json")
# 检查文件是否存在并删除
if os.path.exists(project_file_path):
os.remove(project_file_path)
else:
QMessageBox.warning(self, '警告', f"项目文件 '{selected_project_name}.json' 不存在。")
# 从UI中移除该项目
self.combo_box.removeItem(self.combo_box.currentIndex())
# 重新加载配置文件列表
self.load_config_files()
else:
QMessageBox.information(self, '信息', '删除操作已取消。')
else:
QMessageBox.warning(self, '警告', '没有选中任何项目。')
def get_current_config_file(self):
"""获取当前选中的配置文件"""
return os.path.join(self.config_dir, self.combo_box.currentText() + ".json")
class AddProjectDialog(QDialog):
def __init__(self, dir, parent=None, path=None, combobox=None):
super(AddProjectDialog, self).__init__(parent)
self.setWindowTitle("添加/修改项目信息")
self.resize(800, 400) # 增加宽度以适应多列布局
self.dir = dir
self.path = path
self.combobox = combobox
# 定义所有表单项的配置
self.form_fields = [
{"key": "项目名称", "label": "项目名称:", "default": ""},
{"key": "风场名", "label": "风场名:", "default": ""},
{"key": "风场地址", "label": "风场地址:", "default": ""},
{"key": "甲方公司", "label": "甲方公司:", "default": ""},
{"key": "甲方负责人", "label": "甲方负责人:", "default": ""},
{"key": "甲方负责人电话", "label": "甲方负责人电话:", "default": ""},
{"key": "乙方公司", "label": "乙方公司:", "default": "武汉迪特聚能科技有限公司"},
{"key": "乙方负责人", "label": "乙方负责人:", "default": ""},
{"key": "乙方负责人电话", "label": "乙方负责人电话:", "default": ""},
{"key": "项目规格", "label": "项目规格:", "default": ""},
{"key": "项目工期", "label": "项目工期:", "default": ""},
]
# 加载现有数据(如果有)
self.data = {}
if self.path:
self.load_existing_data()
self.init_ui()
def load_existing_data(self):
"""加载现有项目数据"""
try:
with open(self.path, 'r', encoding='utf-8') as f:
self.data = json.load(f)
except json.JSONDecodeError:
QMessageBox.critical(self, "错误", "JSON 文件格式错误!")
except Exception as e:
QMessageBox.critical(self, "错误", f"加载文件失败: {str(e)}")
def init_ui(self):
"""初始化用户界面"""
main_layout = QVBoxLayout()
# 创建表单布局
form_widget = QWidget()
form_layout = QHBoxLayout(form_widget)
# 创建两列布局
column1 = QVBoxLayout()
column2 = QVBoxLayout()
# 将表单字段添加到列中
self.fields = {}
for i, field in enumerate(self.form_fields):
# 创建标签和输入框
label = QLabel(field["label"])
edit = QLineEdit()
# 设置默认值或从现有数据加载
if self.path and field["key"] in self.data:
edit.setText(self.data[field["key"]])
else:
edit.setText(field["default"])
# 存储输入框引用
self.fields[field["key"]] = edit
# 根据索引决定添加到哪一列
if i < len(self.form_fields) / 2:
column1.addWidget(label)
column1.addWidget(edit)
else:
column2.addWidget(label)
column2.addWidget(edit)
# 添加间距和弹性空间
column1.addStretch()
column2.addStretch()
# 将列添加到表单布局
form_layout.addLayout(column1)
form_layout.addSpacing(20) # 列间距
form_layout.addLayout(column2)
# 创建按钮
self.save_button = QPushButton("保存")
self.save_button.clicked.connect(self.on_save_button_clicked)
# 添加到主布局
main_layout.addWidget(form_widget)
main_layout.addWidget(self.save_button)
self.setLayout(main_layout)
def on_save_button_clicked(self):
"""保存按钮点击事件"""
# 收集所有字段数据
project_info = {
field["key"]: self.fields[field["key"]].text()
for field in self.form_fields
}
project_info["json路径"] = self.dir
# 验证保存目录
if not os.path.exists(self.dir) or not os.path.isdir(self.dir):
QMessageBox.warning(self, "警告", "请选择一个有效的保存项目信息的文件夹路径")
return
# 构建默认文件名
default_filename = f"{project_info['项目名称']}.json"
# 让用户选择文件名
file_path, _ = QFileDialog.getSaveFileName(
self,
"保存项目信息",
os.path.join(self.dir, default_filename),
"JSON Files (*.json)"
)
if not file_path:
QMessageBox.warning(self, "警告", "未选择文件名")
return
# 保存项目信息到文件
try:
with open(file_path, 'w', encoding='utf-8') as file:
json.dump(project_info, file, ensure_ascii=False, indent=4)
except Exception as e:
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
return
self.parent().load_config_files()
# 提示用户保存成功
QMessageBox.information(self, "信息", f"项目信息已成功保存到 {file_path}")
# 关闭对话框
self.close()
class AddPersonnelDialog(QDialog):
def __init__(self, dir, parent=None, path=None, combobox=None):
super(AddPersonnelDialog, self).__init__(parent)
self.setWindowTitle("添加/修改单次检查信息")
self.resize(800, 400) # 增加宽度以适应多列布局
self.dir = dir
self.path = path
self.combobox = combobox
# 定义所有表单项的配置
self.form_fields = [
{"key": "检查人员", "label": "检查人员:", "default": ""},
{"key": "厂家人员", "label": "厂家人员:", "default": ""},
{"key": "业主人员", "label": "业主人员:", "default": ""},
{"key": "数据处理人员", "label": "数据处理人员:", "default": ""},
{"key": "报告编制人员", "label": "报告编制人员:", "default": ""},
{"key": "机组型号", "label": "机组型号:", "default": ""},
{"key": "机组厂家", "label": "机组厂家:", "default": ""},
{"key": "叶片型号", "label": "叶片型号", "default": ""},
{"key": "叶片厂家", "label": "叶片厂家:", "default": ""},
{"key": "施工日期", "label": "施工日期", "default": "开始-结束"},
#{"key": "", "label": "", "default": ""},
# 可以轻松添加更多人员字段
]
# 添加复选框字段
self.checkbox_fields = [
{"key": "外部检查", "label": "外部检查", "default": False},
{"key": "内部检查", "label": "内部检查", "default": False},
{"key": "防雷检查", "label": "防雷检查", "default": False}
]
# 加载现有数据(如果有)
self.data = {}
if self.path:
self.load_existing_data()
self.init_ui()
def load_existing_data(self):
"""加载现有人员数据"""
try:
with open(self.path, 'r', encoding='utf-8') as f:
self.data = json.load(f)
except json.JSONDecodeError:
QMessageBox.critical(self, "错误", "JSON 文件格式错误!")
except Exception as e:
QMessageBox.critical(self, "错误", f"加载文件失败: {str(e)}")
def init_ui(self):
"""初始化用户界面"""
main_layout = QVBoxLayout()
# 创建表单布局
form_widget = QWidget()
form_layout = QHBoxLayout(form_widget)
# 创建两列布局
column1 = QVBoxLayout()
column2 = QVBoxLayout()
# 将表单字段添加到列中
self.fields = {}
for i, field in enumerate(self.form_fields):
# 创建标签和输入框
label = QLabel(field["label"])
edit = QLineEdit()
# 设置默认值或从现有数据加载
if self.path and field["key"] in self.data:
edit.setText(self.data[field["key"]])
else:
edit.setText(field["default"])
# 存储输入框引用
self.fields[field["key"]] = edit
# 根据索引决定添加到哪一列
if i < len(self.form_fields) / 2:
column1.addWidget(label)
column1.addWidget(edit)
else:
column2.addWidget(label)
column2.addWidget(edit)
# 添加间距和弹性空间
column1.addStretch()
column2.addStretch()
# 将列添加到表单布局
form_layout.addLayout(column1)
form_layout.addSpacing(20) # 列间距
form_layout.addLayout(column2)
# 创建按钮
self.save_button = QPushButton("保存")
self.save_button.clicked.connect(self.on_save_button_clicked)
# 创建复选框布局
checkbox_group = QGroupBox("检查类型")
checkbox_layout = QHBoxLayout()
# 添加复选框
self.checkboxes = {}
for field in self.checkbox_fields:
checkbox = QCheckBox(field["label"])
if self.path and field["key"] in self.data:
checkbox.setChecked(bool(self.data[field["key"]]))
else:
checkbox.setChecked(field["default"])
self.checkboxes[field["key"]] = checkbox
checkbox_layout.addWidget(checkbox)
checkbox_group.setLayout(checkbox_layout)
# 添加到主布局
main_layout.addWidget(checkbox_group)
main_layout.addWidget(form_widget)
main_layout.addWidget(self.save_button)
self.setLayout(main_layout)
def on_save_button_clicked(self):
"""保存按钮点击事件"""
# 收集所有字段数据
personnel_info = {
field["key"]: self.fields[field["key"]].text()
for field in self.form_fields
}
# 添加复选框数据
for key, checkbox in self.checkboxes.items():
personnel_info[key] = checkbox.isChecked()
personnel_info["json路径"] = self.dir
# 验证保存目录
if not os.path.exists(self.dir) or not os.path.isdir(self.dir):
QMessageBox.warning(self, "警告", "请选择一个有效的保存人员信息的文件夹路径")
return
# 构建默认文件名
default_filename = "人员信息.json"
# 让用户选择文件名
file_path, _ = QFileDialog.getSaveFileName(
self,
"保存人员信息",
os.path.join(self.dir, default_filename),
"JSON Files (*.json)"
)
if not file_path:
QMessageBox.warning(self, "警告", "未选择文件名")
return
# 保存人员信息到文件
try:
with open(file_path, 'w', encoding='utf-8') as file:
json.dump(personnel_info, file, ensure_ascii=False, indent=4)
except Exception as e:
QMessageBox.critical(self, "错误", f"保存文件失败: {str(e)}")
return
self.parent().load_config_files()
# 提示用户保存成功
QMessageBox.information(self, "信息", f"人员信息已成功保存到 {file_path}")
# 关闭对话框
self.close()
class SmartDropdown(QWidget):
"""
支持动态同步的智能下拉框
-全局可保存共享选择池
-可编辑共享池
存储形式:
"""
shared_pool = {} # 类变量存储共享选项
class MyFontComboBox(QFontComboBox):
def __init__(self, parent=None):
super().__init__(parent)
# 获取内部的QLineEdit部件
self.line_edit = self.lineEdit()
# 当获得焦点时全选文本
self.line_edit.selectionChanged.connect(self.select_all_text)
def select_all_text(self):
if self.line_edit.hasFocus():
self.line_edit.selectAll()
@classmethod
def update_shared_pool(cls, dropdown_type, options):
"""更新指定类别的共享选项"""
if not options: # 如果选项为空,添加默认选项
options = ["未选中"]
cls.shared_pool[dropdown_type] = list(options)
cls.save_shared_pool()
# 自动刷新所有该类型的下拉框
for widget in QApplication.allWidgets():
if isinstance(widget, SmartDropdown) and widget.dropdown_type == dropdown_type:
widget._load_options()
@classmethod
def save_shared_pool(cls):
"""保存共享池到QSettings"""
settings = QSettings()
settings.setValue("SmartDropdown/shared_pool", cls.shared_pool)
@classmethod
def load_shared_pool(cls):
"""从QSettings加载共享池"""
settings = QSettings()
pool = settings.value("SmartDropdown/shared_pool", {})
cls.shared_pool = {k: v for k, v in pool.items()} if pool else {}
# 确保每个类别至少有一个默认选项
for dropdown_type in cls.shared_pool:
if not cls.shared_pool[dropdown_type]:
cls.shared_pool[dropdown_type] = ["未选中"]
def __init__(self, dropdown_type, messagebrowser, label_text=None, parent=None):
super().__init__(parent)
self.dropdown_type = dropdown_type
self.label_text = label_text
self.messagebrowser = messagebrowser
# 初始化共享池
if not self.shared_pool:
self.load_shared_pool()
# 确保当前类型在共享池中存在
if self.dropdown_type not in self.shared_pool:
self.shared_pool[self.dropdown_type] = ["未选中"]
self.save_shared_pool()
self._setup_ui()
self._load_options()
self._initialize_defect_type()
self.messagebrowser.append(f"初始化下拉框: {dropdown_type}")
@property
def current_index(self):
"""当前选中索引0表示未选中"""
return self.combo_box.currentIndex()
@current_index.setter
def current_index(self, index):
if self.combo_box.count() > 0:
clamped_index = max(0, min(index, self.combo_box.count()-1))
self.combo_box.setCurrentIndex(clamped_index)
def _initialize_defect_type(self):
"""初始化缺陷类型选项,如果选项不足则自动重置"""
if self.dropdown_type == "缺陷类型":
# 检查当前选项数量是否不足
if len(self.shared_pool.get(self.dropdown_type, [])) <= 1:
default_options = [
"未选中...", "前缘涂层损伤(风损)", "表面裂纹", "腻子脱落", "漆面脱落",
"油污","布层破损(深层、不规则面状)", "裂纹(浅层线状)", "开裂(深层线带窄面状)",
"贯穿损伤", "鼓包", "凹陷", "雷击45d", "雷击zzw", "雷击ck",
"合模缝开裂", "透光", "雷击碳化", "褶皱", "脱胶", "缺胶",
"防雷线断裂", "开裂"
]
self.update_shared_pool(self.dropdown_type, default_options)
self.messagebrowser.append(
f"初始化提示: 检测到{self.dropdown_type}选项不足,已自动重置为默认选项集"
f"(原因: 第一次初始化或选项被清空)"
)
def _setup_ui(self):
"""初始化UI组件"""
self.combo_box = QFontComboBox()
self.combo_box.setEditable(True)
# 操作按钮
self.add_btn = QPushButton("+添加输入的类别")
self.del_btn = QPushButton("-删除选中类别")
self.refresh_btn = QPushButton()
self.refresh_btn.setIcon(QIcon.fromTheme("view-refresh")) # 使用系统刷新图标
self.reset_btn = QPushButton("重置")
# 设置按钮大小
for btn in [self.add_btn, self.del_btn, self.reset_btn]:
btn.setFixedWidth(150)
self.refresh_btn.setFixedWidth(30)
# 设置工具提示
self.add_btn.setToolTip("添加选项到共享池")
self.del_btn.setToolTip("从共享池删除当前选项")
self.refresh_btn.setToolTip("同步共享池选项")
self.reset_btn.setToolTip("重置为默认选项")
# 按钮布局
btn_layout = QHBoxLayout()
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.del_btn)
btn_layout.addWidget(self.reset_btn)
btn_layout.addWidget(self.refresh_btn)
btn_layout.setSpacing(2)
btn_layout.setContentsMargins(0, 0, 0, 0)
# 主布局
main_layout = QVBoxLayout()
main_layout.setSpacing(5)
if self.label_text:
main_layout.addWidget(QLabel(self.label_text))
main_layout.addWidget(self.combo_box)
main_layout.addLayout(btn_layout)
self.setLayout(main_layout)
# 信号连接
self.add_btn.clicked.connect(self._add_option)
self.del_btn.clicked.connect(self._del_option)
self.refresh_btn.clicked.connect(self._refresh_options)
self.reset_btn.clicked.connect(self._reset_options)
self.combo_box.currentIndexChanged.connect(self._update_buttons)
def _reset_options(self):
"""重置选项为默认值"""
if self.dropdown_type == "缺陷类型":
default_options = [
"未选中...", "前缘涂层损伤(风损)", "表面裂纹", "腻子脱落", "漆面脱落",
"油污","布层破损(深层、不规则面状)", "裂纹(浅层线状)", "开裂(深层线带窄面状)",
"贯穿损伤", "鼓包", "凹陷", "雷击45d", "雷击zzw", "雷击ck",
"合模缝开裂", "透光", "雷击碳化", "褶皱", "脱胶", "缺胶",
"防雷线断裂", "开裂"
]
reply = QMessageBox.question(
self, '确认重置',
'确定要重置缺陷类型为默认选项吗?此操作不可撤销!',
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
QMessageBox.StandardButton.No)
if reply == QMessageBox.StandardButton.Yes:
self.update_shared_pool(self.dropdown_type, default_options)
self.messagebrowser.append(f"已重置 {self.dropdown_type} 为默认选项")
else:
QMessageBox.information(
self, '提示',
f'当前类型 {self.dropdown_type} 没有预定义的重置选项')
def _load_options(self):
"""加载选项(确保只有一个未选中选项)"""
current_text = self.combo_box.currentText() if self.combo_box.count() > 0 else ""
self.combo_box.blockSignals(True) # 防止触发信号
self.combo_box.clear()
# 确保共享池中有当前类型的选项
if (self.dropdown_type not in self.shared_pool or
not self.shared_pool[self.dropdown_type]):
self.shared_pool[self.dropdown_type] = ["未选中"]
self.save_shared_pool()
# 直接从共享池加载选项
self.combo_box.addItems(self.shared_pool[self.dropdown_type])
# 恢复之前的选中项(如果有)
if current_text:
idx = self.combo_box.findText(current_text)
if idx >= 0:
self.combo_box.setCurrentIndex(idx)
self.combo_box.blockSignals(False)
self._update_buttons()
self.messagebrowser.append(f"加载选项: {self.dropdown_type} - {self.shared_pool[self.dropdown_type]}")
def _refresh_options(self):
"""手动刷新选项"""
self.messagebrowser.append(f"手动刷新选项: {self.dropdown_type}")
self._load_options()
def _update_buttons(self):
"""根据当前状态更新按钮可用性"""
has_options = len(self.shared_pool.get(self.dropdown_type, [])) > 0
has_selection = self.current_index >= 0
self.del_btn.setEnabled(has_options and has_selection)
self.add_btn.setEnabled(True)
self.refresh_btn.setEnabled(True)
self.reset_btn.setEnabled(True)
def _add_option(self):
"""添加新选项到共享池"""
new_option = self.combo_box.currentText().strip()
if not new_option:
return
if new_option not in self.shared_pool[self.dropdown_type]:
if QMessageBox.Yes == QMessageBox.question(
self, "确认添加",
f"添加 '{new_option}''{self.dropdown_type}' 选项池?",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No):
if self.dropdown_type not in self.shared_pool:
self.shared_pool[self.dropdown_type] = ["未选中"]
self.shared_pool[self.dropdown_type].append(new_option)
self.messagebrowser.append(f"添加选项: {new_option}{self.dropdown_type}")
self.update_shared_pool(self.dropdown_type, self.shared_pool[self.dropdown_type])
self.combo_box.setCurrentIndex(self.combo_box.findText(new_option))
else:
self.messagebrowser.append(f"添加失败: 选项 {new_option} 已存在于 {self.dropdown_type} 选项池")
def _del_option(self):
"""从共享池删除当前选项"""
if self.current_index < 0: # 无效选择
return
current_text = self.combo_box.currentText()
if current_text == "未选中":
self.messagebrowser.append("不能删除默认的'未选中'选项")
return
if QMessageBox.Yes == QMessageBox.question(
self, "确认删除",
f"'{self.dropdown_type}' 删除 '{current_text}'",
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No):
try:
self.shared_pool[self.dropdown_type].remove(current_text)
# 如果删除后选项为空,恢复默认选项
if not self.shared_pool[self.dropdown_type]:
self.shared_pool[self.dropdown_type] = ["未选中"]
self.messagebrowser.append(f"删除选项: {current_text}{self.dropdown_type}")
self.update_shared_pool(self.dropdown_type, self.shared_pool[self.dropdown_type])
except ValueError:
self.messagebrowser.append(f"删除失败: 选项 {current_text} 不存在")
class FolderDropWidget(QWidget):
selection_changed = Signal(list)
def __init__(self, parent=None):
super().__init__(parent)
self.setAcceptDrops(True)
self.current_dir = None
self.selected_folders = set()
self.init_ui()
self.apply_styles()
def init_ui(self):
main_layout = QVBoxLayout()
main_layout.setSpacing(5)
main_layout.setContentsMargins(0, 0, 0, 0)
# 顶部按钮区域
btn_layout = QHBoxLayout()
self.reset_btn = QPushButton("重新选择项目文件夹")
self.reset_btn.clicked.connect(self.reset_selection)
self.reset_btn.setFixedSize(120, 40)
self.reset_btn.setVisible(False)
self.clear_btn = QPushButton("清空")
self.clear_btn.clicked.connect(self.clear_selected)
self.clear_btn.setFixedSize(120, 40)
self.clear_btn.setVisible(False)
self.full_select_btn = QPushButton("全选")
self.full_select_btn.clicked.connect(self.select_all_folders)
self.full_select_btn.setFixedSize(120, 40)
self.full_select_btn.setVisible(False)
# 添加返回按钮
self.back_btn = QPushButton("返回项目选择")
self.back_btn.clicked.connect(lambda: (self.stacked_layout.setCurrentIndex(1) or self.clear_selected() or self.reset_btn.setVisible(True) or self.back_btn.setVisible(False)))
self.back_btn.setVisible(False)
btn_layout.addWidget(self.reset_btn)
btn_layout.addWidget(self.back_btn)
btn_layout.addWidget(self.full_select_btn)
btn_layout.addWidget(self.clear_btn)
btn_layout.addStretch()
main_layout.addLayout(btn_layout)
# 主内容区域 - 使用堆叠布局管理不同状态
self.stacked_layout = QStackedLayout()
# 状态1: 未选择文件夹时的提示
self.prompt_container = QWidget()
prompt_layout = QVBoxLayout()
self.prompt_label = QLabel("拖放文件夹到此处或点击选择文件夹")
self.prompt_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.prompt_label.mousePressEvent = self.select_folder
prompt_layout.addWidget(self.prompt_label)
self.prompt_container.setLayout(prompt_layout)
self.stacked_layout.addWidget(self.prompt_container)
# 状态2: 显示第一层文件夹 (项目选择)
self.level1_container = QWidget()
level1_main_layout = QVBoxLayout()
level1_main_layout.setSpacing(0)
level1_main_layout.setContentsMargins(0, 0, 0, 0)
# 添加状态标签
self.level1_title = QLabel("项目选择")
self.level1_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
level1_main_layout.addWidget(self.level1_title)
self.level1_list = QListWidget()
self.level1_list.setIconSize(QSize(48, 48))
self.level1_list.setViewMode(QListWidget.ViewMode.IconMode)
self.level1_list.setResizeMode(QListWidget.ResizeMode.Adjust)
self.level1_list.setMovement(QListWidget.Movement.Static)
self.level1_list.setSpacing(10)
self.level1_list.itemDoubleClicked.connect(self.show_level2_folders)
level1_main_layout.addWidget(self.level1_list)
self.level1_container.setLayout(level1_main_layout)
self.stacked_layout.addWidget(self.level1_container)
# 状态3: 显示第二层文件夹 (机组选择)
self.level2_container = QWidget()
level2_main_layout = QVBoxLayout()
level2_main_layout.setSpacing(0)
level2_main_layout.setContentsMargins(0, 0, 0, 0)
# 添加状态标签
self.level2_title = QLabel("机组选择")
self.level2_title.setAlignment(Qt.AlignmentFlag.AlignCenter)
level2_main_layout.addWidget(self.level2_title)
self.level2_list = QListWidget()
self.level2_list.setIconSize(QSize(48, 48))
self.level2_list.setViewMode(QListWidget.ViewMode.IconMode)
self.level2_list.setResizeMode(QListWidget.ResizeMode.Adjust)
self.level2_list.setMovement(QListWidget.Movement.Static)
self.level2_list.setSpacing(10)
level2_main_layout.addWidget(self.level2_list)
self.level2_container.setLayout(level2_main_layout)
self.stacked_layout.addWidget(self.level2_container)
# 将堆叠布局添加到主布局
stack_container = QWidget()
stack_container.setLayout(self.stacked_layout)
main_layout.addWidget(stack_container)
# 已选目录列表 (始终显示在底部)
self.selected_group = QGroupBox("已选机组")
self.selected_group.setVisible(False)
selected_layout = QVBoxLayout()
self.selected_list = QListWidget()
self.selected_list.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection)
selected_layout.addWidget(self.selected_list)
self.selected_group.setLayout(selected_layout)
main_layout.addWidget(self.selected_group)
self.setLayout(main_layout)
def select_all_folders(self):
self.selected_folders.update(
set(item.data(Qt.ItemDataRole.UserRole) for item in self.level2_list.findItems("", Qt.MatchFlag.MatchContains))
)
self.update_checkbox_states()
self.selection_changed.emit(self.get_selected_folders())
def apply_styles(self):
self.prompt_label.setStyleSheet(f"""
{PATH_DISPLAY_STYLE}
border: 2px dashed {SECONDARY_COLOR};
min-height: 150px;
""")
self.level1_list.setStyleSheet(f"""
QListWidget {{
background-color: #f8f9fa;
min-height: 150px;
}}
""")
self.level2_list.setStyleSheet(f"""
QListWidget {{
background-color: #f8f9fa;
min-height: 150px;
}}
""")
self.level1_title.setStyleSheet(f"""
{LABEL_STYLE}
font-size: {TITLE_FONT_SIZE}pt;
font-weight: bold;
padding: 10px;
""")
self.level2_title.setStyleSheet(f"""
{LABEL_STYLE}
font-size: {TITLE_FONT_SIZE}pt;
font-weight: bold;
padding: 10px;
""")
self.selected_group.setStyleSheet(GROUP_BOX_STYLE)
self.full_select_btn.setStyleSheet(BUTTON_STYLE)
self.reset_btn.setStyleSheet(BUTTON_STYLE)
self.clear_btn.setStyleSheet(BUTTON_STYLE)
self.back_btn.setStyleSheet(BUTTON_STYLE)
def select_folder(self, event=None):
folder = QFileDialog.getExistingDirectory(self, "选择文件夹")
if folder:
self.load_folder(folder)
def reset_selection(self):
self.current_dir = None
self.selected_folders.clear()
self.level1_list.clear()
self.level2_list.clear()
self.selected_list.clear()
self.stacked_layout.setCurrentIndex(0) # 显示提示
self.reset_btn.setVisible(False)
self.clear_btn.setVisible(False)
self.back_btn.setVisible(False)
self.selected_group.setVisible(False)
self.selection_changed.emit([])
def clear_selected(self):
self.selected_folders.clear()
self.selected_list.clear()
self.update_checkbox_states()
self.selection_changed.emit([])
def load_folder(self, path):
self.current_dir = path
self.load_level1_folders(path)
self.stacked_layout.setCurrentIndex(1) # 显示第一层
self.reset_btn.setVisible(True)
self.back_btn.setVisible(False)
self.clear_btn.setVisible(True)
self.full_select_btn.setVisible(True)
self.selected_group.setVisible(True)
def dragEnterEvent(self, event: QDragEnterEvent):
if event.mimeData().hasUrls():
event.acceptProposedAction()
def dropEvent(self, event: QDropEvent):
for url in event.mimeData().urls():
path = url.toLocalFile()
if os.path.isdir(path):
self.load_folder(path)
break
def load_level1_folders(self, root_path):
self.level1_list.clear()
self.level2_list.clear()
for item in os.listdir(root_path):
item_path = os.path.join(root_path, item)
if os.path.isdir(item_path):
item_widget = QListWidgetItem(QIcon.fromTheme("folder"), item)
item_widget.setData(Qt.ItemDataRole.UserRole, item_path)
item_widget.setSizeHint(QSize(80, 80))
item_widget.setTextAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignBottom)
self.level1_list.addItem(item_widget)
def show_level2_folders(self, item):
self.level2_list.clear()
self.reset_btn.setVisible(False)
self.back_btn.setVisible(True)
folder_path = item.data(Qt.ItemDataRole.UserRole)
for sub_item in os.listdir(folder_path):
sub_item_path = os.path.join(folder_path, sub_item)
if os.path.isdir(sub_item_path):
list_item = QListWidgetItem()
list_item.setSizeHint(QSize(100, 80))
list_item.setData(Qt.ItemDataRole.UserRole, sub_item_path)
widget = QWidget()
layout = QVBoxLayout()
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
icon_label = QLabel()
icon_label.setPixmap(QIcon.fromTheme("folder").pixmap(48, 48))
icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
layout.addWidget(icon_label)
name_check_layout = QHBoxLayout()
checkbox = QCheckBox(sub_item)
checkbox.setStyleSheet(f"""
{PRIMARY_CHECKBOX_STYLE}
QCheckBox::indicator:checked {{
background-color: {PRIMARY_COLOR};
border: 1px solid {PRIMARY_COLOR};
}}
""")
checkbox.stateChanged.connect(lambda state, path=sub_item_path: self.toggle_folder_selection(path, state))
checkbox.setChecked(sub_item_path in self.selected_folders)
name_check_layout.addWidget(checkbox)
layout.addLayout(name_check_layout)
widget.setLayout(layout)
list_item.setSizeHint(widget.sizeHint())
self.level2_list.addItem(list_item)
self.level2_list.setItemWidget(list_item, widget)
self.stacked_layout.setCurrentIndex(2) # 显示第二层
def toggle_folder_selection(self, folder_path, state):
if state == Qt.CheckState.Checked.value:
self.selected_folders.add(folder_path)
item = QListWidgetItem(folder_path)
self.selected_list.addItem(item)
else:
self.selected_folders.discard(folder_path)
items = self.selected_list.findItems(folder_path, Qt.MatchFlag.MatchExactly)
for item in items:
self.selected_list.takeItem(self.selected_list.row(item))
self.selection_changed.emit(self.get_selected_folders())
def update_checkbox_states(self):
for i in range(self.level2_list.count()):
item = self.level2_list.item(i)
widget = self.level2_list.itemWidget(item)
if widget:
checkbox = widget.findChild(QCheckBox)
if checkbox:
path = item.data(Qt.ItemDataRole.UserRole)
checkbox.setChecked(path in self.selected_folders)
def get_selected_folders(self):
return list(self.selected_folders)
class DraggableLine(QFrame):
def __init__(self, parent=None):
super().__init__(parent)
self.setFrameShape(QFrame.HLine)
self.setFrameShadow(QFrame.Sunken)
self.setLineWidth(2)
self.setFixedHeight(10)
self.dragging = False
self.setCursor(Qt.SizeVerCursor)
def mousePressEvent(self, event):
if event.button() == Qt.LeftButton:
self.dragging = True
self.start_pos = event.globalPos()
def mouseMoveEvent(self, event):
if self.dragging:
delta = event.globalPos() - self.start_pos
self.start_pos = event.globalPos()
# 通知父窗口调整布局
self.parent().adjust_row_height(delta.y())
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
self.dragging = False