MRobot/app/tools/code_task_config.py

306 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QWidget, QScrollArea
from qfluentwidgets import (
BodyLabel, TitleLabel, HorizontalSeparator, PushButton, PrimaryPushButton,
LineEdit, SpinBox, DoubleSpinBox, CheckBox, TextEdit
)
from qfluentwidgets import theme, Theme
import yaml
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QStackedLayout, QFileDialog, QHeaderView
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QTreeWidgetItem as TreeItem
from qfluentwidgets import TitleLabel, BodyLabel, SubtitleLabel, StrongBodyLabel, HorizontalSeparator, PushButton, TreeWidget, InfoBar,FluentIcon, Dialog,SubtitleLabel,BodyLabel
from PyQt5.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QSpinBox, QPushButton, QTableWidget, QTableWidgetItem, QHeaderView, QCheckBox
from qfluentwidgets import CardWidget, LineEdit, SpinBox, CheckBox, TextEdit, PrimaryPushButton, PushButton, InfoBar, DoubleSpinBox
from qfluentwidgets import HeaderCardWidget
from PyQt5.QtWidgets import QScrollArea, QWidget
from qfluentwidgets import theme, Theme
from PyQt5.QtWidgets import QDoubleSpinBox
import os
class TaskConfigDialog(QDialog):
def __init__(self, parent=None, config_path=None):
super().__init__(parent)
self.setWindowTitle("任务配置")
self.resize(900, 520)
# 设置背景色跟随主题
if theme() == Theme.DARK:
self.setStyleSheet("background-color: #232323;")
else:
self.setStyleSheet("background-color: #f7f9fc;")
# 主布局
main_layout = QVBoxLayout(self)
main_layout.setContentsMargins(16, 16, 16, 16)
main_layout.setSpacing(12)
# 顶部横向分栏
self.top_layout = QHBoxLayout()
self.top_layout.setSpacing(16)
# ----------- 左侧任务按钮区 -----------
self.left_widget = QWidget()
self.left_layout = QVBoxLayout(self.left_widget)
self.left_layout.setContentsMargins(0, 0, 0, 0)
self.left_layout.setSpacing(8)
self.task_list_label = BodyLabel("任务列表")
# self.left_layout.addWidget(self.task_list_label)
# 添加任务列表居中
self.task_list_label.setAlignment(Qt.AlignCenter)
self.left_layout.addWidget(self.task_list_label, alignment=Qt.AlignCenter)
# 添加一个水平分割线
self.left_layout.addWidget(HorizontalSeparator())
# 任务按钮区
self.task_btn_area = QScrollArea()
self.task_btn_area.setWidgetResizable(True)
self.task_btn_area.setFrameShape(QScrollArea.NoFrame)
self.task_btn_container = QWidget()
self.task_btn_layout = QVBoxLayout(self.task_btn_container)
self.task_btn_layout.setContentsMargins(0, 0, 0, 0)
self.task_btn_layout.setSpacing(4)
self.task_btn_layout.addStretch()
self.task_btn_area.setWidget(self.task_btn_container)
self.left_layout.addWidget(self.task_btn_area, stretch=1)
self.left_widget.setFixedWidth(180)
self.top_layout.addWidget(self.left_widget, stretch=0)
# ----------- 左侧任务按钮区 END -----------
main_layout.addLayout(self.top_layout, stretch=1)
# 下方按钮区
btn_layout = QHBoxLayout()
# 左下角:添加/删除任务
self.add_btn = PrimaryPushButton("创建新任务")
self.add_btn.setAutoDefault(False) # 禁止回车触发
self.add_btn.setDefault(False)
self.del_btn = PushButton("删除当前任务")
self.del_btn.setAutoDefault(False) # 禁止回车触发
self.del_btn.setDefault(False)
self.add_btn.clicked.connect(self.add_task)
self.del_btn.clicked.connect(self.delete_current_task)
btn_layout.addWidget(self.add_btn)
btn_layout.addWidget(self.del_btn)
btn_layout.addStretch() # 添加/删除靠左stretch在中间
# 右下角:生成/取消
self.ok_btn = PrimaryPushButton("生成任务")
self.ok_btn.setAutoDefault(False) # 允许回车触发
self.ok_btn.setDefault(False) # 设置为默认按钮
self.cancel_btn = PushButton("取消")
self.cancel_btn.setAutoDefault(False) # 禁止回车触发
self.cancel_btn.setDefault(False)
btn_layout.addWidget(self.ok_btn)
btn_layout.addWidget(self.cancel_btn)
main_layout.addLayout(btn_layout)
self.ok_btn.clicked.connect(self.accept)
self.cancel_btn.clicked.connect(self.reject)
self.tasks = []
self.current_index = -1
if config_path and os.path.exists(config_path):
try:
with open(config_path, "r", encoding="utf-8") as f:
tasks = yaml.safe_load(f)
if tasks:
for t in tasks:
self.tasks.append(self._make_task_obj(t))
except Exception:
pass
# 允许没有任何任务
self.current_index = 0 if self.tasks else -1
self.refresh_task_btns()
if self.tasks:
self.show_task_form(self.tasks[self.current_index])
else:
self.show_task_form(None)
def refresh_task_btns(self):
# 清空旧按钮
while self.task_btn_layout.count():
item = self.task_btn_layout.takeAt(0)
w = item.widget()
if w:
w.deleteLater()
# 重新添加按钮
for idx, t in enumerate(self.tasks):
btn = PushButton(t["name"])
btn.setCheckable(True)
btn.setChecked(idx == self.current_index)
btn.clicked.connect(lambda checked, i=idx: self.select_task(i))
self.task_btn_layout.addWidget(btn)
self.task_btn_layout.addStretch()
def add_task(self):
self.save_form()
new_idx = len(self.tasks)
self.tasks.append(self._make_task_obj({"name": f"Task{new_idx+1}"}))
self.current_index = new_idx
self.refresh_task_btns()
self.show_task_form(self.tasks[self.current_index])
def delete_current_task(self):
if self.current_index < 0 or not self.tasks:
return
del self.tasks[self.current_index]
if not self.tasks:
self.current_index = -1
self.refresh_task_btns()
self.show_task_form(None)
return
if self.current_index >= len(self.tasks):
self.current_index = len(self.tasks) - 1
self.refresh_task_btns()
self.show_task_form(self.tasks[self.current_index])
def select_task(self, idx):
self.save_form()
self.current_index = idx
self.refresh_task_btns()
self.show_task_form(self.tasks[idx])
def show_task_form(self, task):
# 先移除旧的 form_widget
if hasattr(self, "form_widget") and self.form_widget is not None:
self.top_layout.removeWidget(self.form_widget)
self.form_widget.deleteLater()
self.form_widget = None
# 新建 form_widget 和 form_layout
self.form_widget = QWidget()
self.form_layout = QVBoxLayout(self.form_widget)
self.form_layout.setContentsMargins(0, 0, 0, 0)
self.form_layout.setSpacing(12)
# 添加到右侧
self.top_layout.addWidget(self.form_widget, stretch=1)
if not task:
label = TitleLabel("暂无任务,请点击下方“添加任务”。")
label.setAlignment(Qt.AlignCenter)
self.form_layout.addStretch()
self.form_layout.addWidget(label)
self.form_layout.addStretch()
return
# 任务名称
row1 = QHBoxLayout()
label_name = BodyLabel("任务名称")
self.name_edit = LineEdit()
self.name_edit.setText(task["name"])
self.name_edit.setPlaceholderText("任务名称")
# 新增:名称编辑完成后刷新按钮
self.name_edit.editingFinished.connect(self.on_name_edit_finished)
row1.addWidget(label_name)
row1.addWidget(self.name_edit)
self.form_layout.addLayout(row1)
# 频率
row2 = QHBoxLayout()
label_freq = BodyLabel("任务运行频率")
self.freq_spin = DoubleSpinBox()
self.freq_spin.setRange(0, 10000)
self.freq_spin.setDecimals(3)
self.freq_spin.setSingleStep(1)
self.freq_spin.setSuffix(" Hz")
self.freq_spin.setValue(float(task.get("frequency", 500)))
row2.addWidget(label_freq)
row2.addWidget(self.freq_spin)
self.form_layout.addLayout(row2)
# 延迟
row3 = QHBoxLayout()
label_delay = BodyLabel("初始化延时")
self.delay_spin = SpinBox()
self.delay_spin.setRange(0, 10000)
self.delay_spin.setSuffix(" ms")
self.delay_spin.setValue(task.get("delay", 0))
row3.addWidget(label_delay)
row3.addWidget(self.delay_spin)
self.form_layout.addLayout(row3)
# 堆栈
row4 = QHBoxLayout()
label_stack = BodyLabel("堆栈大小")
self.stack_spin = SpinBox()
self.stack_spin.setRange(128, 8192)
self.stack_spin.setSingleStep(128)
self.stack_spin.setSuffix(" Byte") # 添加单位
self.stack_spin.setValue(task.get("stack", 256))
row4.addWidget(label_stack)
row4.addWidget(self.stack_spin)
self.form_layout.addLayout(row4)
# 频率控制
row5 = QHBoxLayout()
self.freq_ctrl = CheckBox("启用默认频率控制")
self.freq_ctrl.setChecked(task.get("freq_control", True))
row5.addWidget(self.freq_ctrl)
self.form_layout.addLayout(row5)
# 描述
label_desc = BodyLabel("任务描述")
self.desc_edit = TextEdit()
self.desc_edit.setText(task.get("description", ""))
self.desc_edit.setPlaceholderText("任务描述")
self.form_layout.addWidget(label_desc)
self.form_layout.addWidget(self.desc_edit)
self.form_layout.addStretch()
def on_name_edit_finished(self):
# 保存当前表单内容
self.save_form()
# 刷新左侧按钮名称
self.refresh_task_btns()
def _make_task_obj(self, task=None):
return {
"name": task["name"] if task else f"Task1",
"frequency": task.get("frequency", 500) if task else 500,
"delay": task.get("delay", 0) if task else 0,
"stack": task.get("stack", 256) if task else 256,
"description": task.get("description", "") if task else "",
"freq_control": task.get("freq_control", True) if task else True,
}
def save_form(self):
if self.current_index < 0 or self.current_index >= len(self.tasks):
return
t = self.tasks[self.current_index]
t["name"] = self.name_edit.text().strip()
t["frequency"] = float(self.freq_spin.value()) # 支持小数
t["delay"] = self.delay_spin.value()
t["stack"] = self.stack_spin.value()
t["description"] = self.desc_edit.toPlainText().strip()
t["freq_control"] = self.freq_ctrl.isChecked()
def get_tasks(self):
self.save_form()
tasks = []
for idx, t in enumerate(self.tasks):
name = t["name"].strip()
freq = t["frequency"]
delay = t["delay"]
stack = t["stack"]
desc = t["description"].strip()
freq_ctrl = t["freq_control"]
if stack < 128 or (stack & (stack - 1)) != 0 or stack % 128 != 0:
raise ValueError(f"{idx+1}个任务“{name}”的堆栈大小必须为128、256、512、1024等128*2^n")
task = {
"name": name,
"function": f"Task_{name}",
"delay": delay,
"stack": stack,
"description": desc,
"freq_control": freq_ctrl
}
if freq_ctrl:
task["frequency"] = freq
tasks.append(task)
return tasks