686 lines
28 KiB
Python
686 lines
28 KiB
Python
|
|
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)
|