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 ") print("支持组件:窗口、标签、按钮、输入框、下拉框、复选框等") print("=" * 50) sys.exit(0)