上传文件至 /

This commit is contained in:
2025-10-26 12:17:46 +00:00
parent d5d50f6eec
commit 4ed840aa40
3 changed files with 3699 additions and 0 deletions

957
Setup.py Normal file
View File

@@ -0,0 +1,957 @@
import os
import sys
import time
import requests
import shutil
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
QPushButton, QVBoxLayout, QHBoxLayout, QWidget,
QFileDialog, QProgressBar, QMessageBox, QFrame,
QCheckBox, QStackedWidget, QTextEdit, QScrollArea)
from PyQt5.QtCore import Qt, QThread, pyqtSignal, QTimer, QUrl
from PyQt5.QtGui import (QFont, QIcon, QPalette, QColor, QPixmap,
QImageReader, QImage, QIcon as QtGuiQIcon)
# --------------------------- 全局样式配置 ---------------------------
GLOBAL_STYLE = """
/* 主按钮样式:科技蓝底色+圆角 */
QPushButton#PrimaryBtn {
background-color: #1E88E5;
color: white;
border: none;
border-radius: 4px;
padding: 8px 16px;
font-family: "微软雅黑", "Segoe UI";
font-size: 10pt;
}
QPushButton#PrimaryBtn:hover {
background-color: #1976D2;
}
QPushButton#PrimaryBtn:disabled {
background-color: #BBDEFB;
}
/* 次要按钮样式:灰色边框+透明底色 */
QPushButton#SecondaryBtn {
background-color: transparent;
color: #333333;
border: 1px solid #CCCCCC;
border-radius: 4px;
padding: 8px 16px;
font-family: "微软雅黑", "Segoe UI";
font-size: 10pt;
}
QPushButton#SecondaryBtn:hover {
background-color: #F5F5F5;
}
/* 进度条样式:蓝色进度+圆角 */
QProgressBar {
border: none;
border-radius: 4px;
background-color: #F5F5F5;
height: 8px;
font-family: "微软雅黑";
font-size: 8pt;
}
QProgressBar::chunk {
background-color: #1E88E5;
border-radius: 4px;
}
/* 输入框样式:细边框+聚焦高亮 */
QLineEdit {
border: 1px solid #CCCCCC;
border-radius: 4px;
padding: 6px 10px;
font-family: "微软雅黑";
font-size: 9pt;
}
QLineEdit:focus {
border-color: #1E88E5;
}
/* 面板样式:圆角+浅灰底色 */
QFrame#CardFrame {
background-color: white;
border-radius: 6px;
border: 1px solid #EEEEEE;
}
/* 标题样式 */
QLabel#TitleLabel {
font-family: "微软雅黑", "Segoe UI";
font-size: 14pt;
font-weight: bold;
color: #212121;
}
/* 副标题样式 */
QLabel#SubtitleLabel, QTextEdit {
font-family: "微软雅黑", "Segoe UI";
font-size: 10pt;
color: #666666;
}
/* 滚动区域样式 */
QScrollArea {
border: none;
}
"""
# --------------------------- 下载线程 ---------------------------
class DownloadThread(QThread):
progress_updated = pyqtSignal(int, str, float) # (进度, 文件名, 下载速度MB/s)
download_finished = pyqtSignal(bool, str, str) # (成功, 消息, 文件名)
def __init__(self, url, save_path, file_name, max_retries=2):
super().__init__()
self.url = url
self.save_path = save_path
self.file_name = file_name
self.max_retries = max_retries # 最大重试次数
self.start_time = None
def run(self):
retry_count = 0
while retry_count <= self.max_retries:
try:
# 确保保存目录存在(固定目录)
save_dir = os.path.dirname(self.save_path)
if not os.path.exists(save_dir):
os.makedirs(save_dir)
# 发送请求(增加超时设置)
self.start_time = time.time()
response = requests.get(
self.url,
stream=True,
timeout=15,
headers={"User-Agent": "EasyUI-Installer/1.0"}
)
response.raise_for_status() # 触发HTTP错误
total_size = int(response.headers.get('content-length', 0))
# 下载文件
with open(self.save_path, 'wb') as file:
downloaded_size = 0
for data in response.iter_content(chunk_size=8192): # 增大缓冲区,提升速度
if data:
file.write(data)
downloaded_size += len(data)
# 计算进度和下载速度
if total_size > 0:
progress = int((downloaded_size / total_size) * 100)
elapsed = time.time() - self.start_time
speed = downloaded_size / (1024 * 1024 * elapsed) if elapsed > 0 else 0
self.progress_updated.emit(progress, self.file_name, round(speed, 2))
# 验证文件完整性
if os.path.exists(self.save_path) and os.path.getsize(self.save_path) > 0:
self.download_finished.emit(True, self.save_path, self.file_name)
else:
raise Exception("文件下载不完整或为空")
break # 成功则退出重试循环
except Exception as e:
retry_count += 1
if retry_count > self.max_retries:
self.download_finished.emit(False, f"重试{self.max_retries}次后失败:{str(e)}", self.file_name)
else:
time.sleep(2) # 重试间隔2秒
# --------------------------- 图标下载线程 ---------------------------
class IconDownloadThread(QThread):
download_finished = pyqtSignal(bool, QIcon, bytes) # (成功, 图标对象, 原始数据)
def __init__(self, icon_url):
super().__init__()
self.icon_url = icon_url
def run(self):
try:
# 下载图标
response = requests.get(
self.icon_url,
timeout=10,
headers={"User-Agent": "EasyUI-Installer/1.0"}
)
response.raise_for_status()
# 将下载的内容转换为QIcon
image = QImage()
if image.loadFromData(response.content):
icon = QIcon(QPixmap.fromImage(image))
self.download_finished.emit(True, icon, response.content)
else:
self.download_finished.emit(False, QIcon(), b'')
except Exception as e:
print(f"图标下载失败: {str(e)}")
self.download_finished.emit(False, QIcon(), b'')
# --------------------------- 多文件下载管理器 ---------------------------
class MultiFileDownloader(QThread):
overall_progress = pyqtSignal(int) # 总体进度
file_progress = pyqtSignal(int, str, float) # (单个进度, 文件名, 速度)
file_finished = pyqtSignal(bool, str, str) # 单个文件完成
all_finished = pyqtSignal() # 所有文件完成
time_remaining = pyqtSignal(str) # 剩余时间格式mm:ss
def __init__(self, downloads):
super().__init__()
self.downloads = downloads # [(url, save_path, file_name), ...]
self.total_files = len(downloads)
self.completed_files = 0
self.current_file_index = 0
self.current_thread = None
self.stopped = False
self.current_speed = 0 # 当前下载速度MB/s
self.remaining_size = 0 # 剩余文件总大小MB
def run(self):
# 预计算总大小(用于剩余时间估算)
self.calc_total_remaining_size()
for i, (url, save_path, file_name) in enumerate(self.downloads):
if self.stopped:
break
self.current_file_index = i
self.file_progress.emit(0, file_name, 0.0)
# 创建当前文件下载线程
self.current_thread = DownloadThread(url, save_path, file_name)
self.current_thread.progress_updated.connect(self.on_file_progress)
self.current_thread.download_finished.connect(self.on_file_complete)
self.current_thread.start()
self.current_thread.wait()
self.all_finished.emit()
def calc_total_remaining_size(self):
"""计算剩余文件总大小MB"""
self.remaining_size = 0
for url, save_path, file_name in self.downloads:
try:
response = requests.head(url, timeout=10)
file_size = int(response.headers.get('content-length', 0)) / (1024 * 1024)
self.remaining_size += file_size
except:
self.remaining_size += 10 # 估算未知大小文件为10MB
def on_file_progress(self, progress, file_name, speed):
self.current_speed = speed
self.file_progress.emit(progress, file_name, speed)
# 计算总体进度
completed_size_ratio = (self.completed_files + progress/100) / self.total_files
overall = int(completed_size_ratio * 100)
self.overall_progress.emit(overall)
# 估算剩余时间(仅当速度>0时
if speed > 0.01:
# 计算当前文件剩余大小 + 未开始文件大小
current_file_remaining = (1 - progress/100) * (self.remaining_size / self.total_files)
unstarted_files_size = (self.total_files - self.current_file_index - 1) * (self.remaining_size / self.total_files)
total_remaining = current_file_remaining + unstarted_files_size
remaining_seconds = int(total_remaining / speed)
minutes = remaining_seconds // 60
seconds = remaining_seconds % 60
self.time_remaining.emit(f"{minutes:02d}:{seconds:02d}")
else:
self.time_remaining.emit("--:--")
def on_file_complete(self, success, message, file_name):
self.file_finished.emit(success, message, file_name)
if success:
self.completed_files += 1
# 更新剩余大小
self.remaining_size -= (self.remaining_size / self.total_files)
def stop(self):
self.stopped = True
if self.current_thread and self.current_thread.isRunning():
self.current_thread.terminate()
# --------------------------- 主安装窗口 ---------------------------
class EasyUIInstaller(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Easy UI 安装程序")
self.setGeometry(300, 200, 700, 500)
self.setMinimumSize(700, 500)
self.setStyleSheet(GLOBAL_STYLE)
# 图标配置 - 使用网络图标
self.icon_url = "https://sunshinetown.oss-cn-shenzhen.aliyuncs.com/eui.ico" # 网络图标路径
self.app_icon = QIcon() # 初始化空图标
self.icon_data = b'' # 存储图标原始数据,用于创建快捷方式
self.download_icon() # 启动图标下载
# 核心数据初始化 - 固定下载路径C:\用户\自动识别用户名\JGZ_YES\EasyUILang\
# 获取当前用户名
self.user_name = os.path.expanduser("~").split(os.sep)[-1]
# 构建固定路径
self.fixed_install_dir = os.path.join("C:", "用户", self.user_name, "JGZ_YES", "EasyUILang")
self.resources = {
"interpreter": {
"name": "Easy UI 解释器",
"url": "https://sunshinetown.oss-cn-shenzhen.aliyuncs.com/easy_ui_interpreter.exe",
"default_path": os.path.join(self.fixed_install_dir, "easy_ui_interpreter.exe")
},
"editor": {
"name": "Easy UI 编辑器",
"url": "https://sunshinetown.oss-cn-shenzhen.aliyuncs.com/EasyUI_Editor.exe",
"default_path": os.path.join(self.fixed_install_dir, "EasyUI_Editor.exe")
}
}
self.download_queue = []
self.download_manager = None
# 初始化分步容器
self.init_stacked_widget()
def download_icon(self):
"""下载网络图标"""
print(f"正在下载图标: {self.icon_url}")
self.icon_thread = IconDownloadThread(self.icon_url)
self.icon_thread.download_finished.connect(self.on_icon_downloaded)
self.icon_thread.start()
def on_icon_downloaded(self, success, icon, data):
"""图标下载完成回调"""
if success and not icon.isNull():
print("图标下载成功")
self.app_icon = icon
self.icon_data = data # 保存图标原始数据
self.setWindowIcon(self.app_icon)
# 更新界面上的图标
if hasattr(self, 'logo_label'):
pixmap = self.app_icon.pixmap(
self.logo_label.width(),
self.logo_label.height(),
mode=QtGuiQIcon.Normal,
state=QtGuiQIcon.On
)
self.logo_label.setPixmap(pixmap)
if hasattr(self, 'complete_icon'):
pixmap = self.app_icon.pixmap(
self.complete_icon.width(),
self.complete_icon.height(),
mode=QtGuiQIcon.Normal,
state=QtGuiQIcon.On
)
self.complete_icon.setPixmap(pixmap)
else:
print("图标下载失败,使用默认图标")
self.app_icon = QIcon.fromTheme("application-x-executable")
self.setWindowIcon(self.app_icon)
def init_stacked_widget(self):
"""创建分步容器"""
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.main_layout = QVBoxLayout(self.central_widget)
self.main_layout.setContentsMargins(20, 20, 20, 20)
self.main_layout.setSpacing(20)
# 1. 顶部Logo区域
self.logo_layout = QHBoxLayout()
self.logo_label = QLabel()
self.logo_label.setFixedSize(40, 40) # 固定Logo大小
# 初始显示默认图标,下载完成后会更新
if self.app_icon.isNull():
palette = QPalette()
palette.setColor(QPalette.Background, QColor("#1E88E5"))
self.logo_label.setAutoFillBackground(True)
self.logo_label.setPalette(palette)
else:
pixmap = self.app_icon.pixmap(
self.logo_label.width(),
self.logo_label.height()
)
self.logo_label.setPixmap(pixmap)
self.app_name_label = QLabel("Easy UI")
self.app_name_label.setStyleSheet("font-size: 16pt; font-weight: bold; color: #1E88E5;")
self.logo_layout.addWidget(self.logo_label)
self.logo_layout.addWidget(self.app_name_label)
self.logo_layout.addStretch()
self.main_layout.addLayout(self.logo_layout)
# 2. 分步内容容器
self.stacked_widget = QStackedWidget()
self.main_layout.addWidget(self.stacked_widget, 1) # 占主要空间
# 3. 底部按钮区域
self.btn_layout = QHBoxLayout()
self.btn_layout.setSpacing(10)
self.main_layout.addLayout(self.btn_layout)
# 初始化各页面
self.init_welcome_page()
self.init_license_page()
self.init_component_page()
self.init_progress_page()
self.init_complete_page()
# 默认显示欢迎页
self.stacked_widget.setCurrentIndex(0)
self.update_buttons_by_page(0)
# --------------------------- 页面1欢迎页 ---------------------------
def init_welcome_page(self):
self.welcome_page = QWidget()
self.welcome_layout = QVBoxLayout(self.welcome_page)
self.welcome_layout.setAlignment(Qt.AlignCenter)
self.welcome_layout.setSpacing(30)
# 标题和副标题
self.welcome_title = QLabel("欢迎使用 Easy UI 安装程序")
self.welcome_title.setObjectName("TitleLabel")
self.welcome_title.setAlignment(Qt.AlignCenter)
self.welcome_subtitle = QLabel("本程序将引导您完成 Easy UI 的安装预计耗时2-5分钟")
self.welcome_subtitle.setObjectName("SubtitleLabel")
self.welcome_subtitle.setAlignment(Qt.AlignCenter)
# 系统要求提示
self.system_require_label = QLabel(f"""
系统要求:
• Windows 10 及以上版本64位
• 至少 100MB 可用磁盘空间
• 稳定的网络连接(用于下载组件)
安装路径:
C:\\用户\\{self.user_name}\\JGZ_YES\\EasyUILang\\
""")
self.system_require_label.setObjectName("SubtitleLabel")
self.system_require_label.setAlignment(Qt.AlignLeft)
self.welcome_layout.addWidget(self.welcome_title)
self.welcome_layout.addWidget(self.welcome_subtitle)
self.welcome_layout.addWidget(self.system_require_label)
self.stacked_widget.addWidget(self.welcome_page)
# --------------------------- 页面2许可协议可滚动版本 ---------------------------
def init_license_page(self):
self.license_page = QWidget()
self.license_layout = QVBoxLayout(self.license_page)
self.license_layout.setSpacing(20)
# 标题
self.license_title = QLabel("软件许可协议")
self.license_title.setObjectName("TitleLabel")
self.license_layout.addWidget(self.license_title)
# 协议内容面板(带滚动功能)
self.license_frame = QFrame()
self.license_frame.setObjectName("CardFrame")
self.license_frame.setFixedHeight(250)
self.license_inner_layout = QVBoxLayout(self.license_frame)
# 添加滚动区域
self.license_scroll = QScrollArea()
self.license_scroll.setWidgetResizable(True)
self.license_scroll_content = QWidget()
self.license_scroll_layout = QVBoxLayout(self.license_scroll_content)
# 协议文本
self.license_text = QTextEdit()
self.license_text.setReadOnly(True)
self.license_text.setText("""
Easy UI 软件许可协议
1. 许可范围
本软件仅供个人非商业使用,禁止未经授权用于商业用途、盈利活动或非法行为。
2. 用户权利
• 免费使用软件的全部功能及更新服务
• 在许可范围内修改个人使用的软件副本
• 获得软件相关的技术支持(限非商业用途)
3. 用户义务
• 不得传播经过篡改、破解或植入恶意代码的软件版本
• 不得利用软件侵犯他人知识产权、隐私或其他合法权益
• 不得违反国家法律法规及相关政策使用软件
4. 免责声明
• 软件按“现状”提供,开发者不保证无故障运行或完全满足用户需求
• 开发者不对软件使用过程中产生的任何直接或间接损失承担责任
• 因用户违反本协议导致的法律风险,由用户自行承担
5. 协议生效与终止
• 用户点击“同意”即表示完全接受本协议所有条款
• 若用户违反本协议,开发者有权终止其使用许可
""")
self.license_scroll_layout.addWidget(self.license_text)
self.license_scroll.setWidget(self.license_scroll_content)
self.license_inner_layout.addWidget(self.license_scroll)
self.license_layout.addWidget(self.license_frame)
# 同意勾选框
self.agree_check = QCheckBox("我已阅读并同意上述许可协议")
self.agree_check.setObjectName("SubtitleLabel")
self.agree_check.setChecked(False)
self.license_layout.addWidget(self.agree_check)
self.license_layout.addStretch()
self.stacked_widget.addWidget(self.license_page)
# --------------------------- 页面3组件选择 ---------------------------
def init_component_page(self):
self.component_page = QWidget()
self.component_layout = QVBoxLayout(self.component_page)
self.component_layout.setSpacing(20)
# 标题和副标题
self.component_title = QLabel("选择安装组件")
self.component_title.setObjectName("TitleLabel")
self.component_layout.addWidget(self.component_title)
self.component_subtitle = QLabel("默认安装所有组件,您可根据需求取消不需要的选项")
self.component_subtitle.setObjectName("SubtitleLabel")
self.component_layout.addWidget(self.component_subtitle)
# 组件选择面板
self.component_frame = QFrame()
self.component_frame.setObjectName("CardFrame")
self.component_inner_layout = QVBoxLayout(self.component_frame)
self.component_inner_layout.setContentsMargins(20, 20, 20, 20)
self.component_inner_layout.setSpacing(25)
# 解释器组件
self.interpreter_group = QWidget()
self.interpreter_group_layout = QVBoxLayout(self.interpreter_group)
self.interpreter_group_layout.setSpacing(10)
self.interpreter_check = QCheckBox(f"{self.resources['interpreter']['name']} (必需组件)")
self.interpreter_check.setObjectName("SubtitleLabel")
self.interpreter_check.setChecked(True)
self.interpreter_check.setEnabled(False)
self.interpreter_group_layout.addWidget(self.interpreter_check)
# 解释器路径选择(固定路径,不可修改)
self.interpreter_path_layout = QHBoxLayout()
self.interpreter_path_label = QLabel("安装位置:")
self.interpreter_path_label.setObjectName("SubtitleLabel")
self.interpreter_path = QLineEdit(self.resources['interpreter']['default_path'])
self.interpreter_path.setReadOnly(True) # 设置为只读,防止修改
self.interpreter_browse_btn = QPushButton("浏览...")
self.interpreter_browse_btn.setObjectName("SecondaryBtn")
self.interpreter_browse_btn.clicked.connect(lambda: self.show_path_info())
self.interpreter_path_layout.addWidget(self.interpreter_path_label)
self.interpreter_path_layout.addWidget(self.interpreter_path, 1)
self.interpreter_path_layout.addWidget(self.interpreter_browse_btn)
self.interpreter_group_layout.addLayout(self.interpreter_path_layout)
self.component_inner_layout.addWidget(self.interpreter_group)
# 编辑器组件
self.editor_group = QWidget()
self.editor_group_layout = QVBoxLayout(self.editor_group)
self.editor_group_layout.setSpacing(10)
self.editor_check = QCheckBox(f"{self.resources['editor']['name']} (推荐组件)")
self.editor_check.setObjectName("SubtitleLabel")
self.editor_check.setChecked(True)
self.editor_group_layout.addWidget(self.editor_check)
# 编辑器路径选择(固定路径,不可修改)
self.editor_path_layout = QHBoxLayout()
self.editor_path_label = QLabel("安装位置:")
self.editor_path_label.setObjectName("SubtitleLabel")
self.editor_path = QLineEdit(self.resources['editor']['default_path'])
self.editor_path.setReadOnly(True) # 设置为只读,防止修改
self.editor_browse_btn = QPushButton("浏览...")
self.editor_browse_btn.setObjectName("SecondaryBtn")
self.editor_browse_btn.clicked.connect(lambda: self.show_path_info())
self.editor_path_layout.addWidget(self.editor_path_label)
self.editor_path_layout.addWidget(self.editor_path, 1)
self.editor_path_layout.addWidget(self.editor_browse_btn)
self.editor_group_layout.addLayout(self.editor_path_layout)
self.component_inner_layout.addWidget(self.editor_group)
self.component_layout.addWidget(self.component_frame)
self.stacked_widget.addWidget(self.component_page)
def show_path_info(self):
"""显示路径信息提示,告知用户路径不可修改"""
QMessageBox.information(
self, "安装路径说明",
f"软件将固定安装到以下路径:\nC:\\用户\\{self.user_name}\\JGZ_YES\\EasyUILang\\"
)
# --------------------------- 页面4安装进度 ---------------------------
def init_progress_page(self):
self.progress_page = QWidget()
self.progress_layout = QVBoxLayout(self.progress_page)
self.progress_layout.setSpacing(20)
# 标题
self.progress_title = QLabel("正在安装")
self.progress_title.setObjectName("TitleLabel")
self.progress_layout.addWidget(self.progress_title)
# 进度面板
self.progress_frame = QFrame()
self.progress_frame.setObjectName("CardFrame")
self.progress_inner_layout = QVBoxLayout(self.progress_frame)
self.progress_inner_layout.setContentsMargins(20, 20, 20, 20)
self.progress_inner_layout.setSpacing(15)
# 当前任务提示
self.current_task_label = QLabel("准备下载安装组件...")
self.current_task_label.setObjectName("SubtitleLabel")
self.progress_inner_layout.addWidget(self.current_task_label)
# 总体进度条
self.overall_progress_bar = QProgressBar()
self.overall_progress_bar.setValue(0)
self.progress_inner_layout.addWidget(self.overall_progress_bar)
# 进度详情
self.progress_detail_layout = QHBoxLayout()
self.speed_label = QLabel("速度0.00 MB/s")
self.speed_label.setObjectName("SubtitleLabel")
self.time_remaining_label = QLabel("剩余时间:--:--")
self.time_remaining_label.setObjectName("SubtitleLabel")
self.progress_detail_layout.addWidget(self.speed_label)
self.progress_detail_layout.addStretch()
self.progress_detail_layout.addWidget(self.time_remaining_label)
self.progress_inner_layout.addLayout(self.progress_detail_layout)
self.progress_layout.addWidget(self.progress_frame)
self.stacked_widget.addWidget(self.progress_page)
# --------------------------- 页面5安装完成 ---------------------------
def init_complete_page(self):
self.complete_page = QWidget()
self.complete_layout = QVBoxLayout(self.complete_page)
self.complete_layout.setSpacing(25)
self.complete_layout.setAlignment(Qt.AlignCenter)
# 完成图标
self.complete_icon = QLabel()
self.complete_icon.setFixedSize(80, 80)
# 初始显示默认图标
if self.app_icon.isNull():
palette = QPalette()
palette.setColor(QPalette.Background, QColor("#4CAF50"))
self.complete_icon.setAutoFillBackground(True)
self.complete_icon.setPalette(palette)
self.complete_icon.setStyleSheet("border-radius: 40px;")
else:
pixmap = self.app_icon.pixmap(
self.complete_icon.width(),
self.complete_icon.height()
)
self.complete_icon.setPixmap(pixmap)
self.complete_layout.addWidget(self.complete_icon, alignment=Qt.AlignCenter)
# 完成标题
self.complete_title = QLabel("安装完成!")
self.complete_title.setObjectName("TitleLabel")
self.complete_layout.addWidget(self.complete_title, alignment=Qt.AlignCenter)
# 完成提示
self.complete_subtitle = QLabel(f"Easy UI 已成功安装到:\nC:\\用户\\{self.user_name}\\JGZ_YES\\EasyUILang\\")
self.complete_subtitle.setObjectName("SubtitleLabel")
self.complete_subtitle.setAlignment(Qt.AlignCenter)
self.complete_layout.addWidget(self.complete_subtitle, alignment=Qt.AlignCenter)
# 后续操作选项
self.complete_options_layout = QVBoxLayout()
self.run_check = QCheckBox("立即运行 Easy UI 编辑器")
self.run_check.setObjectName("SubtitleLabel")
self.run_check.setChecked(True)
self.desktop_shortcut_check = QCheckBox("创建桌面快捷方式")
self.desktop_shortcut_check.setObjectName("SubtitleLabel")
self.desktop_shortcut_check.setChecked(True)
self.complete_options_layout.addWidget(self.run_check)
self.complete_options_layout.addWidget(self.desktop_shortcut_check)
self.complete_layout.addLayout(self.complete_options_layout)
self.stacked_widget.addWidget(self.complete_page)
# --------------------------- 核心交互逻辑 ---------------------------
def update_buttons_by_page(self, page_index):
"""根据当前页面更新底部按钮"""
# 清空现有按钮
for i in range(self.btn_layout.count()):
widget = self.btn_layout.itemAt(i).widget()
if widget:
widget.deleteLater()
# 根据页面索引添加按钮
if page_index == 0: # 欢迎页:仅“下一步”
self.next_btn = QPushButton("下一步")
self.next_btn.setObjectName("PrimaryBtn")
self.next_btn.clicked.connect(lambda: self.switch_page(1))
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.next_btn)
elif page_index == 1: # 许可协议:“上一步”+“下一步”
self.prev_btn = QPushButton("上一步")
self.prev_btn.setObjectName("SecondaryBtn")
self.prev_btn.clicked.connect(lambda: self.switch_page(0))
self.next_btn = QPushButton("下一步")
self.next_btn.setObjectName("PrimaryBtn")
self.next_btn.setEnabled(False)
self.next_btn.clicked.connect(lambda: self.switch_page(2))
# 勾选框联动按钮状态
self.agree_check.stateChanged.connect(
lambda state: self.next_btn.setEnabled(state == Qt.Checked)
)
self.btn_layout.addWidget(self.prev_btn)
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.next_btn)
elif page_index == 2: # 组件选择:“上一步”+“安装”
self.prev_btn = QPushButton("上一步")
self.prev_btn.setObjectName("SecondaryBtn")
self.prev_btn.clicked.connect(lambda: self.switch_page(1))
self.install_btn = QPushButton("开始安装")
self.install_btn.setObjectName("PrimaryBtn")
self.install_btn.clicked.connect(self.start_install)
self.btn_layout.addWidget(self.prev_btn)
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.install_btn)
elif page_index == 3: # 安装进度:“取消”
self.cancel_btn = QPushButton("取消安装")
self.cancel_btn.setObjectName("SecondaryBtn")
self.cancel_btn.clicked.connect(self.cancel_install)
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.cancel_btn)
elif page_index == 4: # 安装完成:“完成”
self.finish_btn = QPushButton("完成")
self.finish_btn.setObjectName("PrimaryBtn")
self.finish_btn.clicked.connect(self.finish_install)
self.btn_layout.addStretch()
self.btn_layout.addWidget(self.finish_btn)
def switch_page(self, target_index):
"""切换页面并更新按钮"""
self.stacked_widget.setCurrentIndex(target_index)
self.update_buttons_by_page(target_index)
def start_install(self):
"""验证组件并开始安装"""
# 构建下载队列(固定路径)
self.download_queue = []
if self.interpreter_check.isChecked():
inter_path = self.resources['interpreter']['default_path']
self.download_queue.append((
self.resources['interpreter']['url'],
inter_path,
self.resources['interpreter']['name']
))
if self.editor_check.isChecked():
edit_path = self.resources['editor']['default_path']
self.download_queue.append((
self.resources['editor']['url'],
edit_path,
self.resources['editor']['name']
))
# 验证路径权限
if not self.verify_path_permission():
return
# 跳转到进度页并启动下载
self.switch_page(3)
self.download_manager = MultiFileDownloader(self.download_queue)
self.download_manager.overall_progress.connect(self.overall_progress_bar.setValue)
self.download_manager.file_progress.connect(self.update_progress_detail)
self.download_manager.time_remaining.connect(self.time_remaining_label.setText)
self.download_manager.file_finished.connect(self.on_file_finished)
self.download_manager.all_finished.connect(self.on_all_finished)
self.download_manager.start()
def verify_path_permission(self):
"""验证安装路径是否有写入权限"""
for url, path, name in self.download_queue:
try:
test_dir = os.path.dirname(path)
if not os.path.exists(test_dir):
os.makedirs(test_dir)
# 测试写入
test_file = os.path.join(test_dir, "test_permission.tmp")
with open(test_file, 'w') as f:
f.write("test")
os.remove(test_file)
except Exception as e:
QMessageBox.warning(
self, "路径权限错误",
f"无法写入{name}的安装路径:\n{str(e)}\n请检查路径权限或以管理员身份运行安装程序。"
)
return False
return True
def update_progress_detail(self, progress, file_name, speed):
"""更新进度详情"""
self.current_task_label.setText(f"正在安装 {file_name}... ({progress}%)")
self.speed_label.setText(f"速度:{speed:.2f} MB/s")
def on_file_finished(self, success, message, file_name):
"""单个文件安装完成回调"""
if not success:
QMessageBox.warning(self, f"{file_name}安装失败", message)
def on_all_finished(self):
"""所有文件安装完成"""
# 创建桌面快捷方式(如果勾选)
if self.desktop_shortcut_check.isChecked():
self.create_desktop_shortcut()
self.switch_page(4)
def create_desktop_shortcut(self):
"""创建桌面快捷方式"""
editor_path = self.resources['editor']['default_path']
if not os.path.exists(editor_path):
QMessageBox.warning(self, "创建失败", "未找到Easy UI编辑器可执行文件无法创建快捷方式")
return
# 尝试创建快捷方式
try:
import pythoncom
import win32com.client
from winshell import desktop
# 初始化COM
pythoncom.CoInitialize()
# 获取桌面路径
desktop_path = desktop()
shortcut_path = os.path.join(desktop_path, "Easy UI 编辑器.lnk")
# 保存图标到本地临时文件
icon_temp_path = os.path.join(self.fixed_install_dir, "eui_icon.ico")
if self.icon_data:
with open(icon_temp_path, 'wb') as f:
f.write(self.icon_data)
# 创建快捷方式
shell = win32com.client.Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(shortcut_path)
shortcut.TargetPath = editor_path
shortcut.WorkingDirectory = self.fixed_install_dir
shortcut.Description = "Easy UI 编辑器"
shortcut.Hotkey = "Ctrl+Alt+E"
# 设置图标
if os.path.exists(icon_temp_path):
shortcut.IconLocation = icon_temp_path
else:
shortcut.IconLocation = editor_path
shortcut.save()
pythoncom.CoUninitialize()
return
except ImportError:
# 备用方案使用PowerShell命令
try:
import subprocess
# 获取桌面路径
desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
shortcut_path = os.path.join(desktop_path, "Easy UI 编辑器.lnk")
# 保存图标
icon_temp_path = os.path.join(self.fixed_install_dir, "eui_icon.ico")
if self.icon_data:
with open(icon_temp_path, 'wb') as f:
f.write(self.icon_data)
# PowerShell命令
ps_command = f'''
$WshShell = New-Object -ComObject WScript.Shell
$shortcut = $WshShell.CreateShortcut("{shortcut_path}")
$shortcut.TargetPath = "{editor_path}"
$shortcut.WorkingDirectory = "{self.fixed_install_dir}"
$shortcut.Description = "Easy UI 编辑器"
{f'$shortcut.IconLocation = "{icon_temp_path}"' if os.path.exists(icon_temp_path) else ''}
$shortcut.Save()
'''
subprocess.run(
["powershell", "-Command", ps_command],
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
return
except Exception as e:
print(f"创建快捷方式失败:{str(e)}")
QMessageBox.warning(
self, "创建失败",
"无法自动创建桌面快捷方式,您可以手动创建:\n"
f"1. 找到文件:{editor_path}\n"
"2. 右键点击文件,选择'发送到'->'桌面快捷方式'"
)
def cancel_install(self):
"""取消安装"""
if self.download_manager and self.download_manager.isRunning():
confirm = QMessageBox.question(
self, "确认取消",
"取消安装将终止当前下载,已下载的文件可能不完整,是否继续?",
QMessageBox.Yes | QMessageBox.No
)
if confirm == QMessageBox.Yes:
self.download_manager.stop()
self.switch_page(2) # 回到组件选择页
def finish_install(self):
"""完成安装"""
# 运行程序(如果勾选)
if self.run_check.isChecked():
editor_path = self.resources['editor']['default_path']
if os.path.exists(editor_path):
os.startfile(editor_path)
# 关闭安装程序
self.close()
# 程序入口
if __name__ == "__main__":
# 先设置高DPI属性
if hasattr(Qt, 'AA_EnableHighDpiScaling'):
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
if hasattr(Qt, 'AA_UseHighDpiPixmaps'):
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
# 确保中文显示正常
app = QApplication(sys.argv)
app.setFont(QFont("微软雅黑", 9))
window = EasyUIInstaller()
window.show()
sys.exit(app.exec_())

2056
easy_ui_editor.py Normal file

File diff suppressed because it is too large Load Diff

686
easy_ui_interpreter.py Normal file
View File

@@ -0,0 +1,686 @@
import sys
import os
import re
from PyQt5.QtWidgets import (QApplication, QMainWindow, QLabel, QLineEdit,
QComboBox, QCheckBox, QPushButton, QWidget,
QVBoxLayout, QHBoxLayout, QMessageBox, QFrame,
QTextEdit, QSlider, QProgressBar, QCalendarWidget,
QGroupBox, QRadioButton)
from PyQt5.QtCore import Qt, QUrl, QTimer, pyqtSlot
from PyQt5.QtGui import QIcon, QIntValidator, QPixmap, QImage
from PyQt5.QtMultimedia import QMediaPlayer, QMediaContent
from urllib.request import urlopen
class EasyUIInterpreter:
def __init__(self):
self.app = None
self.window = None
self.widgets = {} # 存储所有组件
self.variables = {} # 存储可交互组件
self.main_layout = None
self.media_players = {} # 仅保留音频播放器
self.timers = {} # 存储定时器
self.groups = {}
def parse_and_run(self, code):
if not QApplication.instance():
self.app = QApplication(sys.argv)
else:
self.app = QApplication.instance()
# 重置UI状态
self.widgets = {}
self.variables = {}
self.media_players = {}
self.timers = {}
self.groups = {}
self.window = None
self.main_layout = None
lines = [line.strip() for line in code.split('\n') if line.strip()]
for line in lines:
self.parse_line(line)
if not self.window:
self.create_window("EUI默认窗口", 400, 300)
else:
self.main_layout.addStretch()
self.window.show()
sys.exit(self.app.exec_())
# ---------------------- 解析逻辑 ----------------------
def parse_line(self, line):
line = line.strip().rstrip(';')
if not line:
return
# 窗口配置
window_pattern = r'window\s*=\s*title="([^"]+)"\s*,\s*width=(\d+)\s*,\s*height=(\d+)(?:\s*,\s*icon="([^"]+)")?'
window_match = re.match(window_pattern, line)
if window_match:
title = window_match.group(1)
width = int(window_match.group(2))
height = int(window_match.group(3))
icon_path = window_match.group(4) if window_match.group(4) else None
self.create_window(title, width, height, icon_path)
return
# 文字标签
label_match = re.match(r'label\s*=\s*text="([^"]+)"\s*,\s*id=(\w+)', line)
if label_match:
self.create_label(label_match.group(1), label_match.group(2))
return
# 输入框
entry_pattern = r'entry\s*=\s*hint="([^"]+)"\s*,\s*id=(\w+)(?:\s*,\s*readonly=(true|false))?(?:\s*,\s*type=(number|text))?'
entry_match = re.match(entry_pattern, line)
if entry_match:
hint = entry_match.group(1)
widget_id = entry_match.group(2)
readonly = entry_match.group(3).lower() == 'true' if entry_match.group(3) else False
input_type = entry_match.group(4) if entry_match.group(4) else 'text'
self.create_entry(hint, widget_id, readonly, input_type)
return
# 下拉选择框
combo_match = re.match(r'combo\s*=\s*label="([^"]+)"\s*,\s*id=(\w+)\s*,\s*options=\[(.*?)\]', line)
if combo_match:
options = [opt.strip().strip('"') for opt in combo_match.group(3).split(',') if opt.strip()]
self.create_combobox(combo_match.group(1), combo_match.group(2), options)
return
# 多选框组
check_match = re.match(r'checkbox\s*=\s*label="([^"]+)"\s*,\s*id=(\w+)\s*,\s*options=\[(.*?)\]', line)
if check_match:
options = [opt.strip().strip('"') for opt in check_match.group(3).split(',') if opt.strip()]
self.create_checkboxes(check_match.group(1), check_match.group(2), options)
return
# 按钮
button_match = re.match(r'button\s*=\s*text="([^"]+)"\s*,\s*id=(\w+)\s*,\s*click="([^"]+)"', line)
if button_match:
self.create_button(button_match.group(1), button_match.group(2), button_match.group(3))
return
# 音频播放器
audio_pattern = r'audio\s*=\s*(url|os)="([^"]+)"\s*,\s*id=(\w+)'
audio_match = re.match(audio_pattern, line)
if audio_match:
self.create_audio_player(audio_match.group(1), audio_match.group(2), audio_match.group(3))
return
# 图片组件
image_pattern = r'image\s*=\s*(path|url|os)="([^"]+)"\s*,\s*id=(\w+)(?:\s*,\s*width=(\d+))?(?:\s*,\s*height=(\d+))?(?:\s*,\s*tooltip="([^"]+)")?'
image_match = re.match(image_pattern, line)
if image_match:
img_type = image_match.group(1)
img_path = image_match.group(2)
img_id = image_match.group(3)
width = int(image_match.group(4)) if image_match.group(4) else None
height = int(image_match.group(5)) if image_match.group(5) else None
tooltip = image_match.group(6) if image_match.group(6) else ""
self.create_image(img_type, img_path, img_id, width, height, tooltip)
return
# 滑块控件
slider_pattern = r'slider\s*=\s*label="([^"]+)"\s*,\s*id=(\w+)\s*,\s*min=(\d+)\s*,\s*max=(\d+)\s*,\s*value=(\d+)'
slider_match = re.match(slider_pattern, line)
if slider_match:
self.create_slider(
slider_match.group(1), slider_match.group(2),
int(slider_match.group(3)), int(slider_match.group(4)), int(slider_match.group(5))
)
return
# 文本区域
textarea_pattern = r'textarea\s*=\s*label="([^"]+)"\s*,\s*id=(\w+)\s*,\s*rows=(\d+)(?:\s*,\s*readonly=(true|false))?'
textarea_match = re.match(textarea_pattern, line)
if textarea_match:
readonly = textarea_match.group(4).lower() == 'true' if textarea_match.group(4) else False
self.create_textarea(textarea_match.group(1), textarea_match.group(2), int(textarea_match.group(3)), readonly)
return
# 分隔线
separator_match = re.match(r'separator\s*=\s*text="([^"]*)"\s*,\s*id=(\w+)', line)
if separator_match:
self.create_separator(separator_match.group(1), separator_match.group(2))
return
# 进度条
progress_pattern = r'progress\s*=\s*label="([^"]+)"\s*,\s*id=(\w+)\s*,\s*min=(\d+)\s*,\s*max=(\d+)\s*,\s*value=(\d+)'
progress_match = re.match(progress_pattern, line)
if progress_match:
self.create_progressbar(
progress_match.group(1), progress_match.group(2),
int(progress_match.group(3)), int(progress_match.group(4)), int(progress_match.group(5))
)
return
# 日历控件
calendar_match = re.match(r'calendar\s*=\s*label="([^"]+)"\s*,\s*id=(\w+)', line)
if calendar_match:
self.create_calendar(calendar_match.group(1), calendar_match.group(2))
return
# 单选按钮组
radio_match = re.match(r'radiogroup\s*=\s*label="([^"]+)"\s*,\s*id=(\w+)\s*,\s*options=\[(.*?)\]', line)
if radio_match:
options = [opt.strip().strip('"') for opt in radio_match.group(3).split(',') if opt.strip()]
self.create_radiogroup(radio_match.group(1), radio_match.group(2), options)
return
# 分组框
groupbox_match = re.match(r'groupbox\s*=\s*title="([^"]+)"\s*,\s*id=(\w+)', line)
if groupbox_match:
self.create_groupbox(groupbox_match.group(1), groupbox_match.group(2))
return
# 定时器
timer_pattern = r'timer\s*=\s*id=(\w+)\s*,\s*interval=(\d+)\s*,\s*action="([^"]+)"'
timer_match = re.match(timer_pattern, line)
if timer_match:
self.create_timer(timer_match.group(1), int(timer_match.group(2)), timer_match.group(3))
return
# ---------------------- 组件创建方法 ----------------------
def create_window(self, title, width, height, icon_path=None):
self.window = QMainWindow()
self.window.setWindowTitle(title)
self.window.resize(width, height)
if icon_path and os.path.exists(icon_path):
try:
self.window.setWindowIcon(QIcon(icon_path))
except Exception as e:
QMessageBox.warning(self.window, "警告", f"图标设置失败:{str(e)}")
central_widget = QWidget()
self.window.setCentralWidget(central_widget)
self.main_layout = QVBoxLayout(central_widget)
self.main_layout.setContentsMargins(20, 20, 20, 20)
self.main_layout.setSpacing(15)
def create_label(self, text, widget_id):
if not self.window:
self.create_window("默认窗口", 400, 300)
label = QLabel(text)
label.setMinimumHeight(30)
self._get_current_layout().addWidget(label)
self.widgets[widget_id] = label
def create_entry(self, hint, widget_id, readonly=False, input_type='text'):
if not self.window:
self.create_window("默认窗口", 400, 300)
container = QWidget()
container.setMinimumHeight(30)
layout = QHBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(10)
label = QLabel(hint)
entry = QLineEdit()
entry.setReadOnly(readonly)
if input_type == 'number':
entry.setValidator(QIntValidator())
layout.addWidget(label)
layout.addWidget(entry)
self._get_current_layout().addWidget(container)
self.widgets[widget_id] = entry
self.variables[widget_id] = entry
def create_combobox(self, label_text, widget_id, options):
if not self.window:
self.create_window("默认窗口", 400, 300)
container = QWidget()
container.setMinimumHeight(30)
layout = QHBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(10)
label = QLabel(label_text)
combo = QComboBox()
combo.addItems(options)
layout.addWidget(label)
layout.addWidget(combo)
self._get_current_layout().addWidget(container)
self.widgets[widget_id] = combo
self.variables[widget_id] = combo
def create_checkboxes(self, label_text, widget_id, options):
if not self.window:
self.create_window("默认窗口", 400, 300)
container = QWidget()
container.setMinimumHeight(60)
layout = QVBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(5)
title_label = QLabel(label_text)
layout.addWidget(title_label)
check_layout = QHBoxLayout()
check_layout.setSpacing(15)
checkboxes = []
for opt in options:
cb = QCheckBox(opt)
check_layout.addWidget(cb)
checkboxes.append(cb)
layout.addLayout(check_layout)
self._get_current_layout().addWidget(container)
self.widgets[widget_id] = checkboxes
self.variables[widget_id] = checkboxes
def create_button(self, text, widget_id, action):
if not self.window:
self.create_window("默认窗口", 400, 300)
button = QPushButton(text)
button.setMinimumHeight(30)
button.setMaximumWidth(150)
button.clicked.connect(lambda checked, a=action: self.handle_button_click(a))
self._get_current_layout().addWidget(button, alignment=Qt.AlignLeft)
self.widgets[widget_id] = button
def create_audio_player(self, audio_type, audio_path, audio_id):
player = QMediaPlayer()
self.media_players[audio_id] = {
"player": player,
"type": "audio"
}
try:
if audio_type == "url":
media = QMediaContent(QUrl(audio_path))
else:
abs_path = os.path.abspath(audio_path).replace(" ", "%20") # 处理空格
if not os.path.exists(abs_path.replace("%20", " ")):
QMessageBox.warning(self.window, "警告", f"音频文件不存在:{abs_path.replace('%20', ' ')}")
return
media = QMediaContent(QUrl.fromLocalFile(abs_path))
player.setMedia(media)
except Exception as e:
QMessageBox.warning(self.window, "警告", f"音频加载失败:{str(e)}")
def create_image(self, img_type, img_path, img_id, width=None, height=None, tooltip=""):
if not self.window:
self.create_window("默认窗口", 400, 300)
container = QWidget()
container.setMinimumHeight(height if height else 100)
container.setMinimumWidth(width if width else 100)
layout = QVBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
img_label = QLabel()
img_label.setToolTip(tooltip)
img_label.setAlignment(Qt.AlignCenter)
pixmap = None
try:
if img_type == "path":
if img_path.startswith(('http://', 'https://')):
with urlopen(img_path) as response:
img_data = response.read()
image = QImage.fromData(img_data)
pixmap = QPixmap.fromImage(image)
else:
abs_path = os.path.abspath(img_path)
if os.path.exists(abs_path):
pixmap = QPixmap(abs_path)
else:
img_label.setText("图片文件不存在")
QMessageBox.warning(self.window, "警告", f"本地图片路径不存在:{abs_path}")
elif img_type == "url":
with urlopen(img_path) as response:
img_data = response.read()
image = QImage.fromData(img_data)
pixmap = QPixmap.fromImage(image)
elif img_type == "os":
abs_path = os.path.abspath(img_path)
if os.path.exists(abs_path):
pixmap = QPixmap(abs_path)
else:
img_label.setText("图片文件不存在")
QMessageBox.warning(self.window, "警告", f"本地图片路径不存在:{abs_path}")
except Exception as e:
img_label.setText("图片加载失败")
QMessageBox.warning(self.window, "警告", f"图片加载失败:{str(e)}")
if pixmap and not pixmap.isNull():
if width and height:
pixmap = pixmap.scaled(width, height, Qt.KeepAspectRatio, Qt.SmoothTransformation)
elif width:
pixmap = pixmap.scaledToWidth(width, Qt.SmoothTransformation)
elif height:
pixmap = pixmap.scaledToHeight(height, Qt.SmoothTransformation)
img_label.setPixmap(pixmap)
layout.addWidget(img_label)
self._get_current_layout().addWidget(container)
self.widgets[img_id] = img_label
self.variables[img_id] = img_label
def create_slider(self, label_text, widget_id, min_val, max_val, value):
if not self.window:
self.create_window("默认窗口", 400, 300)
container = QWidget()
container.setMinimumHeight(60)
layout = QVBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(5)
value_label = QLabel(f"{label_text}{value}")
slider = QSlider(Qt.Horizontal)
slider.setRange(min_val, max_val)
slider.setValue(value)
slider.setTickInterval(1)
slider.setTickPosition(QSlider.TicksBelow)
slider.valueChanged.connect(lambda v: value_label.setText(f"{label_text}{v}"))
layout.addWidget(value_label)
layout.addWidget(slider)
self._get_current_layout().addWidget(container)
self.widgets[widget_id] = slider
self.variables[widget_id] = slider
def create_textarea(self, label_text, widget_id, rows, readonly=False):
if not self.window:
self.create_window("默认窗口", 400, 300)
container = QWidget()
layout = QVBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(5)
label = QLabel(label_text)
textarea = QTextEdit()
textarea.setReadOnly(readonly)
textarea.setMinimumHeight(rows * 25)
layout.addWidget(label)
layout.addWidget(textarea)
self._get_current_layout().addWidget(container)
self.widgets[widget_id] = textarea
self.variables[widget_id] = textarea
def create_separator(self, text, widget_id):
if not self.window:
self.create_window("默认窗口", 400, 300)
if text:
container = QWidget()
layout = QHBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(10)
left_line = QFrame()
left_line.setFrameShape(QFrame.HLine)
left_line.setFrameShadow(QFrame.Sunken)
right_line = QFrame()
right_line.setFrameShape(QFrame.HLine)
right_line.setFrameShadow(QFrame.Sunken)
label = QLabel(text)
layout.addWidget(left_line, 1)
layout.addWidget(label, 0, Qt.AlignCenter)
layout.addWidget(right_line, 1)
self._get_current_layout().addWidget(container)
self.widgets[widget_id] = container
else:
line = QFrame()
line.setFrameShape(QFrame.HLine)
line.setFrameShadow(QFrame.Sunken)
self._get_current_layout().addWidget(line)
self.widgets[widget_id] = line
def create_progressbar(self, label_text, widget_id, min_val, max_val, value):
if not self.window:
self.create_window("默认窗口", 400, 300)
container = QWidget()
container.setMinimumHeight(50)
layout = QVBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(5)
label = QLabel(label_text)
progress = QProgressBar()
progress.setRange(min_val, max_val)
progress.setValue(value)
progress.setTextVisible(True)
layout.addWidget(label)
layout.addWidget(progress)
self._get_current_layout().addWidget(container)
self.widgets[widget_id] = progress
self.variables[widget_id] = progress
def create_calendar(self, label_text, widget_id):
if not self.window:
self.create_window("默认窗口", 400, 300)
container = QWidget()
layout = QVBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(10)
label = QLabel(label_text)
calendar = QCalendarWidget()
calendar.setSelectionMode(QCalendarWidget.SingleSelection)
layout.addWidget(label)
layout.addWidget(calendar)
self._get_current_layout().addWidget(container)
self.widgets[widget_id] = calendar
self.variables[widget_id] = calendar
def create_radiogroup(self, label_text, widget_id, options):
if not self.window:
self.create_window("默认窗口", 400, 300)
container = QWidget()
layout = QVBoxLayout(container)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(5)
title_label = QLabel(label_text)
layout.addWidget(title_label)
radio_buttons = []
for i, opt in enumerate(options):
radio = QRadioButton(opt)
if i == 0:
radio.setChecked(True)
layout.addWidget(radio)
radio_buttons.append(radio)
self._get_current_layout().addWidget(container)
self.widgets[widget_id] = radio_buttons
self.variables[widget_id] = radio_buttons
def create_groupbox(self, title, group_id):
if not self.window:
self.create_window("默认窗口", 400, 300)
groupbox = QGroupBox(title)
group_layout = QVBoxLayout(groupbox)
group_layout.setContentsMargins(15, 15, 15, 15)
group_layout.setSpacing(10)
self._get_current_layout().addWidget(groupbox)
self.groups[group_id] = group_layout
self.widgets[group_id] = groupbox
def create_timer(self, timer_id, interval, action):
if timer_id in self.timers:
self.timers[timer_id]['timer'].stop()
timer = QTimer()
timer.setInterval(interval)
timer.timeout.connect(lambda: self.handle_timer_timeout(timer_id))
self.timers[timer_id] = {
'timer': timer,
'action': action
}
# ---------------------- 事件处理 ----------------------
def _get_current_layout(self):
return list(self.groups.values())[-1] if self.groups else self.main_layout
@pyqtSlot()
def handle_timer_timeout(self, timer_id):
if timer_id not in self.timers:
return
timer_info = self.timers[timer_id]
action = timer_info['action']
if action.startswith("update_progress="):
try:
progress_part, step_part = action.split(",")
progress_id = progress_part.split("=")[1].strip()
step = int(step_part.split("=")[1].strip())
progress_bar = self.widgets.get(progress_id)
if not progress_bar or not isinstance(progress_bar, QProgressBar):
return
current_value = progress_bar.value()
new_value = current_value + step
new_value = max(progress_bar.minimum(), min(progress_bar.maximum(), new_value))
progress_bar.setValue(new_value)
if new_value >= progress_bar.maximum():
timer_info['timer'].stop()
except Exception as e:
QMessageBox.warning(self.window, "定时器错误", f"更新进度条失败:{str(e)}")
def handle_button_click(self, action):
# 音频控制
if action.startswith("play_audio="):
self._control_audio(action.split("=")[1], "play")
return
if action.startswith("pause_audio="):
self._control_audio(action.split("=")[1], "pause")
return
if action.startswith("stop_audio="):
self._control_audio(action.split("=")[1], "stop")
return
# 定时器控制
if action.startswith("start_timer="):
timer_id = action.split("=")[1].strip()
self._control_timer(timer_id, "start")
return
if action.startswith("stop_timer="):
timer_id = action.split("=")[1].strip()
self._control_timer(timer_id, "stop")
return
# 进度条控制
if action.startswith("set_progress="):
parts = action.split(",")
if len(parts) >= 2 and parts[1].startswith("value="):
try:
p_id = parts[0].split("=")[1].strip()
val = int(parts[1].split("=")[1].strip())
if p_id in self.widgets and isinstance(self.widgets[p_id], QProgressBar):
self.widgets[p_id].setValue(val)
except Exception as e:
QMessageBox.warning(self.window, "错误", f"设置进度条失败:{str(e)}")
return
# 显示组件值
if action.startswith("显示="):
self._show_widget_value(action.split("=")[1].strip())
return
def _control_audio(self, audio_id, action):
if audio_id not in self.media_players or self.media_players[audio_id]["type"] != "audio":
QMessageBox.warning(self.window, "警告", f"音频组件ID不存在{audio_id}")
return
player = self.media_players[audio_id]["player"]
if action == "play":
player.play()
elif action == "pause":
player.pause()
elif action == "stop":
player.stop()
def _control_timer(self, timer_id, action):
if timer_id not in self.timers:
QMessageBox.warning(self.window, "警告", f"定时器ID不存在{timer_id}")
return
timer = self.timers[timer_id]['timer']
if action == "start":
timer.start()
elif action == "stop":
timer.stop()
def _show_widget_value(self, widget_id):
if widget_id not in self.variables:
QMessageBox.warning(self.window, "警告", f"组件ID不存在{widget_id}")
return
target = self.variables[widget_id]
msg = ""
if isinstance(target, list) and all(isinstance(x, QCheckBox) for x in target):
selected = [cb.text() for cb in target if cb.isChecked()]
msg = f"多选框选中项:{', '.join(selected) if selected else ''}"
elif isinstance(target, list) and all(isinstance(x, QRadioButton) for x in target):
selected = [rb.text() for rb in target if rb.isChecked()]
msg = f"单选框选中项:{', '.join(selected)}"
elif isinstance(target, QComboBox):
msg = f"下拉框选中:{target.currentText()}"
elif isinstance(target, QLineEdit):
msg = f"输入框内容:{target.text()}"
elif isinstance(target, QSlider):
msg = f"滑块值:{target.value()}"
elif isinstance(target, QTextEdit):
content = target.toPlainText()
msg = f"文本区域内容:{content[:100]}..." if len(content) > 100 else f"文本区域内容:{content}"
elif isinstance(target, QCalendarWidget):
msg = f"选中日期:{target.selectedDate().toString('yyyy-MM-dd')}"
elif isinstance(target, QProgressBar):
msg = f"进度条值:{target.value()}%"
elif isinstance(target, QLabel) and hasattr(target, 'pixmap') and target.pixmap():
msg = f"图片信息:已加载图片({target.pixmap().width()}x{target.pixmap().height()}"
QMessageBox.information(self.window, "组件值", msg)
# ---------------------- 运行入口 ----------------------
if __name__ == "__main__":
if len(sys.argv) > 1:
file_path = sys.argv[1]
try:
with open(file_path, 'r', encoding='utf-8') as f:
ewui_code = f.read()
interpreter = EasyUIInterpreter()
interpreter.parse_and_run(ewui_code)
except Exception as e:
print(f"[EUI解释器错误]{str(e)}", file=sys.stderr)
sys.exit(1)
else:
print("=" * 50)
print("Easy UI 解释器(基础版)")
print("用法python easy_ui.py <EWUI文件路径>")
print("支持组件:窗口、标签、按钮、输入框、下拉框、复选框等")
print("=" * 50)
sys.exit(0)