улучшены коллбеки для считывания xml

улучшен парс структур и юнионов переменных
улучшен поиск
This commit is contained in:
Razvalyaev 2025-07-09 05:44:03 +03:00
parent 0ba81a5147
commit 4517087194
11 changed files with 137 additions and 56 deletions

View File

@ -30,10 +30,9 @@ class VariableSelectorDialog(QDialog):
self.tree = QTreeWidget()
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
self.tree.setRootIsDecorated(False) # без иконки "вложенность"
self.tree.setRootIsDecorated(True)
self.tree.setUniformRowHeights(True)
# --- Автоматическая ширина колонок
self.tree.setStyleSheet("""
QTreeWidget::item:selected {
background-color: #87CEFA;
@ -47,11 +46,15 @@ class VariableSelectorDialog(QDialog):
self.btn_add = QPushButton("Добавить выбранные")
self.btn_add.clicked.connect(self.on_add_clicked)
self.btn_delete = QPushButton("Удалить выбранные")
self.btn_delete.clicked.connect(self.on_delete_clicked)
layout = QVBoxLayout()
layout.addWidget(QLabel("Поиск:"))
layout.addWidget(self.search_input)
layout.addWidget(self.tree)
layout.addWidget(self.btn_add)
layout.addWidget(self.btn_delete) # Кнопка удаления
self.setLayout(layout)
self.populate_tree()
@ -68,14 +71,18 @@ class VariableSelectorDialog(QDialog):
item = QTreeWidgetItem([name, type_str])
item.setData(0, Qt.UserRole, name)
# Делаем bitfield-поля неактивными
if "(bitfield:" in type_str:
item.setDisabled(True)
self.set_tool(item, "Битовые поля недоступны для выбора")
for i, attr in enumerate(['file', 'extern', 'static']):
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
if show_var:
item.setForeground(0, Qt.gray)
item.setForeground(1, Qt.gray)
item.setToolTip(0, "Уже добавлена")
item.setToolTip(1, "Уже добавлена")
self.set_tool(item, "Уже добавлена")
if parent is None:
self.tree.addTopLevelItem(item)
@ -95,32 +102,61 @@ class VariableSelectorDialog(QDialog):
self.add_tree_item_recursively(None, var)
header = self.tree.header()
header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
header.setSectionResizeMode(QHeaderView.Interactive) # вручную можно менять
self.tree.setColumnWidth(0, 400)
self.tree.resizeColumnToContents(1)
""" header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeToContents) """
def filter_tree(self):
text = self.search_input.text().strip().lower()
path_parts = text.split('.') if text else []
def match_recursive(item):
matched = False
def hide_all(item):
item.setHidden(True)
for i in range(item.childCount()):
child = item.child(i)
if match_recursive(child):
hide_all(item.child(i))
def path_matches_search(name, search_parts):
name_parts = name.lower().split('.')
if len(name_parts) < len(search_parts):
return False
for sp, np in zip(search_parts, name_parts):
if not np.startswith(sp):
return False
return True
def show_matching_path(item, level=0):
name = item.text(0).lower()
# Проверяем соответствие до длины path_parts
if not path_parts:
matched = True
else:
matched = False
# Проверяем совпадение по пути
if path_matches_search(name, path_parts[:level+1]):
matched = True
# Проверяем текущий элемент
name = item.text(0).lower()
typ = item.text(1).lower()
if text in name or text in typ:
matched = True
item.setHidden(not matched)
# Открываем только те элементы, у которых есть совпадение в потомках или в себе
item.setExpanded(matched)
return matched
# Раскрываем, если это не последний уровень поиска
if matched and level < len(path_parts) - 1:
item.setExpanded(True)
else:
item.setExpanded(False)
matched_any_child = False
for i in range(item.childCount()):
child = item.child(i)
if show_matching_path(child, level + 1):
matched_any_child = True
return matched or matched_any_child
for i in range(self.tree.topLevelItemCount()):
match_recursive(self.tree.topLevelItem(i))
item = self.tree.topLevelItem(i)
hide_all(item)
show_matching_path(item, 0)
def on_add_clicked(self):
@ -165,4 +201,21 @@ class VariableSelectorDialog(QDialog):
self.all_vars.append(new_var)
self.var_map[name] = new_var # Чтобы в будущем не добавлялось повторно
self.accept()
self.accept()
def on_delete_clicked(self):
# Деактивируем (удаляем из видимых) выбранные переменные
for item in self.tree.selectedItems():
name = item.text(0)
if not name:
continue
if name in self.var_map:
var = self.var_map[name]
var['show_var'] = 'false'
var['enable'] = 'false'
self.accept()
def set_tool(self, item, text):
item.setToolTip(0, text)
item.setToolTip(1, text)

Binary file not shown.

View File

@ -1,8 +1,8 @@
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// Этот файл сгенерирован автоматически
#include "debug_tools.h"
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// Инклюды для доступа к переменным
#include "xp_project.h"
#include "RS_Functions_modbus.h"
#include "vector.h"
@ -44,7 +44,7 @@
#include "rmp_cntl_my1.h"
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// Экстерны для доступа к переменным
extern int ADC0finishAddr;
extern int ADC0startAddr;
extern int ADC1finishAddr;
@ -313,9 +313,9 @@ extern _iq zadan_Id_min;
extern int zero_ADC[20];
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// Определение массива с указателями на переменные для отладки
int DebugVar_Qnt = 1;
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
DebugVar_t dbg_vars[] = {\
{(char *)&project.cds_tk.plane_address , pt_uint16 , t_iq_none , "project.cds_tk.plane_address" }, \
{(char *)&ADC1finishAddr , pt_int16 , iq_none , "ADC1finishAddr" }, \
};

View File

@ -89,7 +89,7 @@ def parse_structs(filename):
structs = {}
typedef_map = {}
def parse_struct_element(elem):
fields = {}
@ -99,19 +99,22 @@ def parse_structs(filename):
# Проверка на вложенную структуру
nested_struct_elem = field.find("struct")
if nested_struct_elem is not None:
# Рекурсивно парсим вложенную структуру и вставляем её как подсловарь
nested_fields = parse_struct_element(nested_struct_elem)
fields[fname] = nested_fields
# Оборачиваем в dict с ключом 'type' для хранения типа из XML
fields[fname] = {
'type': ftype, # здесь тип, например "BENDER_ERROR"
**nested_fields # развёрнутые поля вложенной структуры
}
else:
# Обычное поле
fields[fname] = ftype
return fields
structs_elem = root.find("structs")
if structs_elem is not None:
for struct in structs_elem.findall("struct"):
@ -156,16 +159,11 @@ def safe_parse_xml(xml_path):
except Exception as e:
print(f"Неожиданная ошибка при чтении XML файла '{xml_path}': {e}")
return None, None
def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0):
"""
Рекурсивно разворачивает структуру.
type_str может быть строкой (имя типа) или словарём (вложенная структура).
"""
if depth > 10:
return []
# Если тип уже является вложенной структурой
# Если type_str — словарь структуры
if isinstance(type_str, dict):
fields = type_str
else:
@ -175,21 +173,42 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
return []
children = []
for field_name, field_type in fields.items():
full_name = f"{prefix}.{field_name}"
child = {
'name': full_name,
'type': field_type,
'pt_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
for field_name, field_value in fields.items():
# Пропускаем поле 'type', оно служит для хранения имени типа
if field_name == 'type':
continue
# Рекурсивно разворачиваем, если field_type — вложенная структура
subchildren = expand_struct_recursively(full_name, field_type, structs, typedefs, var_attrs, depth + 1)
if subchildren:
child['children'] = subchildren
full_name = f"{prefix}.{field_name}"
if isinstance(field_value, dict):
# Если вложенная структура — берем её имя типа из поля 'type' или пустую строку
type_name = field_value.get('type', '')
child = {
'name': full_name,
'type': type_name,
'pt_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
# Рекурсивно раскрываем вложенные поля
subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1)
if subchildren:
child['children'] = subchildren
else:
# Простое поле — строка типа
# Пропускаем указатели на функции
if isinstance(field_value, str) and "(" in field_value and "*" in field_value and ")" in field_value:
continue
child = {
'name': full_name,
'type': field_value,
'pt_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
children.append(child)
@ -219,6 +238,11 @@ def expand_vars(vars_list, structs, typedefs):
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
elif pt_type == 'pt_union' and isinstance(fields, dict):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
else:
expanded.append(var)

View File

@ -118,6 +118,7 @@ class VarEditor(QWidget):
proj_layout = QHBoxLayout()
proj_layout.addWidget(QLabel("Project Path:"))
self.proj_path_edit = QLineEdit()
self.proj_path_edit.returnPressed.connect(self.read_xml_file)
self.proj_path_edit.textChanged.connect(self.__on_proj_path_changed)
proj_layout.addWidget(self.proj_path_edit)
btn_proj_browse = QPushButton("...")
@ -129,6 +130,7 @@ class VarEditor(QWidget):
makefile_layout = QHBoxLayout()
makefile_layout.addWidget(QLabel("Makefile Path (relative path):"))
self.makefile_edit = QLineEdit()
self.makefile_edit.returnPressed.connect(self.read_xml_file)
self.makefile_edit.textChanged.connect(self.__on_makefile_path_changed)
makefile_layout.addWidget(self.makefile_edit)
btn_makefile_browse = QPushButton("...")
@ -375,7 +377,6 @@ class VarEditor(QWidget):
)
self.xml_output_edit.setText(file_path)
self.xml_path = file_path
self.read_xml_file()
def keyPressEvent(self, event: QKeyEvent):
if event.key() == Qt.Key_Delete:
@ -405,9 +406,11 @@ class VarEditor(QWidget):
def __on_xml_path_changed(self):
self.xml_path = self.get_xml_path()
self.read_xml_file()
def __on_proj_path_changed(self):
self.proj_path = self.get_proj_path()
self.read_xml_file()
def __on_makefile_path_changed(self):
self.makefile_path = self.get_makefile_path()
@ -415,6 +418,7 @@ class VarEditor(QWidget):
path = make_relative_path(self.makefile_path, self.proj_path)
self.makefile_edit.setText(path)
self.makefile_path = path
self.read_xml_file()
def __after_scanvars_finished(self):

View File

@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<analysis proj_path="F:/Work/Projects/TMS/TMS_new_bus" makefile_path="Debug/makefile" structs_path="Src/DebugTools/structs.xml">
<analysis proj_path="E:/.WORK/TMS/TMS_new_bus" makefile_path="Debug/makefile" structs_path="Src/DebugTools/structs.xml">
<variables>
<var name="ADC0finishAddr">
<show_var>false</show_var>
@ -27,7 +27,7 @@
</var>
<var name="ADC1finishAddr">
<show_var>false</show_var>
<enable>true</enable>
<enable>false</enable>
<shortname>ADC1finishAddr</shortname>
<pt_type>pt_int16</pt_type>
<iq_type>iq_none</iq_type>
@ -3602,8 +3602,8 @@
<static>false</static>
</var>
<var name="project.cds_tk.plane_address">
<show_var>true</show_var>
<enable>true</enable>
<show_var>false</show_var>
<enable>false</enable>
<shortname>project.cds_tk.plane_address</shortname>
<pt_type>pt_uint16</pt_type>
<iq_type>t_iq_none</iq_type>