mirror of
https://github.com/goldenfishs/MRobot.git
synced 2025-09-14 12:54:33 +08:00
准备超级优化代码生成
This commit is contained in:
parent
a7c46ca9fc
commit
4973bb101a
@ -8,6 +8,7 @@ from qfluentwidgets import HeaderCardWidget
|
|||||||
from PyQt5.QtWidgets import QScrollArea, QWidget
|
from PyQt5.QtWidgets import QScrollArea, QWidget
|
||||||
from qfluentwidgets import theme, Theme
|
from qfluentwidgets import theme, Theme
|
||||||
from PyQt5.QtWidgets import QDoubleSpinBox
|
from PyQt5.QtWidgets import QDoubleSpinBox
|
||||||
|
from .tools.code_task_config import TaskConfigDialog
|
||||||
|
|
||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
@ -20,12 +21,6 @@ import textwrap
|
|||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
def preserve_all_user_regions(new_code, old_code):
|
def preserve_all_user_regions(new_code, old_code):
|
||||||
"""
|
|
||||||
自动保留所有 /* USER XXX BEGIN */ ... /* USER XXX END */ 区域内容。
|
|
||||||
new_code: 模板生成的新代码
|
|
||||||
old_code: 旧代码
|
|
||||||
返回:合并后的代码
|
|
||||||
"""
|
|
||||||
import re
|
import re
|
||||||
pattern = re.compile(
|
pattern = re.compile(
|
||||||
r"/\*\s*(USER [A-Z0-9_ ]+)\s*BEGIN\s*\*/(.*?)/\*\s*\1\s*END\s*\*/",
|
r"/\*\s*(USER [A-Z0-9_ ]+)\s*BEGIN\s*\*/(.*?)/\*\s*\1\s*END\s*\*/",
|
||||||
@ -41,9 +36,6 @@ def preserve_all_user_regions(new_code, old_code):
|
|||||||
return pattern.sub(repl, new_code)
|
return pattern.sub(repl, new_code)
|
||||||
|
|
||||||
def save_with_preserve(path, new_code):
|
def save_with_preserve(path, new_code):
|
||||||
"""
|
|
||||||
写入文件,自动保留所有用户自定义区域
|
|
||||||
"""
|
|
||||||
if os.path.exists(path):
|
if os.path.exists(path):
|
||||||
with open(path, "r", encoding="utf-8") as f:
|
with open(path, "r", encoding="utf-8") as f:
|
||||||
old_code = f.read()
|
old_code = f.read()
|
||||||
@ -77,87 +69,65 @@ class IocConfig:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
class DataInterface(QWidget):
|
class HomePageWidget(QWidget):
|
||||||
def __init__(self, parent=None):
|
def __init__(self, parent=None, on_choose_project=None, on_update_template=None):
|
||||||
super().__init__(parent=parent)
|
super().__init__(parent)
|
||||||
self.setObjectName("dataInterface")
|
layout = QVBoxLayout(self)
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
layout.addStretch()
|
||||||
|
|
||||||
# 属性初始化
|
|
||||||
self.project_path = ""
|
|
||||||
self.project_name = ""
|
|
||||||
self.ioc_file = ""
|
|
||||||
self.freertos_enabled = False # 新增属性
|
|
||||||
|
|
||||||
# 主布局
|
|
||||||
self.stacked_layout = QStackedLayout(self)
|
|
||||||
self.setLayout(self.stacked_layout)
|
|
||||||
|
|
||||||
# --- 页面1:工程路径选择 ---
|
|
||||||
self.select_widget = QWidget()
|
|
||||||
outer_layout = QVBoxLayout(self.select_widget)
|
|
||||||
outer_layout.setContentsMargins(0, 0, 0, 0)
|
|
||||||
outer_layout.addStretch()
|
|
||||||
|
|
||||||
# 直接用布局和控件,无卡片
|
|
||||||
content_layout = QVBoxLayout()
|
content_layout = QVBoxLayout()
|
||||||
content_layout.setSpacing(28)
|
content_layout.setSpacing(28)
|
||||||
content_layout.setContentsMargins(48, 48, 48, 48)
|
content_layout.setContentsMargins(48, 48, 48, 48)
|
||||||
|
|
||||||
# 主标题
|
|
||||||
title = TitleLabel("MRobot 代码生成")
|
title = TitleLabel("MRobot 代码生成")
|
||||||
title.setAlignment(Qt.AlignCenter)
|
title.setAlignment(Qt.AlignCenter)
|
||||||
content_layout.addWidget(title)
|
content_layout.addWidget(title)
|
||||||
|
|
||||||
# 副标题
|
|
||||||
subtitle = BodyLabel("请选择您的由CUBEMX生成的工程路径(.ico所在的目录),然后开启代码之旅!")
|
subtitle = BodyLabel("请选择您的由CUBEMX生成的工程路径(.ico所在的目录),然后开启代码之旅!")
|
||||||
subtitle.setAlignment(Qt.AlignCenter)
|
subtitle.setAlignment(Qt.AlignCenter)
|
||||||
content_layout.addWidget(subtitle)
|
content_layout.addWidget(subtitle)
|
||||||
|
|
||||||
# 简要说明
|
|
||||||
desc = BodyLabel("支持自动配置和生成任务,自主选择模块代码倒入,自动识别cubemx配置!")
|
desc = BodyLabel("支持自动配置和生成任务,自主选择模块代码倒入,自动识别cubemx配置!")
|
||||||
desc.setAlignment(Qt.AlignCenter)
|
desc.setAlignment(Qt.AlignCenter)
|
||||||
content_layout.addWidget(desc)
|
content_layout.addWidget(desc)
|
||||||
|
|
||||||
content_layout.addSpacing(18)
|
content_layout.addSpacing(18)
|
||||||
|
|
||||||
# 选择项目路径按钮
|
|
||||||
self.choose_btn = PushButton(FluentIcon.FOLDER, "选择项目路径")
|
self.choose_btn = PushButton(FluentIcon.FOLDER, "选择项目路径")
|
||||||
self.choose_btn.setFixedWidth(200)
|
self.choose_btn.setFixedWidth(200)
|
||||||
self.choose_btn.clicked.connect(self.choose_project_folder)
|
if on_choose_project:
|
||||||
|
self.choose_btn.clicked.connect(on_choose_project)
|
||||||
content_layout.addWidget(self.choose_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
content_layout.addWidget(self.choose_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||||
|
|
||||||
# 更新代码库按钮
|
|
||||||
self.update_template_btn = PushButton(FluentIcon.SYNC, "更新代码库")
|
self.update_template_btn = PushButton(FluentIcon.SYNC, "更新代码库")
|
||||||
self.update_template_btn.setFixedWidth(200)
|
self.update_template_btn.setFixedWidth(200)
|
||||||
self.update_template_btn.clicked.connect(self.update_user_template)
|
if on_update_template:
|
||||||
|
self.update_template_btn.clicked.connect(on_update_template)
|
||||||
content_layout.addWidget(self.update_template_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
content_layout.addWidget(self.update_template_btn, alignment=Qt.AlignmentFlag.AlignCenter)
|
||||||
|
|
||||||
content_layout.addSpacing(10)
|
content_layout.addSpacing(10)
|
||||||
content_layout.addStretch()
|
content_layout.addStretch()
|
||||||
|
|
||||||
|
layout.addLayout(content_layout)
|
||||||
outer_layout.addLayout(content_layout)
|
layout.addStretch()
|
||||||
outer_layout.addStretch()
|
|
||||||
|
|
||||||
self.stacked_layout.addWidget(self.select_widget)
|
class CodeGenWidget(QWidget):
|
||||||
|
def __init__(self, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
# --- 页面2:主配置页面 ---
|
|
||||||
self.config_widget = QWidget()
|
|
||||||
main_layout = QVBoxLayout(self.config_widget)
|
|
||||||
main_layout.setContentsMargins(32, 32, 32, 32)
|
|
||||||
main_layout.setSpacing(18)
|
|
||||||
|
|
||||||
# 顶部项目信息
|
|
||||||
info_layout = QHBoxLayout()
|
|
||||||
self.back_btn = PushButton(FluentIcon.SKIP_BACK, "返回")
|
|
||||||
self.back_btn.setFixedWidth(90)
|
|
||||||
self.back_btn.clicked.connect(self.back_to_select)
|
|
||||||
info_layout.addWidget(self.back_btn) # 返回按钮放最左
|
|
||||||
self.project_name_label = StrongBodyLabel()
|
self.project_name_label = StrongBodyLabel()
|
||||||
self.project_path_label = BodyLabel()
|
self.project_path_label = BodyLabel()
|
||||||
self.ioc_file_label = BodyLabel()
|
self.ioc_file_label = BodyLabel()
|
||||||
self.freertos_label = BodyLabel()
|
self.freertos_label = BodyLabel()
|
||||||
|
|
||||||
|
main_layout = QVBoxLayout(self)
|
||||||
|
main_layout.setContentsMargins(32, 32, 32, 32)
|
||||||
|
main_layout.setSpacing(18)
|
||||||
|
|
||||||
|
info_layout = QHBoxLayout()
|
||||||
|
self.back_btn = PushButton(FluentIcon.SKIP_BACK, "返回")
|
||||||
|
self.back_btn.setFixedWidth(90)
|
||||||
|
info_layout.addWidget(self.back_btn)
|
||||||
info_layout.addWidget(self.project_name_label)
|
info_layout.addWidget(self.project_name_label)
|
||||||
info_layout.addWidget(self.project_path_label)
|
info_layout.addWidget(self.project_path_label)
|
||||||
info_layout.addWidget(self.ioc_file_label)
|
info_layout.addWidget(self.ioc_file_label)
|
||||||
@ -166,11 +136,9 @@ class DataInterface(QWidget):
|
|||||||
main_layout.addLayout(info_layout)
|
main_layout.addLayout(info_layout)
|
||||||
main_layout.addWidget(HorizontalSeparator())
|
main_layout.addWidget(HorizontalSeparator())
|
||||||
|
|
||||||
# ======= 新增:左右分栏 =======
|
|
||||||
content_hbox = QHBoxLayout()
|
content_hbox = QHBoxLayout()
|
||||||
content_hbox.setSpacing(24)
|
content_hbox.setSpacing(24)
|
||||||
|
|
||||||
# 左侧:文件树
|
|
||||||
left_vbox = QVBoxLayout()
|
left_vbox = QVBoxLayout()
|
||||||
left_vbox.addWidget(SubtitleLabel("用户代码模块选择"))
|
left_vbox.addWidget(SubtitleLabel("用户代码模块选择"))
|
||||||
left_vbox.addWidget(HorizontalSeparator())
|
left_vbox.addWidget(HorizontalSeparator())
|
||||||
@ -184,35 +152,20 @@ class DataInterface(QWidget):
|
|||||||
left_vbox.addWidget(self.file_tree, stretch=1)
|
left_vbox.addWidget(self.file_tree, stretch=1)
|
||||||
content_hbox.addLayout(left_vbox, 2)
|
content_hbox.addLayout(left_vbox, 2)
|
||||||
|
|
||||||
# 右侧:操作按钮和说明
|
|
||||||
right_vbox = QVBoxLayout()
|
right_vbox = QVBoxLayout()
|
||||||
right_vbox.setSpacing(18)
|
right_vbox.setSpacing(18)
|
||||||
right_vbox.addWidget(SubtitleLabel("操作区"))
|
right_vbox.addWidget(SubtitleLabel("操作区"))
|
||||||
right_vbox.addWidget(HorizontalSeparator())
|
right_vbox.addWidget(HorizontalSeparator())
|
||||||
|
|
||||||
# 操作按钮分组
|
|
||||||
btn_group = QVBoxLayout()
|
btn_group = QVBoxLayout()
|
||||||
# 自动环境配置按钮
|
|
||||||
self.env_btn = PushButton("自动环境配置")
|
|
||||||
self.env_btn.setFixedWidth(200)
|
|
||||||
self.env_btn.setToolTip("自动检测并配置常用开发环境(功能开发中)")
|
|
||||||
self.env_btn.clicked.connect(self.auto_env_config)
|
|
||||||
btn_group.addWidget(self.env_btn)
|
|
||||||
# FreeRTOS相关按钮
|
|
||||||
self.freertos_task_btn = PushButton("自动生成FreeRTOS任务")
|
self.freertos_task_btn = PushButton("自动生成FreeRTOS任务")
|
||||||
self.freertos_task_btn.setFixedWidth(200)
|
self.freertos_task_btn.setFixedWidth(200)
|
||||||
self.freertos_task_btn.setToolTip("自动在 freertos.c 中插入任务创建代码")
|
|
||||||
self.freertos_task_btn.clicked.connect(self.on_freertos_task_btn_clicked)
|
|
||||||
btn_group.addWidget(self.freertos_task_btn)
|
btn_group.addWidget(self.freertos_task_btn)
|
||||||
self.task_code_btn = PushButton("配置并生成任务代码")
|
self.task_code_btn = PushButton("配置并生成任务代码")
|
||||||
self.task_code_btn.setFixedWidth(200)
|
self.task_code_btn.setFixedWidth(200)
|
||||||
self.task_code_btn.setToolTip("配置任务参数并一键生成任务代码文件")
|
|
||||||
self.task_code_btn.clicked.connect(self.on_task_code_btn_clicked)
|
|
||||||
btn_group.addWidget(self.task_code_btn)
|
btn_group.addWidget(self.task_code_btn)
|
||||||
self.generate_btn = PushButton(FluentIcon.CODE, "生成代码")
|
self.generate_btn = PushButton(FluentIcon.CODE, "生成代码")
|
||||||
self.generate_btn.setFixedWidth(200)
|
self.generate_btn.setFixedWidth(200)
|
||||||
self.generate_btn.setToolTip("将选中的用户模块代码复制到工程 User 目录")
|
|
||||||
self.generate_btn.clicked.connect(self.generate_code)
|
|
||||||
btn_group.addWidget(self.generate_btn)
|
btn_group.addWidget(self.generate_btn)
|
||||||
btn_group.addSpacing(10)
|
btn_group.addSpacing(10)
|
||||||
right_vbox.addLayout(btn_group)
|
right_vbox.addLayout(btn_group)
|
||||||
@ -220,16 +173,35 @@ class DataInterface(QWidget):
|
|||||||
|
|
||||||
content_hbox.addLayout(right_vbox, 1)
|
content_hbox.addLayout(right_vbox, 1)
|
||||||
main_layout.addLayout(content_hbox, stretch=1)
|
main_layout.addLayout(content_hbox, stretch=1)
|
||||||
self.stacked_layout.addWidget(self.config_widget)
|
|
||||||
self.file_tree.itemChanged.connect(self.on_tree_item_changed)
|
|
||||||
|
|
||||||
def auto_env_config(self):
|
class DataInterface(QWidget):
|
||||||
InfoBar.info(
|
def __init__(self, parent=None):
|
||||||
title="敬请期待",
|
super().__init__(parent=parent)
|
||||||
content="自动环境配置功能暂未实现,等待后续更新。",
|
self.setObjectName("dataInterface")
|
||||||
parent=self,
|
|
||||||
duration=2000
|
self.project_path = ""
|
||||||
|
self.project_name = ""
|
||||||
|
self.ioc_file = ""
|
||||||
|
self.freertos_enabled = False
|
||||||
|
|
||||||
|
self.stacked_layout = QStackedLayout(self)
|
||||||
|
self.setLayout(self.stacked_layout)
|
||||||
|
|
||||||
|
self.home_page = HomePageWidget(
|
||||||
|
on_choose_project=self.choose_project_folder,
|
||||||
|
on_update_template=self.update_user_template
|
||||||
)
|
)
|
||||||
|
self.stacked_layout.addWidget(self.home_page)
|
||||||
|
|
||||||
|
self.codegen_page = CodeGenWidget()
|
||||||
|
self.stacked_layout.addWidget(self.codegen_page)
|
||||||
|
|
||||||
|
# 事件绑定
|
||||||
|
self.codegen_page.back_btn.clicked.connect(self.back_to_select)
|
||||||
|
self.codegen_page.freertos_task_btn.clicked.connect(self.on_freertos_task_btn_clicked)
|
||||||
|
self.codegen_page.task_code_btn.clicked.connect(self.on_task_code_btn_clicked)
|
||||||
|
self.codegen_page.generate_btn.clicked.connect(self.generate_code)
|
||||||
|
self.codegen_page.file_tree.itemChanged.connect(self.on_tree_item_changed)
|
||||||
|
|
||||||
def choose_project_folder(self):
|
def choose_project_folder(self):
|
||||||
folder = QFileDialog.getExistingDirectory(self, "请选择代码项目文件夹")
|
folder = QFileDialog.getExistingDirectory(self, "请选择代码项目文件夹")
|
||||||
@ -250,22 +222,19 @@ class DataInterface(QWidget):
|
|||||||
self.show_config_page()
|
self.show_config_page()
|
||||||
|
|
||||||
def show_config_page(self):
|
def show_config_page(self):
|
||||||
# 更新项目信息
|
self.codegen_page.project_name_label.setText(f"项目名称: {self.project_name}")
|
||||||
self.project_name_label.setText(f"项目名称: {self.project_name}")
|
self.codegen_page.project_path_label.setText(f"项目路径: {self.project_path}")
|
||||||
self.project_path_label.setText(f"项目路径: {self.project_path}")
|
|
||||||
# self.ioc_file_label.setText(f"IOC 文件: {self.ioc_file}")
|
|
||||||
try:
|
try:
|
||||||
ioc = IocConfig(self.ioc_file)
|
ioc = IocConfig(self.ioc_file)
|
||||||
self.freertos_enabled = ioc.is_freertos_enabled() # 记录状态
|
self.freertos_enabled = ioc.is_freertos_enabled()
|
||||||
freertos_status = "已启用" if self.freertos_enabled else "未启用"
|
freertos_status = "已启用" if self.freertos_enabled else "未启用"
|
||||||
self.freertos_label.setText(f"FreeRTOS: {freertos_status}")
|
self.codegen_page.freertos_label.setText(f"FreeRTOS: {freertos_status}")
|
||||||
# self.freertos_task_btn.setEnabled(self.freertos_enabled)
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.freertos_label.setText(f"IOC解析失败: {e}")
|
self.codegen_page.freertos_label.setText(f"IOC解析失败: {e}")
|
||||||
self.freertos_task_btn.hide()
|
self.codegen_page.freertos_task_btn.hide()
|
||||||
self.freertos_enabled = False
|
self.freertos_enabled = False
|
||||||
self.show_user_code_files()
|
self.show_user_code_files()
|
||||||
self.stacked_layout.setCurrentWidget(self.config_widget)
|
self.stacked_layout.setCurrentWidget(self.codegen_page)
|
||||||
|
|
||||||
def on_freertos_task_btn_clicked(self):
|
def on_freertos_task_btn_clicked(self):
|
||||||
if not self.freertos_enabled:
|
if not self.freertos_enabled:
|
||||||
@ -290,7 +259,7 @@ class DataInterface(QWidget):
|
|||||||
self.open_task_config_dialog()
|
self.open_task_config_dialog()
|
||||||
|
|
||||||
def back_to_select(self):
|
def back_to_select(self):
|
||||||
self.stacked_layout.setCurrentWidget(self.select_widget)
|
self.stacked_layout.setCurrentWidget(self.home_page)
|
||||||
|
|
||||||
def update_user_template(self):
|
def update_user_template(self):
|
||||||
url = "http://gitea.qutrobot.top/robofish/MRobot/archive/User_code.zip"
|
url = "http://gitea.qutrobot.top/robofish/MRobot/archive/User_code.zip"
|
||||||
@ -327,19 +296,18 @@ class DataInterface(QWidget):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def show_user_code_files(self):
|
def show_user_code_files(self):
|
||||||
self.file_tree.clear()
|
file_tree = self.codegen_page.file_tree
|
||||||
|
file_tree.clear()
|
||||||
base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../assets/User_code")
|
base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../assets/User_code")
|
||||||
user_dir = os.path.join(self.project_path, "User")
|
user_dir = os.path.join(self.project_path, "User")
|
||||||
sub_dirs = ["bsp", "component", "device", "module"]
|
sub_dirs = ["bsp", "component", "device", "module"]
|
||||||
|
|
||||||
# 读取所有 describe.csv 和 dependencies.csv
|
|
||||||
describe_map = {}
|
describe_map = {}
|
||||||
dependencies_map = {}
|
dependencies_map = {}
|
||||||
for sub in sub_dirs:
|
for sub in sub_dirs:
|
||||||
dir_path = os.path.join(base_dir, sub)
|
dir_path = os.path.join(base_dir, sub)
|
||||||
if not os.path.isdir(dir_path):
|
if not os.path.isdir(dir_path):
|
||||||
continue
|
continue
|
||||||
# describe
|
|
||||||
desc_path = os.path.join(dir_path, "describe.csv")
|
desc_path = os.path.join(dir_path, "describe.csv")
|
||||||
if os.path.exists(desc_path):
|
if os.path.exists(desc_path):
|
||||||
with open(desc_path, encoding="utf-8") as f:
|
with open(desc_path, encoding="utf-8") as f:
|
||||||
@ -347,7 +315,6 @@ class DataInterface(QWidget):
|
|||||||
if "," in line:
|
if "," in line:
|
||||||
k, v = line.strip().split(",", 1)
|
k, v = line.strip().split(",", 1)
|
||||||
describe_map[f"{sub}/{k.strip()}"] = v.strip()
|
describe_map[f"{sub}/{k.strip()}"] = v.strip()
|
||||||
# dependencies
|
|
||||||
dep_path = os.path.join(dir_path, "dependencies.csv")
|
dep_path = os.path.join(dir_path, "dependencies.csv")
|
||||||
if os.path.exists(dep_path):
|
if os.path.exists(dep_path):
|
||||||
with open(dep_path, encoding="utf-8") as f:
|
with open(dep_path, encoding="utf-8") as f:
|
||||||
@ -355,23 +322,23 @@ class DataInterface(QWidget):
|
|||||||
if "," in line:
|
if "," in line:
|
||||||
a, b = line.strip().split(",", 1)
|
a, b = line.strip().split(",", 1)
|
||||||
dependencies_map.setdefault(f"{sub}/{a.strip()}", []).append(b.strip())
|
dependencies_map.setdefault(f"{sub}/{a.strip()}", []).append(b.strip())
|
||||||
|
|
||||||
self._describe_map = describe_map
|
self._describe_map = describe_map
|
||||||
self._dependencies_map = dependencies_map
|
self._dependencies_map = dependencies_map
|
||||||
|
|
||||||
self.file_tree.setHeaderLabels(["模块名", "描述"])
|
file_tree.setHeaderLabels(["模块名", "描述"])
|
||||||
self.file_tree.setSelectionMode(self.file_tree.ExtendedSelection)
|
file_tree.setSelectionMode(file_tree.ExtendedSelection)
|
||||||
self.file_tree.header().setSectionResizeMode(0, QHeaderView.Interactive)
|
file_tree.header().setSectionResizeMode(0, QHeaderView.Interactive)
|
||||||
self.file_tree.header().setSectionResizeMode(1, QHeaderView.Interactive)
|
file_tree.header().setSectionResizeMode(1, QHeaderView.Interactive)
|
||||||
self.file_tree.setBorderRadius(8)
|
file_tree.setBorderRadius(8)
|
||||||
self.file_tree.setBorderVisible(True)
|
file_tree.setBorderVisible(True)
|
||||||
|
|
||||||
for sub in sub_dirs:
|
for sub in sub_dirs:
|
||||||
dir_path = os.path.join(base_dir, sub)
|
dir_path = os.path.join(base_dir, sub)
|
||||||
if not os.path.isdir(dir_path):
|
if not os.path.isdir(dir_path):
|
||||||
continue
|
continue
|
||||||
group_item = TreeItem([sub, ""])
|
group_item = TreeItem([sub, ""])
|
||||||
self.file_tree.addTopLevelItem(group_item)
|
file_tree.addTopLevelItem(group_item)
|
||||||
has_file = False
|
has_file = False
|
||||||
for root, _, files in os.walk(dir_path):
|
for root, _, files in os.walk(dir_path):
|
||||||
rel_root = os.path.relpath(root, base_dir)
|
rel_root = os.path.relpath(root, base_dir)
|
||||||
@ -384,7 +351,7 @@ class DataInterface(QWidget):
|
|||||||
file_item = TreeItem([mod_name, desc])
|
file_item = TreeItem([mod_name, desc])
|
||||||
file_item.setFlags(file_item.flags() | Qt.ItemIsUserCheckable)
|
file_item.setFlags(file_item.flags() | Qt.ItemIsUserCheckable)
|
||||||
file_item.setData(0, Qt.UserRole, rel_c)
|
file_item.setData(0, Qt.UserRole, rel_c)
|
||||||
file_item.setData(0, Qt.UserRole + 1, key) # 存模块key
|
file_item.setData(0, Qt.UserRole + 1, key)
|
||||||
file_item.setToolTip(1, desc)
|
file_item.setToolTip(1, desc)
|
||||||
file_item.setTextAlignment(1, Qt.AlignLeft | Qt.AlignVCenter)
|
file_item.setTextAlignment(1, Qt.AlignLeft | Qt.AlignVCenter)
|
||||||
group_item.addChild(file_item)
|
group_item.addChild(file_item)
|
||||||
@ -400,20 +367,19 @@ class DataInterface(QWidget):
|
|||||||
if not has_file:
|
if not has_file:
|
||||||
empty_item = TreeItem(["(无 .c 文件)", ""])
|
empty_item = TreeItem(["(无 .c 文件)", ""])
|
||||||
group_item.addChild(empty_item)
|
group_item.addChild(empty_item)
|
||||||
self.file_tree.expandAll()
|
file_tree.expandAll()
|
||||||
|
|
||||||
# 勾选依赖自动勾选
|
|
||||||
def on_tree_item_changed(self, item, column):
|
def on_tree_item_changed(self, item, column):
|
||||||
if column != 0:
|
if column != 0:
|
||||||
return
|
return
|
||||||
if item.childCount() > 0:
|
if item.childCount() > 0:
|
||||||
return # 只处理叶子
|
return
|
||||||
if item.checkState(0) == Qt.Checked:
|
if item.checkState(0) == Qt.Checked:
|
||||||
key = item.data(0, Qt.UserRole + 1)
|
key = item.data(0, Qt.UserRole + 1)
|
||||||
deps = self._dependencies_map.get(key, [])
|
deps = self._dependencies_map.get(key, [])
|
||||||
if deps:
|
if deps:
|
||||||
checked = []
|
checked = []
|
||||||
root = self.file_tree.invisibleRootItem()
|
root = self.codegen_page.file_tree.invisibleRootItem()
|
||||||
for i in range(root.childCount()):
|
for i in range(root.childCount()):
|
||||||
group = root.child(i)
|
group = root.child(i)
|
||||||
for j in range(group.childCount()):
|
for j in range(group.childCount()):
|
||||||
@ -430,7 +396,6 @@ class DataInterface(QWidget):
|
|||||||
parent=self,
|
parent=self,
|
||||||
duration=2000
|
duration=2000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_checked_files(self):
|
def get_checked_files(self):
|
||||||
files = []
|
files = []
|
||||||
@ -440,7 +405,7 @@ class DataInterface(QWidget):
|
|||||||
if child.childCount() == 0 and child.checkState(0) == Qt.Checked:
|
if child.childCount() == 0 and child.checkState(0) == Qt.Checked:
|
||||||
files.append(child.data(0, Qt.UserRole))
|
files.append(child.data(0, Qt.UserRole))
|
||||||
_traverse(child)
|
_traverse(child)
|
||||||
root = self.file_tree.invisibleRootItem()
|
root = self.codegen_page.file_tree.invisibleRootItem()
|
||||||
for i in range(root.childCount()):
|
for i in range(root.childCount()):
|
||||||
_traverse(root.child(i))
|
_traverse(root.child(i))
|
||||||
return files
|
return files
|
||||||
@ -458,7 +423,6 @@ class DataInterface(QWidget):
|
|||||||
src_h = os.path.join(base_dir, rel_h)
|
src_h = os.path.join(base_dir, rel_h)
|
||||||
dst_c = os.path.join(user_dir, rel_c)
|
dst_c = os.path.join(user_dir, rel_c)
|
||||||
dst_h = os.path.join(user_dir, rel_h)
|
dst_h = os.path.join(user_dir, rel_h)
|
||||||
# 如果目标文件已存在则跳过
|
|
||||||
if os.path.exists(dst_c):
|
if os.path.exists(dst_c):
|
||||||
skipped.append(dst_c)
|
skipped.append(dst_c)
|
||||||
else:
|
else:
|
||||||
@ -481,7 +445,6 @@ class DataInterface(QWidget):
|
|||||||
parent=self,
|
parent=self,
|
||||||
duration=2000
|
duration=2000
|
||||||
)
|
)
|
||||||
# 生成后刷新文件树,更新标记
|
|
||||||
self.show_user_code_files()
|
self.show_user_code_files()
|
||||||
|
|
||||||
def generate_freertos_task(self):
|
def generate_freertos_task(self):
|
||||||
@ -497,14 +460,12 @@ class DataInterface(QWidget):
|
|||||||
return
|
return
|
||||||
with open(freertos_path, "r", encoding="utf-8") as f:
|
with open(freertos_path, "r", encoding="utf-8") as f:
|
||||||
code = f.read()
|
code = f.read()
|
||||||
|
|
||||||
changed = False
|
changed = False
|
||||||
error_msgs = []
|
error_msgs = []
|
||||||
|
|
||||||
# 1. 添加 #include "task/user_task.h"
|
|
||||||
include_line = '#include "task/user_task.h"'
|
include_line = '#include "task/user_task.h"'
|
||||||
if include_line not in code:
|
if include_line not in code:
|
||||||
# 只插入到 USER CODE BEGIN Includes 区域
|
|
||||||
include_pattern = r'(\/\* *USER CODE BEGIN Includes *\*\/\s*)'
|
include_pattern = r'(\/\* *USER CODE BEGIN Includes *\*\/\s*)'
|
||||||
if re.search(include_pattern, code):
|
if re.search(include_pattern, code):
|
||||||
code = re.sub(
|
code = re.sub(
|
||||||
@ -515,22 +476,19 @@ class DataInterface(QWidget):
|
|||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
error_msgs.append("未找到 /* USER CODE BEGIN Includes */ 区域,无法插入 include。")
|
error_msgs.append("未找到 /* USER CODE BEGIN Includes */ 区域,无法插入 include。")
|
||||||
|
|
||||||
# 2. 在 /* USER CODE BEGIN RTOS_THREADS */ 区域添加 osThreadNew(Task_Init, NULL, &attr_init);
|
|
||||||
rtos_threads_pattern = r'(\/\* *USER CODE BEGIN RTOS_THREADS *\*\/\s*)(.*?)(\/\* *USER CODE END RTOS_THREADS *\*\/)'
|
rtos_threads_pattern = r'(\/\* *USER CODE BEGIN RTOS_THREADS *\*\/\s*)(.*?)(\/\* *USER CODE END RTOS_THREADS *\*\/)'
|
||||||
match = re.search(rtos_threads_pattern, code, re.DOTALL)
|
match = re.search(rtos_threads_pattern, code, re.DOTALL)
|
||||||
task_line = ' osThreadNew(Task_Init, NULL, &attr_init); // 创建初始化任务\n'
|
task_line = ' osThreadNew(Task_Init, NULL, &attr_init); // 创建初始化任务\n'
|
||||||
if match:
|
if match:
|
||||||
threads_code = match.group(2)
|
threads_code = match.group(2)
|
||||||
if 'Task_Init' not in threads_code:
|
if 'Task_Init' not in threads_code:
|
||||||
# 保留原有内容,追加新行
|
|
||||||
new_threads_code = match.group(1) + threads_code + task_line + match.group(3)
|
new_threads_code = match.group(1) + threads_code + task_line + match.group(3)
|
||||||
code = code[:match.start()] + new_threads_code + code[match.end():]
|
code = code[:match.start()] + new_threads_code + code[match.end():]
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
error_msgs.append("未找到 /* USER CODE BEGIN RTOS_THREADS */ 区域,无法插入任务创建代码。")
|
error_msgs.append("未找到 /* USER CODE BEGIN RTOS_THREADS */ 区域,无法插入任务创建代码。")
|
||||||
|
|
||||||
# 3. 清空 StartDefaultTask 的 USER CODE 区域,只保留 osThreadTerminate
|
|
||||||
sdt_pattern = r'(\/\* *USER CODE BEGIN StartDefaultTask *\*\/\s*)(.*?)(\/\* *USER CODE END StartDefaultTask *\*\/)'
|
sdt_pattern = r'(\/\* *USER CODE BEGIN StartDefaultTask *\*\/\s*)(.*?)(\/\* *USER CODE END StartDefaultTask *\*\/)'
|
||||||
match = re.search(sdt_pattern, code, re.DOTALL)
|
match = re.search(sdt_pattern, code, re.DOTALL)
|
||||||
if match:
|
if match:
|
||||||
@ -540,7 +498,7 @@ class DataInterface(QWidget):
|
|||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
error_msgs.append("未找到 /* USER CODE BEGIN StartDefaultTask */ 区域,无法插入终止代码。")
|
error_msgs.append("未找到 /* USER CODE BEGIN StartDefaultTask */ 区域,无法插入终止代码。")
|
||||||
|
|
||||||
if changed:
|
if changed:
|
||||||
with open(freertos_path, "w", encoding="utf-8") as f:
|
with open(freertos_path, "w", encoding="utf-8") as f:
|
||||||
f.write(code)
|
f.write(code)
|
||||||
@ -603,26 +561,24 @@ class DataInterface(QWidget):
|
|||||||
duration=3000
|
duration=3000
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def generate_task_code(self, task_list):
|
def generate_task_code(self, task_list):
|
||||||
base_dir = os.path.dirname(os.path.abspath(__file__))
|
base_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
template_dir = os.path.join(base_dir, "../assets/User_code/task")
|
template_dir = os.path.join(base_dir, "../assets/User_code/task")
|
||||||
output_dir = os.path.join(self.project_path, "User", "task")
|
output_dir = os.path.join(self.project_path, "User", "task")
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
|
|
||||||
user_task_h_tpl = os.path.join(template_dir, "user_task.h.template")
|
user_task_h_tpl = os.path.join(template_dir, "user_task.h.template")
|
||||||
user_task_c_tpl = os.path.join(template_dir, "user_task.c.template")
|
user_task_c_tpl = os.path.join(template_dir, "user_task.c.template")
|
||||||
init_c_tpl = os.path.join(template_dir, "init.c.template")
|
init_c_tpl = os.path.join(template_dir, "init.c.template")
|
||||||
task_c_tpl = os.path.join(template_dir, "task.c.template")
|
task_c_tpl = os.path.join(template_dir, "task.c.template")
|
||||||
|
|
||||||
freq_tasks = [t for t in task_list if t.get("freq_control", True)]
|
freq_tasks = [t for t in task_list if t.get("freq_control", True)]
|
||||||
|
|
||||||
def render_template(path, context):
|
def render_template(path, context):
|
||||||
with open(path, encoding="utf-8") as f:
|
with open(path, encoding="utf-8") as f:
|
||||||
tpl = Template(f.read())
|
tpl = Template(f.read())
|
||||||
return tpl.render(**context)
|
return tpl.render(**context)
|
||||||
|
|
||||||
# ----------- 生成 user_task.h -----------
|
|
||||||
context_h = {
|
context_h = {
|
||||||
"thread_definitions": "\n".join([f" osThreadId_t {t['name']};" for t in task_list]),
|
"thread_definitions": "\n".join([f" osThreadId_t {t['name']};" for t in task_list]),
|
||||||
"freq_definitions": "\n".join([f" float {t['name']};" for t in freq_tasks]),
|
"freq_definitions": "\n".join([f" float {t['name']};" for t in freq_tasks]),
|
||||||
@ -636,8 +592,7 @@ class DataInterface(QWidget):
|
|||||||
user_task_h_path = os.path.join(output_dir, "user_task.h")
|
user_task_h_path = os.path.join(output_dir, "user_task.h")
|
||||||
new_user_task_h = render_template(user_task_h_tpl, context_h)
|
new_user_task_h = render_template(user_task_h_tpl, context_h)
|
||||||
save_with_preserve(user_task_h_path, new_user_task_h)
|
save_with_preserve(user_task_h_path, new_user_task_h)
|
||||||
|
|
||||||
# ----------- 生成 user_task.c -----------
|
|
||||||
context_c = {
|
context_c = {
|
||||||
"task_attr_definitions": "\n".join([
|
"task_attr_definitions": "\n".join([
|
||||||
f"const osThreadAttr_t attr_{t['name']} = {{\n"
|
f"const osThreadAttr_t attr_{t['name']} = {{\n"
|
||||||
@ -651,8 +606,7 @@ class DataInterface(QWidget):
|
|||||||
user_task_c_path = os.path.join(output_dir, "user_task.c")
|
user_task_c_path = os.path.join(output_dir, "user_task.c")
|
||||||
user_task_c = render_template(user_task_c_tpl, context_c)
|
user_task_c = render_template(user_task_c_tpl, context_c)
|
||||||
save_with_preserve(user_task_c_path, user_task_c)
|
save_with_preserve(user_task_c_path, user_task_c)
|
||||||
|
|
||||||
# ----------- 生成 init.c -----------
|
|
||||||
thread_creation_code = "\n".join([
|
thread_creation_code = "\n".join([
|
||||||
f" task_runtime.thread.{t['name']} = osThreadNew({t['function']}, NULL, &attr_{t['name']});"
|
f" task_runtime.thread.{t['name']} = osThreadNew({t['function']}, NULL, &attr_{t['name']});"
|
||||||
for t in task_list
|
for t in task_list
|
||||||
@ -663,8 +617,7 @@ class DataInterface(QWidget):
|
|||||||
init_c_path = os.path.join(output_dir, "init.c")
|
init_c_path = os.path.join(output_dir, "init.c")
|
||||||
init_c = render_template(init_c_tpl, context_init)
|
init_c = render_template(init_c_tpl, context_init)
|
||||||
save_with_preserve(init_c_path, init_c)
|
save_with_preserve(init_c_path, init_c)
|
||||||
|
|
||||||
# ----------- 生成 task.c -----------
|
|
||||||
for t in task_list:
|
for t in task_list:
|
||||||
desc = t.get("description", "")
|
desc = t.get("description", "")
|
||||||
desc_wrapped = "\n ".join(textwrap.wrap(desc, 20))
|
desc_wrapped = "\n ".join(textwrap.wrap(desc, 20))
|
||||||
@ -681,298 +634,7 @@ class DataInterface(QWidget):
|
|||||||
code = tpl.render(**context_task)
|
code = tpl.render(**context_task)
|
||||||
task_c_path = os.path.join(output_dir, f"{t['name']}.c")
|
task_c_path = os.path.join(output_dir, f"{t['name']}.c")
|
||||||
save_with_preserve(task_c_path, code)
|
save_with_preserve(task_c_path, code)
|
||||||
|
|
||||||
# ----------- 保存任务配置到 config.yaml -----------
|
|
||||||
config_yaml_path = os.path.join(output_dir, "config.yaml")
|
config_yaml_path = os.path.join(output_dir, "config.yaml")
|
||||||
with open(config_yaml_path, "w", encoding="utf-8") as f:
|
with open(config_yaml_path, "w", encoding="utf-8") as f:
|
||||||
yaml.safe_dump(task_list, f, allow_unicode=True)
|
yaml.safe_dump(task_list, f, allow_unicode=True)
|
||||||
|
|
||||||
|
|
||||||
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
|
|
306
app/tools/code_task_config.py
Normal file
306
app/tools/code_task_config.py
Normal file
@ -0,0 +1,306 @@
|
|||||||
|
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
|
Loading…
Reference in New Issue
Block a user