diff --git a/RenderPipelineFile/toolkit/plugin_configurator/main.py b/RenderPipelineFile/toolkit/plugin_configurator/main.py index 9410f080..fd393ae8 100644 --- a/RenderPipelineFile/toolkit/plugin_configurator/main.py +++ b/RenderPipelineFile/toolkit/plugin_configurator/main.py @@ -49,6 +49,8 @@ from rplibs.pyqt_imports import * #noqa # Load the generated UI Layout from ui.main_window_generated import Ui_MainWindow # noqa +from ui.widgets import UniversalMessageDialog + from rpcore.pluginbase.manager import PluginManager # noqa from rpcore.util.network_communication import NetworkCommunication # noqa from rpcore.mount_manager import MountManager # noqa @@ -119,9 +121,13 @@ class PluginConfigurator(QMainWindow, Ui_MainWindow): self, "Warning", msg, QMessageBox.Yes, QMessageBox.No) if reply == QMessageBox.Yes: - QMessageBox.information( - self, "Success", - "Settings have been reset! You may have to restart the pipeline.") + UniversalMessageDialog.show_success( + self, + "Success", + "Settings have been reset! You may have to restart the pipeline.", + show_cancel=False, + confirm_text="OK" + ) self._plugin_mgr.reset_plugin_settings(self._current_plugin) # Save config diff --git a/icons/sueccess_icon.png b/icons/sueccess_icon.png deleted file mode 100644 index 4dda99f9..00000000 Binary files a/icons/sueccess_icon.png and /dev/null differ diff --git a/icons/sure_delete_icon.png b/icons/sure_delete_icon.png deleted file mode 100644 index 540f03b4..00000000 Binary files a/icons/sure_delete_icon.png and /dev/null differ diff --git a/project/project_manager.py b/project/project_manager.py index f4613fbd..24562bd8 100644 --- a/project/project_manager.py +++ b/project/project_manager.py @@ -19,7 +19,7 @@ from PyQt5.QtWidgets import ( from PyQt5.QtCore import Qt # 导入自定义对话框 -from ui.widgets import NewProjectDialog +from ui.widgets import NewProjectDialog, UniversalMessageDialog class ProjectManager: """项目管理器 - 统一管理项目的生命周期""" @@ -103,11 +103,23 @@ class ProjectManager: parent_window.current_project_path = full_project_path # 显示成功消息 - QMessageBox.information(parent_window, "成功", f"项目 '{project_name}' 创建成功!") + UniversalMessageDialog.show_success( + parent_window, + "成功", + f"项目 '{project_name}' 创建成功!", + show_cancel=False, + confirm_text="确认" + ) return True except Exception as e: - QMessageBox.critical(parent_window, "错误", f"创建项目失败: {str(e)}") + UniversalMessageDialog.show_error( + parent_window, + "错误", + f"创建项目失败: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) return False def openProject(self, parent_window): @@ -127,7 +139,13 @@ class ProjectManager: # 检查是否是有效的项目文件夹 config_file = os.path.join(project_path, "project.json") if not os.path.exists(config_file): - QMessageBox.warning(parent_window, "警告", "选择的不是有效的项目文件夹!") + UniversalMessageDialog.show_warning( + parent_window, + "警告", + "选择的不是有效的项目文件夹!", + show_cancel=False, + confirm_text="确认" + ) return False # 读取项目配置 @@ -161,7 +179,13 @@ class ProjectManager: if hasattr(parent_window, 'fileView') and hasattr(parent_window, 'fileModel'): parent_window.fileView.setRootIndex(parent_window.fileModel.index(project_path)) - QMessageBox.information(parent_window, "成功", "项目加载成功!") + UniversalMessageDialog.show_success( + parent_window, + "成功", + "项目加载成功!", + show_cancel=False, + confirm_text="确认" + ) return True # 检查场景文件 # scene_file = os.path.join(project_path, "scenes", "scene.bam") @@ -200,7 +224,13 @@ class ProjectManager: # return False except Exception as e: - QMessageBox.critical(parent_window, "错误", f"加载项目时发生错误:{str(e)}") + UniversalMessageDialog.show_error( + parent_window, + "错误", + f"加载项目时发生错误:{str(e)}", + show_cancel=False, + confirm_text="确认" + ) return False def openProjectForPath(self, project_path, parent_window=None): @@ -217,7 +247,13 @@ class ProjectManager: config_file = os.path.join(project_path, "project.json") if not os.path.exists(config_file): if parent_window: - QMessageBox.warning(parent_window, "警告", f"选择的不是有效的项目文件夹!{project_path}") + UniversalMessageDialog.show_warning( + parent_window, + "警告", + f"选择的不是有效的项目文件夹!{project_path}", + show_cancel=False, + confirm_text="确认" + ) else: print("警告: 选择的不是有效的项目文件夹!") return False @@ -255,13 +291,25 @@ class ProjectManager: if hasattr(parent_window, 'fileView') and hasattr(parent_window, 'fileModel'): parent_window.fileView.setRootIndex(parent_window.fileModel.index(project_path)) - QMessageBox.information(parent_window, "成功", "项目加载成功!") + UniversalMessageDialog.show_success( + parent_window, + "成功", + "项目加载成功!", + show_cancel=False, + confirm_text="确认" + ) print(f"项目 '{project_path}' 加载成功!") return True else: if parent_window: - QMessageBox.warning(parent_window, "错误", "加载场景失败!") + UniversalMessageDialog.show_warning( + parent_window, + "错误", + "加载场景失败!", + show_cancel=False, + confirm_text="确认" + ) else: print("错误: 加载场景失败!") return False @@ -269,7 +317,13 @@ class ProjectManager: except Exception as e: error_msg = f"加载项目时发生错误:{str(e)}" if parent_window: - QMessageBox.critical(parent_window, "错误", error_msg) + UniversalMessageDialog.show_error( + parent_window, + "错误", + error_msg, + show_cancel=False, + confirm_text="确认" + ) else: print(error_msg) return False @@ -283,7 +337,13 @@ class ProjectManager: if hasattr(parent_window, 'current_project_path'): self.current_project_path = parent_window.current_project_path else: - QMessageBox.warning(parent_window, "警告", "请先创建或打开一个项目!") + UniversalMessageDialog.show_warning( + parent_window, + "警告", + "请先创建或打开一个项目!", + show_cancel=False, + confirm_text="确认" + ) return False project_path = self.current_project_path @@ -324,14 +384,32 @@ class ProjectManager: project_name = os.path.basename(project_path) self.updateWindowTitle(parent_window, project_name) - QMessageBox.information(parent_window, "成功", "项目保存成功!") + UniversalMessageDialog.show_success( + parent_window, + "成功", + "项目保存成功!", + show_cancel=False, + confirm_text="确认" + ) return True else: - QMessageBox.warning(parent_window, "错误", "保存场景失败!") + UniversalMessageDialog.show_warning( + parent_window, + "错误", + "保存场景失败!", + show_cancel=False, + confirm_text="确认" + ) return False except Exception as e: - QMessageBox.critical(parent_window, "错误", f"保存项目时发生错误:{str(e)}") + UniversalMessageDialog.show_error( + parent_window, + "错误", + f"保存项目时发生错误:{str(e)}", + show_cancel=False, + confirm_text="确认" + ) return False # ==================== 项目打包功能 ==================== @@ -1099,4 +1177,4 @@ def updateWindowTitle(window, project_name=None): if project_name: window.setWindowTitle(f"{base_title} - {project_name}") else: - window.setWindowTitle(base_title) \ No newline at end of file + window.setWindowTitle(base_title) diff --git a/ui/icon_manager.py b/ui/icon_manager.py index f3a9d375..8bfebceb 100644 --- a/ui/icon_manager.py +++ b/ui/icon_manager.py @@ -64,6 +64,11 @@ class IconManager: 'minimize_icon': 'minimize_icon.png', 'windowing_icon': 'windowing_icon.png', 'close_icon': 'close_icon.png', + + # 弹窗图标 + 'success_icon': 'success_icon.png', + 'warning_icon': 'warning_icon.png', + 'fail_icon': 'delete_fail_icon.png', } # 初始化默认图标 diff --git a/ui/main_window.py b/ui/main_window.py index 0afd939b..73e8c3bc 100644 --- a/ui/main_window.py +++ b/ui/main_window.py @@ -1,4 +1,4 @@ -""" +""" 主窗口设置模块 负责主窗口的界面构建和事件绑定: @@ -21,7 +21,9 @@ from PyQt5.QtCore import Qt, QDir, QTimer, QSize, QPoint, QUrl, QRect from direct.showbase.ShowBaseGlobal import aspect2d from panda3d.core import OrthographicLens -from ui.widgets import CustomPanda3DWidget, CustomFileView, CustomTreeWidget,CustomAssetsTreeWidget, CustomConsoleDockWidget +from ui.widgets import (CustomPanda3DWidget, CustomFileView, CustomTreeWidget, + CustomAssetsTreeWidget, CustomConsoleDockWidget, + UniversalMessageDialog) from ui.icon_manager import get_icon_manager, get_icon @@ -226,6 +228,12 @@ class StyledTerrainDialog(QDialog): widget.setRange(field.get("min", 0), field.get("max", 1000)) widget.setSingleStep(field.get("step", 1)) widget.setValue(field.get("value", field.get("default", 0))) + elif widget_type == "text": + widget = QLineEdit() + widget.setText(field.get("value", field.get("default", ""))) + if field.get("placeholder"): + widget.setPlaceholderText(field.get("placeholder")) + widget.setClearButtonEnabled(True) else: widget = QDoubleSpinBox() widget.setRange(field.get("min", 0.0), field.get("max", 1000.0)) @@ -345,6 +353,8 @@ class StyledTerrainDialog(QDialog): widget = self.field_widgets.get(name) if isinstance(widget, (QDoubleSpinBox, QSpinBox)): return widget.value() + if isinstance(widget, QLineEdit): + return widget.text() return None try: from PyQt5.QtWebEngineWidgets import QWebEngineView @@ -3052,7 +3062,13 @@ class MainWindow(QMainWindow): try: selected_item = self.treeWidget.currentItem() if not selected_item: - QMessageBox.information(self, "提示", "请先选择要复制的节点") + UniversalMessageDialog.show_warning( + self, + "提示", + "请先选择要复制的节点", + show_cancel=False, + confirm_text="确认" + ) return # 获取选中的节点 @@ -3064,35 +3080,71 @@ class MainWindow(QMainWindow): selected_node = getattr(self.world.selection, 'selectedNode', None) if not selected_node or selected_node.isEmpty(): - QMessageBox.warning(self, "错误", "无法获取选中节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法获取选中节点", + show_cancel=False, + confirm_text="确认" + ) return # 检查是否是根节点 if selected_node.getName() == "render": - QMessageBox.warning(self, "错误", "不能复制根节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "不能复制根节点", + show_cancel=False, + confirm_text="确认" + ) return # 序列化节点数据 node_data = self.world.scene_manager.serializeNodeForCopy(selected_node) if not node_data: - QMessageBox.warning(self, "错误", "无法序列化选中节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法序列化选中节点", + show_cancel=False, + confirm_text="确认" + ) return # 存储到剪切板 self.clipboard = [node_data] self.clipboard_mode = "copy" - QMessageBox.information(self, "成功", "节点已复制到剪切板") + UniversalMessageDialog.show_success( + self, + "成功", + "节点已复制到剪切板", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"复制操作失败: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"复制操作失败: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onCut(self): """剪切操作""" try: selected_item = self.treeWidget.currentItem() if not selected_item: - QMessageBox.information(self, "提示", "请先选择要剪切的节点") + UniversalMessageDialog.show_warning( + self, + "提示", + "请先选择要剪切的节点", + show_cancel=False, + confirm_text="确认" + ) return # 获取选中的节点 @@ -3104,18 +3156,36 @@ class MainWindow(QMainWindow): selected_node = getattr(self.world.selection, 'selectedNode', None) if not selected_node or selected_node.isEmpty(): - QMessageBox.warning(self, "错误", "无法获取选中节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法获取选中节点", + show_cancel=False, + confirm_text="确认" + ) return # 检查是否是根节点或特殊节点 if selected_node.getName() in ["render", "camera", "ambientLight", "directionalLight"]: - QMessageBox.warning(self, "错误", "不能剪切根节点或系统节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "不能剪切根节点或系统节点", + show_cancel=False, + confirm_text="确认" + ) return # 序列化节点数据 node_data = self.world.scene_manager.serializeNodeForCopy(selected_node) if not node_data: - QMessageBox.warning(self, "错误", "无法序列化选中节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法序列化选中节点", + show_cancel=False, + confirm_text="确认" + ) return # 存储到剪切板 @@ -3125,16 +3195,34 @@ class MainWindow(QMainWindow): # 删除原节点 self.treeWidget.delete_items([selected_item]) - QMessageBox.information(self, "成功", "节点已剪切到剪切板") + UniversalMessageDialog.show_success( + self, + "成功", + "节点已剪切到剪切板", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"剪切操作失败: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"剪切操作失败: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onPaste(self): """粘贴操作""" try: if not self.clipboard: - QMessageBox.information(self, "提示", "剪切板为空") + UniversalMessageDialog.show_warning( + self, + "提示", + "剪切板为空", + show_cancel=False, + confirm_text="确认" + ) return # 获取粘贴目标节点 @@ -3175,13 +3263,25 @@ class MainWindow(QMainWindow): # 检查父节点有效性 if not parent_node or parent_node.isEmpty(): - QMessageBox.warning(self, "错误", "无法获取有效的父节点") + UniversalMessageDialog.show_warning( + self, + "错误", + "无法获取有效的父节点", + show_cancel=False, + confirm_text="确认" + ) return # 检查目标节点是否为允许的父节点类型 parent_name = parent_node.getName() if parent_name in ["camera", "ambientLight", "directionalLight"]: - QMessageBox.warning(self, "错误", "不能粘贴到该类型节点下") + UniversalMessageDialog.show_warning( + self, + "错误", + "不能粘贴到该类型节点下", + show_cancel=False, + confirm_text="确认" + ) return # 粘贴节点 @@ -3200,10 +3300,22 @@ class MainWindow(QMainWindow): self.clipboard.clear() self.clipboard_mode = None - QMessageBox.information(self, "成功", f"已粘贴 {len(pasted_nodes)} 个节点") + UniversalMessageDialog.show_success( + self, + "成功", + f"已粘贴 {len(pasted_nodes)} 个节点", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"粘贴操作失败: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"粘贴操作失败: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def _serializeNode(self, node): """序列化节点数据""" @@ -3330,13 +3442,31 @@ class MainWindow(QMainWindow): print("成功操作") else: print("撤销失败") - QMessageBox.information(self,"提示","撤销操作失败") + UniversalMessageDialog.show_warning( + self, + "提示", + "撤销操作失败", + show_cancel=False, + confirm_text="确认" + ) else: print("没有可撤销的操作") - QMessageBox.information(self,"提示","没有可撤销的操作") + UniversalMessageDialog.show_warning( + self, + "提示", + "没有可撤销的操作", + show_cancel=False, + confirm_text="确认" + ) else: print("命令管理器未初始化") - QMessageBox.information(self,"提示","命令系统未初始化") + UniversalMessageDialog.show_warning( + self, + "提示", + "命令系统未初始化", + show_cancel=False, + confirm_text="确认" + ) def onRedo(self): """重做操作""" @@ -3347,13 +3477,31 @@ class MainWindow(QMainWindow): print("成功重做") else: print("重做失败") - QMessageBox.information(self,"提示","重做操作失败") + UniversalMessageDialog.show_warning( + self, + "提示", + "重做操作失败", + show_cancel=False, + confirm_text="确认" + ) else: print("没有可重做的操作") - QMessageBox.information(self,"提示","没有可重做的操作") + UniversalMessageDialog.show_warning( + self, + "提示", + "没有可重做的操作", + show_cancel=False, + confirm_text="确认" + ) else: print("命令管理器未初始化") - QMessageBox.information(self,"提示","命令系统未初始化") + UniversalMessageDialog.show_warning( + self, + "提示", + "命令系统未初始化", + show_cancel=False, + confirm_text="确认" + ) def onCreateCesiumView(self): if hasattr(self.world,'gui_manager') and self.world.gui_manager: @@ -3404,15 +3552,16 @@ class MainWindow(QMainWindow): break if not cesium_view_exists: - reply = QMessageBox.question( + result = UniversalMessageDialog.show_info( self, - '提示', - 'Cesium 地图视图尚未打开,是否先打开地图视图?', - QMessageBox.Yes | QMessageBox.No, - QMessageBox.Yes + "提示", + "Cesium 地图视图尚未打开,是否先打开地图视图?", + show_cancel=True, + confirm_text="打开视图", + cancel_text="取消" ) - if reply == QMessageBox.Yes: + if result == QDialog.Accepted: self.onCreateCesiumView() # 给一点时间让 Cesium 视图加载 QTimer.singleShot(1000, self.showAddModelDialog) @@ -3457,22 +3606,28 @@ class MainWindow(QMainWindow): ) if success: - QMessageBox.information( + UniversalMessageDialog.show_success( self, "成功", - f"模型已成功添加到地图!\n模型ID: {model_id}" + f"模型已成功添加到地图!\n模型ID: {model_id}", + show_cancel=False, + confirm_text="确认" ) else: - QMessageBox.warning( + UniversalMessageDialog.show_warning( self, "失败", - "添加模型失败,请检查控制台输出" + "添加模型失败,请检查控制台输出", + show_cancel=False, + confirm_text="确认" ) except Exception as e: - QMessageBox.critical( + UniversalMessageDialog.show_error( self, "错误", - f"添加模型时发生错误:\n{str(e)}" + f"添加模型时发生错误:\n{str(e)}", + show_cancel=False, + confirm_text="确认" ) @@ -3547,43 +3702,103 @@ class MainWindow(QMainWindow): return None, False def onLoadCesiumTileset(self): - dialog = self.createStyledInputDialog( - self, - "加载 Cesium 3D Tiles", - "输入 tileset.json URL:", - QLineEdit.Normal, - "https://assets.ion.cesium.com/96128/tileset.json" - ) + """加载 Cesium 3D Tiles""" + fields = [ + { + "name": "url", + "label": "Tileset URL", + "type": "text", + "value": "https://assets.ion.cesium.com/96128/tileset.json", + "placeholder": "输入 tileset.json 地址", + "label_width": 110 + }, + { + "name": "longitude", + "label": "经度(°)", + "type": "double", + "min": -180.0, + "max": 180.0, + "step": 0.1, + "decimals": 6, + "value": 0.0, + "label_width": 110 + }, + { + "name": "latitude", + "label": "纬度(°)", + "type": "double", + "min": -90.0, + "max": 90.0, + "step": 0.1, + "decimals": 6, + "value": 0.0, + "label_width": 110 + }, + { + "name": "height", + "label": "高度(米)", + "type": "double", + "min": -10000.0, + "max": 10000.0, + "step": 1.0, + "decimals": 2, + "value": 0.0, + "label_width": 110 + } + ] + + dialog = StyledTerrainDialog(self, "加载 Cesium 3D Tiles", fields, primary_text="加载", secondary_text="取消") if dialog.exec_() == QDialog.Accepted: - url = dialog.textValue() - if url: - try: - # 生成唯一的 tileset 名称 - import uuid - tileset_name = f"tileset_{uuid.uuid4().hex[:8]}" + url = dialog.get_value("url") + if not url or not url.strip(): + UniversalMessageDialog.show_warning( + self, + "提示", + "请输入有效的 tileset URL", + show_cancel=False, + confirm_text="确认" + ) + return - # 加载 tileset - if hasattr(self.world, 'addCesiumTileset'): - success = self.world.addCesiumTileset(tileset_name, url, (0, 0, 0)) - if success: - QMessageBox.information( - self, - "成功", - f"Cesium 3D Tiles 已加载到场景中!\n名称: {tileset_name}" - ) - else: - QMessageBox.warning( - self, - "失败", - "加载 Cesium 3D Tiles 失败" - ) - except Exception as e: - QMessageBox.critical( - self, - "错误", - f"加载 Cesium 3D Tiles 时发生错误:\n{str(e)}" + longitude = dialog.get_value("longitude") + latitude = dialog.get_value("latitude") + height = dialog.get_value("height") + + try: + import uuid + tileset_name = f"tileset_{uuid.uuid4().hex[:8]}" + + if hasattr(self.world, 'addCesiumTileset'): + success = self.world.addCesiumTileset( + tileset_name, + url.strip(), + (longitude, latitude, height) ) + if success: + UniversalMessageDialog.show_success( + self, + "成功", + f"Cesium 3D Tiles 已加载到场景中!\n名称: {tileset_name}", + show_cancel=False, + confirm_text="确认" + ) + else: + UniversalMessageDialog.show_warning( + self, + "失败", + "加载 Cesium 3D Tiles 失败", + show_cancel=False, + confirm_text="确认" + ) + except Exception as e: + UniversalMessageDialog.show_error( + self, + "错误", + f"加载 Cesium 3D Tiles 时发生错误:\n{str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onToolChanged(self, button): """工具切换事件处理""" @@ -4310,7 +4525,9 @@ class MainWindow(QMainWindow): """创建脚本按钮事件""" script_name = self.scriptNameEdit.text().strip() if not script_name: - QMessageBox.warning(self, "错误", "请输入脚本名称!") + UniversalMessageDialog.show_warning( + self, "警告", "请输入脚本名称!", show_cancel=False, confirm_text="确认" + ) return template = self.templateCombo.currentText() @@ -4318,17 +4535,35 @@ class MainWindow(QMainWindow): try: success = self.world.createScript(script_name, template) if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 创建成功!") + UniversalMessageDialog.show_success( + self, + "成功", + f"脚本 '{script_name}' 创建成功!", + show_cancel=False, + confirm_text="确认" + ) self.scriptNameEdit.clear() self.refreshScriptsList() else: - QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 创建失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + f"脚本 '{script_name}' 创建失败!", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"创建脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"创建脚本时发生错误: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onCreateScriptDialog(self): """菜单创建脚本事件""" - dialog = self.createStyledInputDialog(self, "创建脚本", "输入脚本名称:") + dialog = self.createStyledInputDialog(self, "创建脚本", "请输入脚本名称:") if dialog.exec_() == QDialog.Accepted: script_name = dialog.textValue() @@ -4336,30 +4571,55 @@ class MainWindow(QMainWindow): try: success = self.world.createScript(script_name.strip(), "basic") if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 创建成功!") + UniversalMessageDialog.show_success( + self, + "成功", + f"脚本 '{script_name}' 创建成功!", + show_cancel=False, + confirm_text="确认" + ) self.refreshScriptsList() else: - QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 创建失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + f"脚本 '{script_name}' 创建失败!", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"创建脚本时出错: {str(e)}") - + UniversalMessageDialog.show_error( + self, + "错误", + f"创建脚本时发生错误: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onLoadScript(self): """加载脚本按钮事件""" current_item = self.scriptsList.currentItem() if not current_item: - QMessageBox.warning(self, "错误", "请选择要加载的脚本!") + UniversalMessageDialog.show_warning( + self, "警告", "请选择要加载的脚本!", show_cancel=False, confirm_text="确认" + ) return script_name = current_item.text() try: success = self.world.reloadScript(script_name) if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 重载成功!") + UniversalMessageDialog.show_success( + self, "成功", f"脚本 '{script_name}' 加载成功!", show_cancel=False, confirm_text="确认" + ) else: - QMessageBox.warning(self, "错误", f"脚本 '{script_name}' 重载失败!") + UniversalMessageDialog.show_warning( + self, "警告", f"脚本 '{script_name}' 加载失败!", show_cancel=False, confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"重载脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, "错误", f"加载脚本时发生错误: {str(e)}", show_cancel=False, confirm_text="确认" + ) def onLoadScriptFile(self): """加载脚本文件菜单事件""" @@ -4376,29 +4636,52 @@ class MainWindow(QMainWindow): try: success = self.world.loadScript(file_path) if success: - QMessageBox.information(self, "成功", "脚本文件加载成功!") + UniversalMessageDialog.show_success( + self, "成功", "脚本文件加载成功!", show_cancel=False, confirm_text="确认" + ) self.refreshScriptsList() else: - QMessageBox.warning(self, "错误", "脚本文件加载失败!") + UniversalMessageDialog.show_warning( + self, "警告", "脚本文件加载失败!", show_cancel=False, confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"加载脚本文件时出错: {str(e)}") - + UniversalMessageDialog.show_error( + self, "错误", f"加载脚本文件时发生错误: {str(e)}", show_cancel=False, confirm_text="确认" + ) def onReloadAllScripts(self): - """重载所有脚本事件""" + """重新加载所有脚本事件""" try: scripts_loaded = self.world.loadAllScripts() - QMessageBox.information(self, "成功", f"重载完成,共加载 {len(scripts_loaded)} 个脚本!") + UniversalMessageDialog.show_success( + self, + "成功", + f"更新完成,共加载 {len(scripts_loaded)} 个脚本!", + show_cancel=False, + confirm_text="确认" + ) self.refreshScriptsList() except Exception as e: - QMessageBox.critical(self, "错误", f"重载脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"加载脚本时发生错误: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onToggleHotReload(self): - """切换热重载状态""" + """切换热更新状态""" enabled = self.toggleHotReloadAction.isChecked() self.world.enableHotReload(enabled) - status = "启用" if enabled else "禁用" - QMessageBox.information(self, "热重载", f"热重载已{status}") + status = "已开启" if enabled else "已关闭" + UniversalMessageDialog.show_info( + self, + "脚本热更", + f"脚本热更{status}", + show_cancel=False, + confirm_text="确认" + ) def onOpenScriptsManager(self): """打开脚本管理器""" @@ -4408,65 +4691,107 @@ class MainWindow(QMainWindow): def onScriptDoubleClick(self, item): """脚本列表双击事件""" - # 可以在这里添加打开外部编辑器的功能 script_name = item.text() - QMessageBox.information(self, "提示", f"双击了脚本: {script_name}\n\n可以使用外部编辑器编辑脚本文件。") + UniversalMessageDialog.show_info( + self, + "提示", + f"双击脚本: {script_name}\n\n请使用外部编辑器编辑脚本文件。", + show_cancel=False, + confirm_text="确认" + ) def onMountScript(self): """挂载脚本事件""" selected_object = getattr(self.world.selection, 'selectedObject', None) if not selected_object: - QMessageBox.warning(self, "错误", "请先选择一个对象!") + UniversalMessageDialog.show_warning( + self, "警告", "请选择一个对象!", show_cancel=False, confirm_text="确认" + ) return script_name = self.mountScriptCombo.currentText() if not script_name: - QMessageBox.warning(self, "错误", "请选择要挂载的脚本!") + UniversalMessageDialog.show_warning( + self, "警告", "请选择要挂载的脚本!", show_cancel=False, confirm_text="确认" + ) return try: success = self.world.addScript(selected_object, script_name) if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 已挂载到对象!") + UniversalMessageDialog.show_success( + self, "成功", f"脚本 '{script_name}' 已挂载到对象!", show_cancel=False, confirm_text="确认" + ) self.updateMountedScriptsList(selected_object) - # 同时更新属性面板 if self.treeWidget and self.treeWidget.currentItem(): self.world.updatePropertyPanel(self.treeWidget.currentItem()) else: - QMessageBox.warning(self, "错误", f"挂载脚本失败!") + UniversalMessageDialog.show_warning( + self, "警告", "挂载脚本失败!", show_cancel=False, confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"挂载脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, "错误", f"挂载脚本时发生错误: {str(e)}", show_cancel=False, confirm_text="确认" + ) def onUnmountScript(self): """卸载脚本事件""" selected_object = getattr(self.world.selection, 'selectedObject', None) if not selected_object: - QMessageBox.warning(self, "错误", "请先选择一个对象!") + UniversalMessageDialog.show_warning( + self, + "警告", + "请选择一个对象!", + show_cancel=False, + confirm_text="确认" + ) return current_item = self.mountedScriptsList.currentItem() if not current_item: - QMessageBox.warning(self, "错误", "请选择要卸载的脚本!") + UniversalMessageDialog.show_warning( + self, + "警告", + "请选择要卸载的脚本!", + show_cancel=False, + confirm_text="确认" + ) return - # 解析脚本名称(移除状态标记) item_text = current_item.text() - script_name = item_text[2:] # 移除 "✓ " 或 "✗ " 前缀 + script_name = item_text[2:] try: success = self.world.removeScript(selected_object, script_name) if success: - QMessageBox.information(self, "成功", f"脚本 '{script_name}' 已从对象卸载!") + UniversalMessageDialog.show_success( + self, + "成功", + f"脚本 '{script_name}' 已从对象卸载!", + show_cancel=False, + confirm_text="确认" + ) self.updateMountedScriptsList(selected_object) - # 同时更新属性面板 if self.treeWidget and self.treeWidget.currentItem(): self.world.updatePropertyPanel(self.treeWidget.currentItem()) else: - QMessageBox.warning(self, "错误", f"卸载脚本失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + "卸载脚本失败!", + show_cancel=False, + confirm_text="确认" + ) except Exception as e: - QMessageBox.critical(self, "错误", f"卸载脚本时出错: {str(e)}") + UniversalMessageDialog.show_error( + self, + "错误", + f"卸载脚本时发生错误: {str(e)}", + show_cancel=False, + confirm_text="确认" + ) def onOpenIconManager(self): """打开图标管理器""" @@ -4560,9 +4885,21 @@ class MainWindow(QMainWindow): terrain_info = self.world.createFlatTerrain((width, height), resolution) if terrain_info: - QMessageBox.information(self, "成功", "平面地形创建成功!") + UniversalMessageDialog.show_success( + self, + "成功", + "平面地形创建成功!", + show_cancel=False, + confirm_text="确认" + ) else: - QMessageBox.warning(self, "警告", "平面地形创建失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + "平面地形创建失败!", + show_cancel=False, + confirm_text="确认" + ) def onCreateHeightmapTerrain(self): """从高度图创建地形""" @@ -4624,9 +4961,21 @@ class MainWindow(QMainWindow): (x_scale, y_scale, z_scale) ) if terrain_info: - QMessageBox.information(self, "成功", "高度图地形创建成功!") + UniversalMessageDialog.show_success( + self, + "成功", + "高度图地形创建成功!", + show_cancel=False, + confirm_text="确认" + ) else: - QMessageBox.warning(self, "警告", "高度图地形创建失败!") + UniversalMessageDialog.show_warning( + self, + "警告", + "高度图地形创建失败!", + show_cancel=False, + confirm_text="确认" + ) def onOpenAssemblyDisassemblyConfig(self): """打开拆装配置界面""" diff --git a/ui/property_panel.py b/ui/property_panel.py index ccab840d..b815ee94 100644 --- a/ui/property_panel.py +++ b/ui/property_panel.py @@ -241,6 +241,41 @@ class PropertyPanelManager: letter-spacing: 0.5px; } """ + + # 固定宽度的徽章样式(用于碰撞检测状态) + self.badge_style_green_fixed = """ + QLabel { + background-color: rgba(45, 255, 196, 0.15); + border: 1px solid rgba(45, 255, 196, 0.6); + color: #2dffc4; + border-radius: 6px; + padding: 4px 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 9px; + font-weight: 500; + letter-spacing: 0.5px; + min-width: 50px; + max-width: 50px; + text-align: center; + } + """ + + self.badge_style_red_fixed = """ + QLabel { + background-color: rgba(255, 99, 99, 0.15); + border: 1px solid rgba(255, 99, 99, 0.6); + color: #ff6363; + border-radius: 6px; + padding: 4px 10px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 9px; + font-weight: 500; + letter-spacing: 0.5px; + min-width: 50px; + max-width: 50px; + text-align: center; + } + """ def createStatusBadge(self, text, badge_type="green"): """创建现代化状态徽章 @@ -265,6 +300,28 @@ class PropertyPanelManager: badge.setStyleSheet(self.badge_style_blue) # 默认蓝色 return badge + def createFixedStatusBadge(self, text, badge_type="green"): + """创建固定宽度的状态徽章(用于碰撞检测等需要保持一致宽度的场景) + + Args: + text: 徽章文字 + badge_type: 徽章类型,支持 "green", "red" + + Returns: + QLabel: 配置好固定宽度样式的标签 + """ + badge = QLabel(text) + badge.setAlignment(Qt.AlignCenter) # 确保文字居中 + + if badge_type == "green": + badge.setStyleSheet(self.badge_style_green_fixed) + elif badge_type == "red": + badge.setStyleSheet(self.badge_style_red_fixed) + else: + badge.setStyleSheet(self.badge_style_green_fixed) # 默认绿色 + + return badge + def createModernButton(self, text, button_type="default"): """创建现代化按钮 @@ -1012,7 +1069,7 @@ class PropertyPanelManager: # 清理碰撞相关控件引用 collision_controls = [ - 'collision_status_text', 'collision_shape_combo', 'collision_shape_label', + 'collision_status_badge', 'collision_shape_combo', 'collision_shape_label', 'collision_visibility_button', 'collision_button', 'collision_layout', 'collision_group', 'collision_pos_x', 'collision_pos_y', 'collision_pos_z', 'collision_radius', 'collision_width', 'collision_length', 'collision_height', @@ -9300,14 +9357,28 @@ except Exception as e: # 检查模型是否已有碰撞 has_collision = self._hasCollision(model) - # 碰撞状态标签 + # 创建主容器 + main_container = QWidget() + main_layout = QVBoxLayout(main_container) + main_layout.setContentsMargins(8, 8, 8, 8) + main_layout.setSpacing(12) + + # 状态和形状选择区域 + header_container = QWidget() + header_layout = QGridLayout(header_container) + header_layout.setContentsMargins(0, 0, 0, 0) + header_layout.setSpacing(8) + + # 碰撞状态行 status_label = QLabel("状态:") collision_layout.addWidget(status_label, 0, 0) - # 状态文本(需要保存引用以便更新) - self.collision_status_text = QLabel("已启用" if has_collision else "未启用") - self.collision_status_text.setStyleSheet("color: green;" if has_collision else "color: red;") - collision_layout.addWidget(self.collision_status_text, 0, 1) + # 状态徽章(使用固定宽度样式) + if has_collision: + self.collision_status_badge = self.createFixedStatusBadge("已启用", "green") + else: + self.collision_status_badge = self.createFixedStatusBadge("未启用", "red") + collision_layout.addWidget(self.collision_status_badge, 0, 1) # 形状选择标签(始终显示) self.collision_shape_label = QLabel("碰撞形状:") @@ -9343,14 +9414,15 @@ except Exception as e: # 添加碰撞参数调整控件 current_row = self._addCollisionParameterControls(model, collision_layout, current_row, current_shape) - # 显示/隐藏切换按钮 - self.collision_visibility_button = QPushButton("隐藏碰撞" if is_collision_visible else "显示碰撞") + # 显示/隐藏切换按钮(使用现代化样式) + visibility_text = "隐藏碰撞" if is_collision_visible else "显示碰撞" + self.collision_visibility_button = self.createModernButton(visibility_text, "primary") self.collision_visibility_button.clicked.connect(lambda: self._toggleCollisionVisibility(model)) collision_layout.addWidget(self.collision_visibility_button, current_row, 0, 1, 2) current_row += 1 - # 移除碰撞按钮 - self.collision_button = QPushButton("移除碰撞") + # 移除碰撞按钮(使用现代化样式) + self.collision_button = self.createModernButton("移除碰撞", "danger") self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) collision_layout.addWidget(self.collision_button, current_row, 0, 1, 2) else: @@ -9365,8 +9437,8 @@ except Exception as e: if hasattr(self, 'collision_visibility_button'): self.collision_visibility_button.setVisible(False) - # 添加碰撞按钮 - self.collision_button = QPushButton("添加碰撞") + # 添加碰撞按钮(使用现代化样式) + self.collision_button = self.createModernButton("添加碰撞", "success") self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) collision_layout.addWidget(self.collision_button, current_row, 0, 1, 2) collision_group.setLayout(collision_layout) @@ -9677,7 +9749,32 @@ except Exception as e: try: if hasattr(self, 'collision_visibility_button'): is_visible = self._isCollisionVisible(model) - self.collision_visibility_button.setText("隐藏碰撞" if is_visible else "显示碰撞") + button_text = "隐藏碰撞" if is_visible else "显示碰撞" + self.collision_visibility_button.setText(button_text) + + # 应用主要按钮样式 + self.collision_visibility_button.setStyleSheet(""" + QPushButton { + background-color: rgba(77, 116, 189, 0.8); + color: #ffffff; + border: 1px solid #4d74bd; + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 500; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(77, 116, 189, 1.0); + border: 1px solid rgba(77, 116, 189, 1.0); + } + QPushButton:pressed { + background-color: rgba(61, 96, 169, 1.0); + border: 1px solid rgba(61, 96, 169, 1.0); + } + """) + print(f"更新可见性按钮:{model.getName()} - {'可见' if is_visible else '隐藏'}") except Exception as e: print(f"更新碰撞可见性按钮失败: {e}") @@ -9845,6 +9942,17 @@ except Exception as e: except Exception as e: print(f"移除碰撞失败: {e}") + def _findButtonRow(self, layout): + """查找按钮应该在的行数""" + # 简单返回一个合适的行数,基于布局中现有的项目数 + row_count = 0 + for i in range(layout.count()): + item = layout.itemAt(i) + if item and item.widget(): + row, col, rowspan, colspan = layout.getItemPosition(i) + row_count = max(row_count, row + 1) + return row_count + def _updateCollisionPanelState(self, model): """更新碰撞面板状态""" try: @@ -9854,24 +9962,65 @@ except Exception as e: self._updating_collision_panel = True - if hasattr(self, 'collision_button') and hasattr(self, 'collision_status_text') and hasattr(self, + if hasattr(self, 'collision_button') and hasattr(self, 'collision_status_badge') and hasattr(self, 'collision_shape_combo'): has_collision = self._hasCollision(model) - # 更新状态文本和颜色 - self.collision_status_text.setText("已启用" if has_collision else "未启用") - self.collision_status_text.setStyleSheet("color: green;" if has_collision else "color: red;") + # 更新状态徽章(使用固定宽度) + if has_collision: + new_badge = self.createFixedStatusBadge("已启用", "green") + else: + new_badge = self.createFixedStatusBadge("未启用", "red") + + # 替换旧的徽章 + old_badge = self.collision_status_badge + parent_layout = old_badge.parent().layout() + if parent_layout: + # 找到旧徽章在布局中的位置 + for i in range(parent_layout.count()): + item = parent_layout.itemAt(i) + if item and item.widget() == old_badge: + # 移除旧徽章并添加新徽章 + parent_layout.removeWidget(old_badge) + old_badge.deleteLater() + parent_layout.addWidget(new_badge, 0, 1) # 状态徽章在第0行第1列 + self.collision_status_badge = new_badge + break if has_collision: # 有碰撞:显示移除按钮,下拉框变为只读并显示当前类型 - self.collision_button.setText("移除碰撞") - - # 先断开所有连接,再重新连接 - try: - self.collision_button.clicked.disconnect() - except: - pass - self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) + if hasattr(self, 'collision_button'): + # 更新按钮文本和样式(简单方法:直接设置文本和样式) + self.collision_button.setText("移除碰撞") + # 应用危险按钮样式 + self.collision_button.setStyleSheet(""" + QPushButton { + background-color: rgba(255, 99, 99, 0.15); + color: #ff6363; + border: 1px solid rgba(255, 99, 99, 0.6); + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 400; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(255, 99, 99, 0.25); + border: 1px solid #ff6363; + } + QPushButton:pressed { + background-color: rgba(255, 99, 99, 0.4); + color: #ffffff; + } + """) + + # 重新连接信号 + try: + self.collision_button.clicked.disconnect() + except: + pass + self.collision_button.clicked.connect(lambda: self._removeCollisionAndUpdate(model)) # 获取并显示当前碰撞类型,设置为只读 current_shape = self._getCurrentCollisionShape(model) @@ -9895,14 +10044,37 @@ except Exception as e: else: # 无碰撞:显示添加按钮,下拉框变为可编辑 - self.collision_button.setText("添加碰撞") + if hasattr(self, 'collision_button'): + self.collision_button.setText("添加碰撞") + # 应用成功按钮样式 + self.collision_button.setStyleSheet(""" + QPushButton { + background-color: rgba(45, 255, 196, 0.15); + color: #2dffc4; + border: 1px solid rgba(45, 255, 196, 0.6); + border-radius: 4px; + padding: 8px 12px; + font-family: 'Microsoft YaHei', 'Inter', sans-serif; + font-size: 10px; + font-weight: 400; + min-height: 16px; + } + QPushButton:hover { + background-color: rgba(45, 255, 196, 0.25); + border: 1px solid #2dffc4; + } + QPushButton:pressed { + background-color: rgba(45, 255, 196, 0.4); + color: #ffffff; + } + """) - # 先断开所有连接,再重新连接 - try: - self.collision_button.clicked.disconnect() - except: - pass - self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) + # 先断开所有连接,再重新连接 + try: + self.collision_button.clicked.disconnect() + except: + pass + self.collision_button.clicked.connect(lambda: self._addCollisionAndUpdate(model)) # 恢复为可编辑状态 self.collision_shape_combo.setEnabled(True) diff --git a/ui/widgets.py b/ui/widgets.py index a9692cc8..a4280eb8 100644 --- a/ui/widgets.py +++ b/ui/widgets.py @@ -43,7 +43,7 @@ class NewProjectDialog(QDialog): self.dragging = False self.drag_position = QPoint() self.icon_manager = get_icon_manager() - self._title_icon_size = QSize(14, 14) + self._title_icon_size = QSize(18, 18) self._icon_close = self.icon_manager.get_icon('close_icon', self._title_icon_size) # 设置严格按照Figma设计的样式 @@ -81,10 +81,12 @@ class NewProjectDialog(QDialog): border: none; color: #EBEBEB; font-size: 14px; - min-width: 32px; - max-width: 32px; - min-height: 32px; - max-height: 32px; + min-width: 18px; + max-width: 18px; + min-height: 18px; + max-height: 18px; + padding: 0px; + border-radius: 3px; } QWidget#controlButtons QPushButton:hover { background-color: #2A2D2E; @@ -94,7 +96,7 @@ class NewProjectDialog(QDialog): border-radius: 0px 5px 0px 0px; } QPushButton#closeButton:hover { - background-color: #E74C3C; + background-color: #2A2D2E; color: #FFFFFF; } QWidget#contentWidget { @@ -431,25 +433,25 @@ class NewProjectDialog(QDialog): # 获取并验证路径 self.projectPath = self.pathEdit.text() if not self.projectPath: - StyledMessageBox.warning(self, "错误", "请选择项目路径!") + UniversalMessageDialog.show_warning(self, "错误", "请选择项目路径!", show_cancel=False, confirm_text="确认") return # 获取并验证项目名称 self.projectName = self.nameEdit.text().strip() if not self.projectName: - StyledMessageBox.warning(self, "错误", "请输入项目名称!") + UniversalMessageDialog.show_warning(self, "错误", "请输入项目名称!", show_cancel=False, confirm_text="确认") return # 验证项目名称格式 if not re.match(r'^[a-zA-Z0-9_\-\u4e00-\u9fa5]+$', self.projectName): - StyledMessageBox.warning(self, "错误", - "项目名称只能包含字母、数字、下划线、中划线和中文!") + UniversalMessageDialog.show_warning(self, "错误", + "项目名称只能包含字母、数字、下划线、中划线和中文!", show_cancel=False, confirm_text="确认") return # 检查项目是否已存在 full_path = os.path.join(self.projectPath, self.projectName) if os.path.exists(full_path): - StyledMessageBox.warning(self, "错误", "项目已存在!") + UniversalMessageDialog.show_warning(self, "错误", "项目已存在!", show_cancel=False, confirm_text="确认") return self.accept() @@ -849,21 +851,21 @@ class CustomAssetsTreeWidget(QTreeWidget): """删除文件或文件夹""" import os import shutil - from PyQt5.QtWidgets import QMessageBox filepath = item.data(0, Qt.UserRole) is_folder = item.data(0, Qt.UserRole + 1) item_type = "文件夹" if is_folder else "文件" - reply = StyledMessageBox.question( - self, - "确认删除", + result = UniversalMessageDialog.show_warning( + self, + "确认删除", f"确定要删除这个{item_type}吗?\n{filepath}", - QMessageBox.Yes | QMessageBox.No, - QMessageBox.No + show_cancel=True, + confirm_text="删除", + cancel_text="取消" ) - if reply == QMessageBox.Yes: + if result == QDialog.Accepted: try: if is_folder: shutil.rmtree(filepath) @@ -873,6 +875,13 @@ class CustomAssetsTreeWidget(QTreeWidget): print(f"删除{item_type}: {filepath}") except OSError as e: print(f"删除{item_type}失败: {e}") + UniversalMessageDialog.show_error( + self, + "错误", + f"删除{item_type}失败: {e}", + show_cancel=False, + confirm_text="确认" + ) def copyPath(self, filepath): """复制路径到剪贴板""" @@ -903,7 +912,6 @@ class CustomAssetsTreeWidget(QTreeWidget): def showProperties(self, item): """显示属性面板""" import os - from PyQt5.QtWidgets import QMessageBox filepath = item.data(0, Qt.UserRole) is_folder = item.data(0, Qt.UserRole + 1) @@ -926,10 +934,23 @@ class CustomAssetsTreeWidget(QTreeWidget): 修改时间: {modified_str} """ - StyledMessageBox.information(self, "属性", properties.strip()) + UniversalMessageDialog.show_info( + self, + "属性", + properties.strip(), + show_cancel=False, + confirm_text="确认" + ) except OSError as e: - StyledMessageBox.warning(self, "错误", f"无法获取属性: {e}") + UniversalMessageDialog.show_warning( + self, + "错误", + f"无法获取属性: {e}", + show_cancel=False, + confirm_text="确认" + ) + # def mouseDoubleClickEvent(self, event): # """处理双击事件""" @@ -2105,6 +2126,392 @@ class StyledMessageBox: ok = dialog.exec_() return dialog.textValue(), ok +class UniversalMessageDialog(QDialog): + """通用消息对话框类 - 支持不同图标和按钮配置""" + + # 消息类型枚举 + SUCCESS = "success_icon" + WARNING = "warning_icon" + ERROR = "fail_icon" + INFO = "info" + + def __init__(self, parent, title, message, message_type=INFO, show_cancel=True, + confirm_text="确认", cancel_text="取消", icon_size=QSize(48, 48)): + """ + 初始化通用消息对话框 + + Args: + parent: 父窗口 + title: 对话框标题 + message: 消息内容 + message_type: 消息类型 (SUCCESS, WARNING, ERROR, INFO) + show_cancel: 是否显示取消按钮 + confirm_text: 确认按钮文字 + cancel_text: 取消按钮文字 + icon_size: 图标尺寸 + """ + super().__init__(parent) + self.setWindowTitle(title) + self.setObjectName("universalMessageDialog") + self.setModal(True) + self.resize(508, 134) + self.setMinimumSize(508, 134) + self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint) + self.setAttribute(Qt.WA_TranslucentBackground, True) + + # 对话框配置 + self.message_type = message_type + self.show_cancel = show_cancel + self.confirm_text = confirm_text + self.cancel_text = cancel_text + self.icon_size = icon_size + + # 拖拽相关 + self.dragging = False + self.drag_position = QPoint() + + # 图标管理 + self.icon_manager = get_icon_manager() + self._title_icon_size = QSize(18, 18) + self._icon_close = self.icon_manager.get_icon('close_icon', self._title_icon_size) + + # 根据消息类型获取对应图标 + self._message_icon = self._get_message_icon() + + # 设置样式 + self._setup_styles() + + # 创建UI + self._create_ui(message) + + def _get_message_icon(self): + """根据消息类型获取对应图标""" + icon_map = { + self.SUCCESS: 'success_icon', + self.WARNING: 'warning_icon', + self.ERROR: 'fail_icon', + self.INFO: 'success_icon' # 默认使用成功图标 + } + + icon_name = icon_map.get(self.message_type, 'success_icon') + return self.icon_manager.get_icon(icon_name, self.icon_size) + + def _setup_styles(self): + """设置对话框样式""" + self.setStyleSheet(""" + QDialog#universalMessageDialog { + background-color: transparent; + border: none; + } + QFrame#baseFrame { + background-color: #19191B; + border: 1px solid #3E3E42; + border-radius: 5px; + } + QWidget#titleBar { + background-color: transparent; + border: none; + border-radius: 5px 5px 0px 0px; + min-height: 32px; + max-height: 32px; + } + QWidget#titleBar QWidget { + background-color: transparent; + border: none; + } + QLabel#titleLabel { + color: #FFFFFF; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 14px; + font-weight: 500; + letter-spacing: 0.7px; + } + QWidget#controlButtons QPushButton { + background-color: transparent; + border: none; + color: #EBEBEB; + font-size: 14px; + min-width: 18px; + max-width: 18px; + min-height: 18px; + max-height: 18px; + padding: 0px; + border-radius: 3px; + } + QWidget#controlButtons QPushButton:hover { + background-color: #2A2D2E; + color: #FFFFFF; + } + QPushButton#closeButton { + border-radius: 0px 5px 0px 0px; + } + QPushButton#closeButton:hover { + background-color: #2A2D2E; + color: #FFFFFF; + } + QFrame#titleSeparator { + background-color: #3E3E42; + border: none; + min-height: 1px; + max-height: 1px; + } + QWidget#contentWidget { + background-color: transparent; + border: none; + } + QLabel#messageLabel { + color: #EBEBEB; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-size: 13px; + font-weight: 400; + letter-spacing: 0.6px; + line-height: 1.4; + padding: 0px; + margin: 0px; + } + QPushButton { + background-color: rgba(89, 98, 118, 0.5); + color: #EBEBEB; + border: none; + border-radius: 2px; + padding: 0px 12px; + font-family: 'Inter', 'Microsoft YaHei', sans-serif; + font-weight: 300; + font-size: 10px; + letter-spacing: 0.5px; + min-width: 90px; + max-width: 90px; + min-height: 28px; + max-height: 28px; + } + QPushButton:hover { + background-color: #3067C0; + color: #FFFFFF; + } + QPushButton:pressed { + background-color: #2556A0; + color: #FFFFFF; + } + QPushButton#primaryButton { + background-color: rgba(89, 98, 118, 0.5); + border: none; + color: #EBEBEB; + font-weight: 300; + } + QPushButton#primaryButton:hover { + background-color: #2556A0; + } + QPushButton#primaryButton:pressed { + background-color: #1E4A8C; + } + QPushButton#secondaryButton { + background-color: rgba(89, 98, 118, 0.5); + border: none; + color: #EBEBEB; + } + QPushButton#secondaryButton:hover { + background-color: #3067C0; + color: #FFFFFF; + } + QPushButton#secondaryButton:pressed { + background-color: #2556A0; + color: #FFFFFF; + } + """) + + def _create_ui(self, message): + """构建用户界面""" + main_layout = QVBoxLayout(self) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(0) + + base_frame = QFrame() + base_frame.setObjectName('baseFrame') + base_frame.setFrameShape(QFrame.NoFrame) + base_frame.setAttribute(Qt.WA_StyledBackground, True) + + base_layout = QVBoxLayout(base_frame) + base_layout.setContentsMargins(25, 14, 25, 14) + base_layout.setSpacing(0) + + self._create_title_bar() + base_layout.addWidget(self.title_bar) + base_layout.addSpacing(4) + + title_separator = QFrame() + title_separator.setObjectName('titleSeparator') + title_separator.setFrameShape(QFrame.HLine) + title_separator.setFrameShadow(QFrame.Plain) + base_layout.addWidget(title_separator) + base_layout.addSpacing(10) + + content_widget = QWidget() + content_widget.setObjectName('contentWidget') + content_layout = QVBoxLayout(content_widget) + content_layout.setContentsMargins(0, 0, 0, 0) + content_layout.setSpacing(10) + + message_area = QHBoxLayout() + message_area.setContentsMargins(0, 0, 0, 0) + message_area.setSpacing(15) + + icon_label = QLabel() + if self._message_icon and not self._message_icon.isNull(): + icon_label.setPixmap(self._message_icon.pixmap(self.icon_size)) + icon_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) + icon_label.setFixedSize(self.icon_size) + message_area.addWidget(icon_label) + + self.message_label = QLabel(message) + self.message_label.setObjectName('messageLabel') + self.message_label.setWordWrap(True) + self.message_label.setAlignment(Qt.AlignTop | Qt.AlignLeft) + self.message_label.setMinimumHeight(self.icon_size.height()) + self.message_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Minimum) + message_area.addWidget(self.message_label, 1) + message_area.addStretch() + + content_layout.addLayout(message_area) + base_layout.addWidget(content_widget) + base_layout.addSpacing(10) + + button_widget = QWidget() + button_layout = QHBoxLayout(button_widget) + button_layout.setContentsMargins(0, 0, 0, 0) + button_layout.setSpacing(10) + button_layout.addStretch() + + if self.show_cancel: + self.cancel_button = QPushButton(self.cancel_text) + self.cancel_button.setObjectName("secondaryButton") + self.cancel_button.clicked.connect(self.reject) + self.cancel_button.setFixedSize(90, 28) + button_layout.addWidget(self.cancel_button) + + self.confirm_button = QPushButton(self.confirm_text) + self.confirm_button.setObjectName("primaryButton") + self.confirm_button.clicked.connect(self.accept) + self.confirm_button.setFixedSize(90, 28) + button_layout.addWidget(self.confirm_button) + + self.confirm_button.setDefault(True) + self.confirm_button.setAutoDefault(True) + if self.show_cancel: + self.cancel_button.setAutoDefault(False) + + base_layout.addWidget(button_widget) + + main_layout.addWidget(base_frame) + + def _create_title_bar(self): + """创建自定义标题栏""" + self.title_bar = QFrame() + self.title_bar.setObjectName("titleBar") + + title_layout = QHBoxLayout(self.title_bar) + title_layout.setContentsMargins(0, 0, 0, 0) + title_layout.setSpacing(0) + + self.title_label = QLabel(self.windowTitle()) + self.title_label.setObjectName("titleLabel") + self.title_label.setAlignment(Qt.AlignVCenter | Qt.AlignLeft) + title_layout.addWidget(self.title_label, 1) + + title_layout.addStretch() + + controls = QWidget() + controls.setObjectName("controlButtons") + controls_layout = QHBoxLayout(controls) + controls_layout.setContentsMargins(0, 0, 0, 0) + controls_layout.setSpacing(0) + + self.close_button = QPushButton() + self.close_button.setObjectName("closeButton") + self.close_button.clicked.connect(self.reject) + self.close_button.setFocusPolicy(Qt.NoFocus) + controls_layout.addWidget(self.close_button) + + self._apply_title_bar_icons() + + title_layout.addWidget(controls) + + + def _apply_title_bar_icons(self): + """应用标题栏图标""" + if self._icon_close: + self.close_button.setIcon(self._icon_close) + self.close_button.setIconSize(self._title_icon_size) + self.close_button.setText("") + self.close_button.setToolTip("关闭") + + def setWindowTitle(self, title): + """设置窗口标题""" + super().setWindowTitle(title) + if hasattr(self, "title_label"): + self.title_label.setText(title) + + def mousePressEvent(self, event): + """鼠标按下事件 - 用于拖拽窗口""" + if event.button() == Qt.LeftButton and self.title_bar.geometry().contains(event.pos()): + self.dragging = True + self.drag_position = event.globalPos() - self.frameGeometry().topLeft() + event.accept() + super().mousePressEvent(event) + + def mouseMoveEvent(self, event): + """鼠标移动事件 - 用于拖拽窗口""" + if event.buttons() == Qt.LeftButton and self.dragging: + self.move(event.globalPos() - self.drag_position) + event.accept() + super().mouseMoveEvent(event) + + def mouseReleaseEvent(self, event): + """鼠标释放事件 - 停止拖拽""" + if event.button() == Qt.LeftButton: + self.dragging = False + super().mouseReleaseEvent(event) + + @staticmethod + def show_success(parent, title, message, show_cancel=False, confirm_text="确认"): + """显示成功消息对话框""" + dialog = UniversalMessageDialog( + parent, title, message, + UniversalMessageDialog.SUCCESS, + show_cancel, confirm_text + ) + return dialog.exec_() + + @staticmethod + def show_warning(parent, title, message, show_cancel=True, confirm_text="确认", cancel_text="取消"): + """显示警告消息对话框""" + dialog = UniversalMessageDialog( + parent, title, message, + UniversalMessageDialog.WARNING, + show_cancel, confirm_text, cancel_text + ) + return dialog.exec_() + + @staticmethod + def show_error(parent, title, message, show_cancel=False, confirm_text="确认"): + """显示错误消息对话框""" + dialog = UniversalMessageDialog( + parent, title, message, + UniversalMessageDialog.ERROR, + show_cancel, confirm_text + ) + return dialog.exec_() + + @staticmethod + def show_info(parent, title, message, show_cancel=True, confirm_text="确认", cancel_text="取消"): + """显示信息消息对话框""" + dialog = UniversalMessageDialog( + parent, title, message, + UniversalMessageDialog.INFO, + show_cancel, confirm_text, cancel_text + ) + return dialog.exec_() + + class StyledTextInputDialog(QDialog): """与新建项目样式一致的文本输入对话框""" @@ -2937,12 +3344,17 @@ class CustomTreeWidget(QTreeWidget): else: message = f"确定要删除 {item_count} 个节点吗?" - reply = StyledMessageBox.question( - self, "确认删除", message, - QMessageBox.Yes | QMessageBox.No, QMessageBox.No + dialog = UniversalMessageDialog( + self, + "确认删除", + message, + message_type=UniversalMessageDialog.WARNING, + show_cancel=True, + confirm_text="确认", + cancel_text="取消" ) - if reply != QMessageBox.Yes: + if dialog.exec_() != QDialog.Accepted: return # 默认选中场景根节点,通常是第一个顶级节点