568 lines
21 KiB
Python
568 lines
21 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""
|
||
创建项目对话框
|
||
"""
|
||
|
||
import os
|
||
from PyQt5.QtWidgets import *
|
||
from PyQt5.QtCore import *
|
||
from PyQt5.QtGui import *
|
||
|
||
from data.project_manager import ProjectManager
|
||
from ui.icon_manager import IconManager
|
||
from ui.widget import UniversalMessageDialog
|
||
|
||
class CreateProjectDialog(QDialog):
|
||
"""创建项目对话框"""
|
||
|
||
def __init__(self, project_manager: ProjectManager, project_settings_page=None, parent=None):
|
||
super().__init__(parent)
|
||
self.project_manager = project_manager
|
||
self.project_settings_page = project_settings_page
|
||
self.selected_template = "empty"
|
||
self.selected_path = ""
|
||
|
||
# 自定义标题栏相关控件
|
||
self.title_bar = None
|
||
self.title_label = None
|
||
self.close_btn = None
|
||
self.drag_offset = None
|
||
|
||
# 设置对话框对象名称以应用样式
|
||
self.setObjectName("CreateProjectDialog")
|
||
self.setWindowFlags(Qt.Dialog | Qt.FramelessWindowHint)
|
||
self.setAttribute(Qt.WA_StyledBackground, True)
|
||
|
||
self.init_ui()
|
||
self.connect_signals()
|
||
|
||
# 设置对话框属性
|
||
self.setWindowTitle("创建新的项目")
|
||
if self.title_label is not None:
|
||
self.title_label.setText(self.windowTitle())
|
||
self.setModal(True)
|
||
self.setMinimumSize(1177, 689) # 根据Figma设计调整尺寸
|
||
self.setMaximumSize(1177, 689)
|
||
self.resize(1177, 689) # 设置默认尺寸
|
||
|
||
# 设置默认项目位置
|
||
self.set_default_location_from_settings()
|
||
|
||
# 居中显示
|
||
self.center_dialog()
|
||
|
||
def init_ui(self):
|
||
"""初始化UI"""
|
||
layout = QVBoxLayout(self)
|
||
layout.setContentsMargins(0, 0, 0, 0)
|
||
layout.setSpacing(0)
|
||
|
||
# 自定义标题栏
|
||
self.title_bar = self.create_title_bar()
|
||
layout.addWidget(self.title_bar)
|
||
|
||
# 主要内容区域
|
||
self.create_main_content(layout)
|
||
|
||
def create_title_bar(self):
|
||
"""创建与导入项目对话框一致的自定义标题栏"""
|
||
title_bar = QWidget()
|
||
title_bar.setObjectName("customTitleBar")
|
||
|
||
layout = QHBoxLayout(title_bar)
|
||
layout.setContentsMargins(18, 12, 14, 12)
|
||
layout.setSpacing(12)
|
||
|
||
self.title_label = QLabel(self.windowTitle())
|
||
self.title_label.setObjectName("customTitleLabel")
|
||
self.title_label.setAttribute(Qt.WA_TransparentForMouseEvents, True)
|
||
layout.addWidget(self.title_label)
|
||
|
||
layout.addStretch()
|
||
|
||
if IconManager.icon_exists("close_bt_icon"):
|
||
close_icon = IconManager.get_icon("close_bt_icon", QSize(18, 18))
|
||
self.close_btn = QPushButton()
|
||
self.close_btn.setIcon(close_icon)
|
||
else:
|
||
self.close_btn = QPushButton("X")
|
||
self.close_btn.setObjectName("customCloseBtn")
|
||
self.close_btn.setFixedSize(18, 18)
|
||
self.close_btn.setCursor(Qt.PointingHandCursor)
|
||
self.close_btn.clicked.connect(self.reject)
|
||
layout.addWidget(self.close_btn)
|
||
|
||
title_bar.installEventFilter(self)
|
||
title_bar.setCursor(Qt.ArrowCursor)
|
||
return title_bar
|
||
|
||
|
||
def create_main_content(self, layout):
|
||
"""创建主要内容"""
|
||
content_widget = QWidget()
|
||
content_widget.setObjectName("createProjectContent")
|
||
content_layout = QHBoxLayout(content_widget)
|
||
content_layout.setContentsMargins(10, 0, 10, 15) # 根据Figma设计调整边距
|
||
content_layout.setSpacing(4) # 添加小间距
|
||
|
||
# 左侧模板选择区域
|
||
self.create_template_section(content_layout)
|
||
|
||
# 右侧项目信息区域
|
||
self.create_project_info_section(content_layout)
|
||
|
||
layout.addWidget(content_widget)
|
||
|
||
def create_template_section(self, layout):
|
||
"""创建模板选择区域"""
|
||
template_widget = QWidget()
|
||
template_widget.setObjectName("templateSection")
|
||
template_layout = QVBoxLayout(template_widget)
|
||
template_layout.setContentsMargins(0, 0, 0, 0)
|
||
template_layout.setSpacing(4)
|
||
|
||
# 标题栏
|
||
title_label = QLabel("选择项目模版") # 与Figma设计保持一致
|
||
title_label.setObjectName("templateHeaderLabel")
|
||
title_label.setFixedSize(724, 33)
|
||
template_layout.addWidget(title_label)
|
||
template_layout.addSpacing(4)
|
||
|
||
# 模板网格
|
||
self.create_template_grid(template_layout)
|
||
|
||
# 设置固定宽度 (根据Figma设计)
|
||
template_widget.setFixedWidth(724)
|
||
layout.addWidget(template_widget)
|
||
|
||
def create_template_grid(self, layout):
|
||
"""创建模板网格"""
|
||
# 滚动区域
|
||
scroll_area = QScrollArea()
|
||
scroll_area.setFixedSize(724, 595)
|
||
scroll_area.setObjectName("templateScrollArea")
|
||
scroll_area.setWidgetResizable(True)
|
||
scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||
scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||
|
||
# 模板容器
|
||
templates_widget = QWidget()
|
||
templates_widget.setObjectName("templatesContainer")
|
||
templates_layout = QGridLayout(templates_widget)
|
||
templates_layout.setSpacing(15) # 根据Figma设计调整间距
|
||
templates_layout.setContentsMargins(15, 15, 15, 15) # 调整容器边距
|
||
templates_layout.setAlignment(Qt.AlignTop | Qt.AlignLeft) # 左上对齐
|
||
|
||
# 模板数据 - 当前只有空白项目模板,后续可以添加更多模板
|
||
templates = [
|
||
("empty", "empty_folder_icon", "空白项目模版"), # 移除emoji图标,与Figma设计一致
|
||
# 后续可以添加的模板示例:
|
||
# ("web", "", "Web应用模板"),
|
||
# ("mobile", "", "移动应用模板"),
|
||
# ("desktop", "", "桌面应用模板"),
|
||
# ("api", "", "API服务模板"),
|
||
# ("data", "", "数据分析模板"),
|
||
# ("game", "", "游戏开发模板"),
|
||
]
|
||
|
||
# 创建模板项
|
||
self.template_buttons = []
|
||
for i, (template_id, icon, name) in enumerate(templates):
|
||
row = i // 3 # 每行最多3个模板,为后续扩展做准备
|
||
col = i % 3
|
||
|
||
template_btn = self.create_template_item(template_id, icon, name)
|
||
templates_layout.addWidget(template_btn, row, col)
|
||
self.template_buttons.append(template_btn)
|
||
|
||
# 添加弹性空间,确保模板按钮不会被拉伸
|
||
templates_layout.setRowStretch(templates_layout.rowCount(), 1)
|
||
templates_layout.setColumnStretch(templates_layout.columnCount(), 1)
|
||
|
||
scroll_area.setWidget(templates_widget)
|
||
layout.addWidget(scroll_area)
|
||
|
||
def create_template_item(self, template_id, icon_path, name):
|
||
"""创建模板项"""
|
||
template_btn = QPushButton()
|
||
template_btn.setObjectName("templateItem")
|
||
template_btn.setCheckable(True)
|
||
# 根据Figma设计调整尺寸 (149x127)
|
||
template_btn.setFixedSize(149, 127)
|
||
|
||
# 设置默认选中
|
||
if template_id == "empty":
|
||
template_btn.setChecked(True)
|
||
|
||
# 布局
|
||
btn_layout = QVBoxLayout(template_btn)
|
||
btn_layout.setAlignment(Qt.AlignCenter)
|
||
btn_layout.setSpacing(27) # 减小间距
|
||
btn_layout.setContentsMargins(15, 20, 15, 20)
|
||
|
||
# 图标区域 - 根据Figma设计,这里是一个空的框架
|
||
if icon_path and IconManager.icon_exists(icon_path):
|
||
icon = IconManager.get_icon(icon_path, QSize(40, 40))
|
||
icon_label = QLabel() # 创建QLabel而不是QIcon
|
||
icon_label.setObjectName("templateIcon")
|
||
icon_label.setAlignment(Qt.AlignCenter)
|
||
icon_label.setPixmap(icon.pixmap(QSize(40, 40))) # 设置图标
|
||
btn_layout.addWidget(icon_label)
|
||
else:
|
||
# 添加空白空间以保持布局一致
|
||
btn_layout.addStretch()
|
||
|
||
# 名称
|
||
name_label = QLabel(name)
|
||
name_label.setObjectName("templateName")
|
||
name_label.setAlignment(Qt.AlignCenter)
|
||
name_label.setWordWrap(True)
|
||
btn_layout.addWidget(name_label)
|
||
|
||
# 点击事件
|
||
def on_clicked():
|
||
# 取消其他按钮的选中状态
|
||
for btn in self.template_buttons:
|
||
if btn != template_btn:
|
||
btn.setChecked(False)
|
||
|
||
template_btn.setChecked(True)
|
||
self.selected_template = template_id
|
||
self.update_template_description(template_id)
|
||
|
||
template_btn.clicked.connect(on_clicked)
|
||
return template_btn
|
||
|
||
def create_project_info_section(self, layout):
|
||
"""创建项目信息区域"""
|
||
info_widget = QWidget()
|
||
info_widget.setObjectName("projectInfoSection")
|
||
info_layout = QVBoxLayout(info_widget)
|
||
info_layout.setContentsMargins(15, 10, 15, 10) # 调整边距
|
||
info_layout.setSpacing(15)
|
||
|
||
# 标题和描述容器
|
||
header_widget = QWidget()
|
||
header_widget.setObjectName("projectInfoHeader")
|
||
header_layout = QVBoxLayout(header_widget)
|
||
header_layout.setContentsMargins(0, 0, 0, 0)
|
||
header_layout.setSpacing(10)
|
||
|
||
# 标题
|
||
title_label = QLabel("项目信息")
|
||
title_label.setObjectName("sectionTitle")
|
||
header_layout.addWidget(title_label)
|
||
|
||
# 模板描述
|
||
self.description_label = QLabel("创建一个空白项目,您可以从头开始构建您的应用程序。包含基础的项目结构和配置文件,适用于任何类型的项目开发。")
|
||
self.description_label.setObjectName("templateDescription")
|
||
self.description_label.setWordWrap(True)
|
||
header_layout.addWidget(self.description_label)
|
||
|
||
info_layout.addWidget(header_widget)
|
||
|
||
# 表单
|
||
self.create_project_form(info_layout)
|
||
|
||
info_layout.addStretch()
|
||
|
||
# 设置固定宽度 (根据Figma设计)
|
||
info_widget.setFixedWidth(427)
|
||
layout.addWidget(info_widget)
|
||
|
||
def create_project_form(self, layout):
|
||
"""创建项目表单"""
|
||
form_layout = QVBoxLayout()
|
||
form_layout.setSpacing(15)
|
||
|
||
# 项目名称
|
||
name_group = QVBoxLayout()
|
||
|
||
name_label = QLabel("项目名称")
|
||
name_label.setObjectName("formLabel")
|
||
name_group.addWidget(name_label)
|
||
name_group.setSpacing(10)
|
||
|
||
self.name_input = QLineEdit()
|
||
self.name_input.setObjectName("formInput")
|
||
self.name_input.setMinimumSize(397, 32)
|
||
self.name_input.setPlaceholderText("输入项目名称")
|
||
name_group.addWidget(self.name_input)
|
||
|
||
# 错误提示标签 - 紧贴在输入框下方的小红字
|
||
self.name_error_label = QLabel("")
|
||
self.name_error_label.setObjectName("errorLabel")
|
||
self.name_error_label.setWordWrap(False) # 不换行,保持一行显示
|
||
self.name_error_label.setFixedHeight(18) # 固定高度,一行小字
|
||
self.name_error_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
||
self.name_error_label.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
|
||
self.name_error_label.hide() # 初始隐藏
|
||
|
||
name_group.addWidget(self.name_error_label)
|
||
|
||
form_layout.addLayout(name_group)
|
||
|
||
# 项目描述
|
||
desc_group = QVBoxLayout()
|
||
desc_label = QLabel("项目描述")
|
||
desc_label.setObjectName("formLabel")
|
||
desc_group.addWidget(desc_label)
|
||
desc_group.setSpacing(10)
|
||
|
||
self.desc_input = QTextEdit()
|
||
self.desc_input.setObjectName("formTextArea")
|
||
self.desc_input.setPlaceholderText("输入项目描述(可选)")
|
||
self.desc_input.setMinimumSize(397, 96)
|
||
desc_group.addWidget(self.desc_input)
|
||
|
||
form_layout.addLayout(desc_group)
|
||
|
||
# 项目位置
|
||
location_group = QVBoxLayout()
|
||
location_label = QLabel("项目位置")
|
||
location_label.setObjectName("formLabel")
|
||
location_group.addWidget(location_label)
|
||
location_group.setSpacing(10)
|
||
|
||
location_layout = QHBoxLayout()
|
||
self.location_input = QLineEdit()
|
||
self.location_input.setObjectName("formInput")
|
||
self.location_input.setFixedSize(280, 32)
|
||
self.location_input.setPlaceholderText("点击浏览选择位置")
|
||
self.location_input.setReadOnly(True)
|
||
location_layout.addWidget(self.location_input)
|
||
|
||
browse_btn = QPushButton("")
|
||
if IconManager.icon_exists('file_icon'):
|
||
browse_btn.setIcon(IconManager.get_icon('file_icon', QSize(20, 20)))
|
||
browse_btn.setObjectName("browseBtn")
|
||
browse_btn.setFixedSize(103, 32) # 减小尺寸:40x32 -> 32x32
|
||
browse_btn.setToolTip("选择项目保存位置")
|
||
browse_btn.clicked.connect(self.browse_location)
|
||
location_layout.addWidget(browse_btn)
|
||
|
||
location_group.addLayout(location_layout)
|
||
form_layout.addLayout(location_group)
|
||
|
||
form_layout.addWidget(self.create_button_area())
|
||
|
||
layout.addLayout(form_layout)
|
||
|
||
def create_button_area(self):
|
||
"""创建按钮区域"""
|
||
button_widget = QWidget()
|
||
button_widget.setObjectName("buttonArea")
|
||
button_layout = QHBoxLayout(button_widget)
|
||
button_layout.setContentsMargins(0, 185, 0, 10) # 根据Figma设计调整边距
|
||
button_layout.setSpacing(10) # 设置按钮间距
|
||
|
||
button_layout.addStretch()
|
||
|
||
# 取消按钮
|
||
cancel_btn = QPushButton("取消")
|
||
cancel_btn.setObjectName("secondaryBtn")
|
||
cancel_btn.setFixedSize(95, 36)
|
||
cancel_btn.clicked.connect(self.reject)
|
||
button_layout.addWidget(cancel_btn)
|
||
|
||
# 创建按钮
|
||
self.create_btn = QPushButton("创建") # 与Figma设计保持一致
|
||
self.create_btn.setObjectName("primaryBtn")
|
||
self.create_btn.setFixedSize(95, 36)
|
||
self.create_btn.clicked.connect(self.create_project)
|
||
button_layout.addWidget(self.create_btn)
|
||
|
||
return button_widget
|
||
|
||
def update_template_description(self, template_id):
|
||
"""更新模板描述"""
|
||
descriptions = {
|
||
"empty": "创建一个空白项目,您可以从头开始构建您的应用程序。包含基础的项目结构和配置文件,适用于任何类型的项目开发。",
|
||
}
|
||
|
||
description = descriptions.get(template_id, "项目模板描述")
|
||
self.description_label.setText(description)
|
||
|
||
def browse_location(self):
|
||
"""浏览项目位置"""
|
||
# 使用项目设置中的默认位置作为起始目录
|
||
if self.project_settings_page:
|
||
start_dir = self.project_settings_page.get_default_project_location()
|
||
else:
|
||
start_dir = os.path.expanduser("~/Documents")
|
||
|
||
folder = QFileDialog.getExistingDirectory(self, "选择项目保存位置", start_dir)
|
||
if folder:
|
||
self.selected_path = folder
|
||
self.location_input.setText(folder)
|
||
# 触发验证
|
||
self.validate_input()
|
||
|
||
def create_project(self):
|
||
"""创建项目"""
|
||
# 验证输入
|
||
name = self.name_input.text().strip()
|
||
if not name:
|
||
# QMessageBox.warning(self, "输入错误", "请输入项目名称")
|
||
UniversalMessageDialog.show_warning(self, "输入错误", "请输入项目名称", False, "确定")
|
||
return
|
||
|
||
if not self.selected_path:
|
||
UniversalMessageDialog.show_warning(self, "输入错误", "请选择项目保存位置", False, "确定")
|
||
return
|
||
|
||
# 使用项目管理器的验证方法进行全面检查
|
||
is_valid, error_message = self.project_manager.validate_project_creation(name, self.selected_path)
|
||
if not is_valid:
|
||
UniversalMessageDialog.show_warning(self, "创建失败", error_message, False, "确定")
|
||
return
|
||
|
||
# 创建项目
|
||
description = self.desc_input.toPlainText().strip()
|
||
try:
|
||
project = self.project_manager.add_project(
|
||
name, description, self.selected_template, self.selected_path
|
||
)
|
||
|
||
# 显示详细的成功信息
|
||
success_msg = f"""项目创建成功!
|
||
|
||
项目名称: {name}
|
||
项目目录: {project.project_dir}
|
||
|
||
项目文件结构已创建完成,包含:
|
||
? models/ - 模型文件夹
|
||
? textures/ - 贴图文件夹
|
||
? scenes/ - 场景文件夹
|
||
? project.json - 项目配置文件
|
||
|
||
您可以开始在此基础上开发您的应用程序。"""
|
||
|
||
UniversalMessageDialog.show_info(self, "创建成功", success_msg, False, "确定")
|
||
self.accept()
|
||
|
||
except Exception as e:
|
||
UniversalMessageDialog.show_error(self, "创建失败", f"项目创建失败:{str(e)}", False, "确定")
|
||
|
||
def get_template_name(self, template_id):
|
||
"""获取模板名称"""
|
||
template_names = {
|
||
"empty": "空白项目模板",
|
||
}
|
||
return template_names.get(template_id, "空白项目模板")
|
||
|
||
def connect_signals(self):
|
||
"""连接信号"""
|
||
# 连接项目名称输入框的文本变化信号
|
||
self.name_input.textChanged.connect(self.validate_input)
|
||
# 连接路径选择的变化
|
||
self.location_input.textChanged.connect(self.validate_input)
|
||
|
||
def validate_input(self):
|
||
"""实时验证输入"""
|
||
name = self.name_input.text().strip()
|
||
path = self.selected_path
|
||
|
||
# 重置样式属性
|
||
self.name_input.setProperty("error", False)
|
||
self.name_input.setProperty("valid", False)
|
||
self.create_btn.setEnabled(True)
|
||
|
||
# 如果名称为空,清空错误提示(用户可能还在输入)
|
||
if not name:
|
||
self.name_error_label.hide()
|
||
self.name_input.style().polish(self.name_input)
|
||
return
|
||
|
||
# 如果路径未选择,不进行完整验证
|
||
if not path:
|
||
self.name_error_label.hide()
|
||
self.name_input.style().polish(self.name_input)
|
||
return
|
||
|
||
# 进行验证
|
||
is_valid, error_message = self.project_manager.validate_project_creation(name, path)
|
||
|
||
if not is_valid:
|
||
# 设置错误状态
|
||
self.name_input.setProperty("error", True)
|
||
self.name_input.setProperty("valid", False)
|
||
# 禁用创建按钮
|
||
self.create_btn.setEnabled(False)
|
||
# 显示错误信息 - 一行小红字
|
||
self.name_error_label.setText(error_message)
|
||
self.name_error_label.show()
|
||
else:
|
||
# 设置成功状态
|
||
self.name_input.setProperty("error", False)
|
||
self.name_input.setProperty("valid", True)
|
||
# 隐藏错误信息
|
||
self.name_error_label.hide()
|
||
|
||
# 刷新样式
|
||
self.name_input.style().polish(self.name_input)
|
||
|
||
def set_default_location(self, location: str):
|
||
"""设置默认项目位置"""
|
||
if location and os.path.exists(location):
|
||
self.selected_path = location
|
||
self.location_input.setText(location)
|
||
# 触发验证
|
||
self.validate_input()
|
||
|
||
def set_default_location_from_settings(self):
|
||
"""从项目设置中设置默认项目位置"""
|
||
if self.project_settings_page:
|
||
default_location = self.project_settings_page.get_default_project_location()
|
||
if default_location:
|
||
self.set_default_location(default_location)
|
||
|
||
|
||
|
||
|
||
|
||
def eventFilter(self, watched, event):
|
||
"""处理标题栏拖动与按钮交互"""
|
||
if watched is self.title_bar:
|
||
if event.type() == QEvent.MouseButtonPress and event.button() == Qt.LeftButton:
|
||
self.drag_offset = event.globalPos() - self.frameGeometry().topLeft()
|
||
return True
|
||
if event.type() == QEvent.MouseMove and event.buttons() & Qt.LeftButton and self.drag_offset is not None:
|
||
self.move(event.globalPos() - self.drag_offset)
|
||
return True
|
||
if event.type() == QEvent.MouseButtonRelease and event.button() == Qt.LeftButton:
|
||
self.drag_offset = None
|
||
return True
|
||
return super().eventFilter(watched, event)
|
||
|
||
|
||
def center_dialog(self):
|
||
"""对话框居中"""
|
||
if self.parent():
|
||
parent_rect = self.parent().geometry()
|
||
x = parent_rect.x() + (parent_rect.width() - self.width()) // 2
|
||
y = parent_rect.y() + (parent_rect.height() - self.height()) // 2
|
||
self.move(x, y)
|
||
else:
|
||
# 如果没有父窗口,则在屏幕中央显示
|
||
screen = QApplication.desktop().screenGeometry()
|
||
x = (screen.width() - self.width()) // 2
|
||
y = (screen.height() - self.height()) // 2
|
||
self.move(x, y)
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|