From 4f949e9854c99bf6a5dd630989f1be4cc2508002 Mon Sep 17 00:00:00 2001 From: Razvalyaev Date: Sat, 12 Jul 2025 08:54:53 +0300 Subject: [PATCH] =?UTF-8?q?=D1=82=D0=B0=D0=B1=D0=BB=D0=B8=D1=86=D0=B0=20?= =?UTF-8?q?=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=B0=20=D1=8D=D0=BB=D0=B5=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=BE=D0=B2=20=D0=B2=D1=8B=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=B5=D0=BD=D0=B0=20=D0=B2=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BB=D0=B0=D1=81=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/VariableSelector.py | 556 +++++----------------------------------- Src/generateVars.py | 2 +- Src/selectTable.py | 458 +++++++++++++++++++++++++++++++++ Src/setupVars.py | 10 +- debug_tools.c | 62 ++--- debug_tools.h | 18 +- debug_vars.c | 38 +-- 7 files changed, 592 insertions(+), 552 deletions(-) create mode 100644 Src/selectTable.py diff --git a/Src/VariableSelector.py b/Src/VariableSelector.py index 4c3af50..6a38630 100644 --- a/Src/VariableSelector.py +++ b/Src/VariableSelector.py @@ -10,6 +10,7 @@ import VariableTable import setupVars import myXML import time +import selectTable array_re = re.compile(r'^(\w+)\[(\d+)\]$') @@ -44,418 +45,75 @@ class VariableSelectorDialog(QDialog): # При изменении состояния чекбокса сохраняем его self.autocomplete_checkbox.stateChanged.connect(self.save_checkbox_state) - self.search_input = QLineEdit() - self.search_input.setPlaceholderText("Поиск по имени переменной...") - self.search_input.textChanged.connect(self.on_search_text_changed) - - self.tree = QTreeWidget() - self.tree.setHeaderLabels(["Имя переменной", "Тип"]) - self.tree.setSelectionMode(QTreeWidget.ExtendedSelection) - self.tree.setRootIsDecorated(True) - self.tree.setUniformRowHeights(True) - - self.tree.setStyleSheet(""" - QTreeWidget::item:selected { - background-color: #87CEFA; - color: black; - } - QTreeWidget::item:hover { - background-color: #D3D3D3; - } - """) - + # Создаем кнопки, они остаются в диалоге self.btn_add = QPushButton("Добавить выбранные") - self.btn_add.clicked.connect(self.on_add_clicked) - self.btn_delete = QPushButton("Удалить выбранные") + + # Создаем экземпляр вашего готового виджета + self.vars_widget = selectTable.VariableSelectWidget(self) + + # Собираем новую, более простую компоновку + search_layout = QHBoxLayout() + search_layout.addWidget(QLabel("Поиск:")) + search_layout.addStretch() + search_layout.addWidget(self.autocomplete_checkbox) + + layout = QVBoxLayout(self) + layout.addLayout(search_layout) + layout.addWidget(self.vars_widget) # Добавляем ваш виджет целиком + + button_layout = QHBoxLayout() + button_layout.addWidget(self.btn_add) + button_layout.addWidget(self.btn_delete) + layout.addLayout(button_layout) + + # Соединяем сигналы кнопок с методами диалога + self.btn_add.clicked.connect(self.on_add_clicked) self.btn_delete.clicked.connect(self.on_delete_clicked) - self.completer = QCompleter() - self.completer.setCompletionMode(QCompleter.PopupCompletion) # важно! - self.completer.setCaseSensitivity(Qt.CaseInsensitive) - self.completer.setFilterMode(Qt.MatchContains) - self.completer.setWidget(self.search_input) + # Соединяем чекбокс с методом виджета + self.autocomplete_checkbox.stateChanged.connect(self.vars_widget.set_autocomplete) + # Устанавливаем начальное состояние автодополнения в виджете + self.vars_widget.set_autocomplete(self.autocomplete_checkbox.isChecked()) - - self.search_input.installEventFilter(self) - - # Создаем горизонтальный layout для "Поиск:" и чекбокса справа - search_layout = QHBoxLayout() - label_search = QLabel("Поиск:") - search_layout.addWidget(label_search, alignment=Qt.AlignLeft) - search_layout.addStretch() # чтобы чекбокс прижался вправо - search_layout.addWidget(self.autocomplete_checkbox, alignment=Qt.AlignRight) - self.completer.activated[str].connect(self.insert_completion) - - layout = QVBoxLayout() - layout.addLayout(search_layout) # заменили label и чекбокс - layout.addWidget(self.search_input) - layout.addWidget(self.tree) - layout.addWidget(self.btn_add) - layout.addWidget(self.btn_delete) - self.setLayout(layout) - + # --- Код в конце __init__ --- self.expanded_vars = setupVars.expand_vars(self.all_vars, self.structs, self.typedefs) - self.build_completion_list() - self.populate_tree() + # Передаем данные в виджет + self.vars_widget.set_data(self.expanded_vars) - - def get_full_item_name(self, item): - names = [] - while item: - names.append(item.text(0)) - item = item.parent() - return '.'.join(reversed(names)) - - def build_completion_list(self): - # Собираем список полных имён всех переменных и вложенных полей - completions = [] - - def recurse(var, prefix=''): - fullname = f"{prefix}.{var['name']}" if prefix else var['name'] - completions.append(fullname) - for child in var.get('children', []): - recurse(child, fullname) - - for v in self.expanded_vars: - recurse(v) - - self.all_completions = completions - - def add_tree_item_recursively(self, parent, var): - """ - Рекурсивно добавляет переменную и её дочерние поля в дерево. - Если parent == None, добавляет на верхний уровень. - """ - name = var['name'] - type_str = var.get('type', '') - show_var = var.get('show_var', 'false') == 'true' - - item = QTreeWidgetItem([name, type_str]) - item.setData(0, Qt.UserRole, name) - full_name = self.get_full_item_name(item) - self.node_index[full_name.lower()] = item - - # Делаем 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) - self.set_tool(item, "Уже добавлена") - - if parent is None: - self.tree.addTopLevelItem(item) - else: - parent.addChild(item) - - for child in var.get('children', []): - self.add_tree_item_recursively(item, child) - - - def populate_tree(self, vars_list=None): - if vars_list is None: - vars_list = self.expanded_vars - self.tree.clear() - self.node_index.clear() - for var in vars_list: - self.add_tree_item_recursively(None, var) - - header = self.tree.header() - header.setSectionResizeMode(QHeaderView.Interactive) # вручную можно менять - self.tree.setColumnWidth(0, 400) - self.tree.resizeColumnToContents(1) - - def expand_to_level(self, item, level, current_level=0): - """ - Рекурсивно раскрывает узлы до заданного уровня. - """ - if current_level < level: - item.setExpanded(True) - else: - item.setExpanded(False) - - for i in range(item.childCount()): - self.expand_to_level(item.child(i), level, current_level + 1) - - def filter_tree(self): - text = self.search_input.text().strip().lower() - path_parts = text.split('.') if text else [] - filtered_vars = filter_vars(self.expanded_vars, path_parts) - - # Сначала перерисовываем дерево - self.populate_tree(filtered_vars) - - # Теперь node_index уже пересоздан — можно работать - expand_level = len(path_parts) - 1 if path_parts else 0 - for i in range(self.tree.topLevelItemCount()): - item = self.tree.topLevelItem(i) - self.expand_to_level(item, expand_level) - - # Раскрываем путь до точного совпадения - if path_parts: - fullname = '.'.join(path_parts) - node = self.node_index.get(fullname.lower()) - if node: - parent = node.parent() - while parent: - parent.setExpanded(True) - parent = parent.parent() - - - - def find_node_by_path(self, root_vars, path_list): - current_level = root_vars - node = None - for part in path_list: - node = None - for var in current_level: - if var['name'] == part: - node = var - break - if node is None: - return None - current_level = node.get('children', []) - return node - - def update_completions(self, text=None): - if text is None: - text = self.search_input.text().strip() - else: - text = text.strip() - - parts = self.split_path(text) - path_parts = parts[:-1] if parts else [] - prefix = parts[-1].lower() if parts else '' - ends_with_sep = text.endswith('.') or text.endswith('->') or text.endswith('[') - is_index_suggestion = text.endswith('[') - - completions = [] - - def find_exact_node(parts): - if not parts: - return None - fullname = parts[0] - for p in parts[1:]: - fullname += '.' + p - return self.node_index.get(fullname.lower()) - - if is_index_suggestion: - base_text = text[:-1] # убираем '[' - parent_node = self.find_node_by_fullname(base_text) - if not parent_node: - base_text_clean = re.sub(r'\[\d+\]$', '', base_text) - parent_node = self.find_node_by_fullname(base_text_clean) - if parent_node: - seen = set() - for i in range(parent_node.childCount()): - child = parent_node.child(i) - cname = child.text(0) - m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname) - if m and cname not in seen: - completions.append(cname) - seen.add(cname) - self.completer.setModel(QStringListModel(completions)) - return completions - - if ends_with_sep: - node = self.find_node_by_fullname(text[:-1]) - if node: - completions.extend(node.child(i).text(0) for i in range(node.childCount())) - elif not path_parts: - # Первый уровень — только если имя начинается с prefix - for i in range(self.tree.topLevelItemCount()): - item = self.tree.topLevelItem(i) - name = item.text(0) - if name.lower().startswith(prefix): - completions.append(name) - else: - node = find_exact_node(path_parts) - if node: - for i in range(node.childCount()): - child = node.child(i) - name = child.text(0) - name_parts = child.data(0, Qt.UserRole + 10) - if name_parts is None: - name_parts = self.split_path(name) - child.setData(0, Qt.UserRole + 10, name_parts) - if not name_parts: - continue - last_part = name_parts[-1].lower() - if prefix == '' or last_part.startswith(prefix): # ← строго startswith - completions.append(name) - - self.completer.setModel(QStringListModel(completions)) - self.completer.complete() - return completions - - - - - - # Функция для поиска узла с полным именем - def find_node_by_fullname(self, name): - return self.node_index.get(name.lower()) - - def insert_completion(self, text): - node = self.find_node_by_fullname(text) - if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->') or text.endswith('[')): - # Определяем разделитель по имени первого ребёнка - child_name = node.child(0).text(0) - if child_name.startswith(text + '->'): - text += '->' - elif child_name.startswith(text + '.'): - text += '.' - elif '[' in child_name: - text += '[' # для массивов - else: - text += '.' # fallback - - if not self._bckspc_pressed: - self.search_input.setText(text) - self.search_input.setCursorPosition(len(text)) - - self.run_completions(text) - else: - self.search_input.setText(text) - self.search_input.setCursorPosition(len(text)) - - def eventFilter(self, obj, event): - if obj == self.search_input and isinstance(event, QKeyEvent): - if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier: - self.manual_completion_active = True - text = self.search_input.text().strip() - self.run_completions(text) - elif event.key() == Qt.Key_Escape: - # Esc — выключаем ручной режим и скрываем подсказки, если autocomplete выключен - if not self.autocomplete_checkbox.isChecked(): - self.manual_completion_active = False - self.completer.popup().hide() - return True - - if event.key() == Qt.Key_Backspace: - self._bckspc_pressed = True - else: - self._bckspc_pressed = False - - return super().eventFilter(obj, event) - - def run_completions(self, text): - completions = self.update_completions(text) - - if not self.autocomplete_checkbox.isChecked() and self._bckspc_pressed: - text = text[:-1] - - if len(completions) == 1 and completions[0].lower() == text.lower(): - # Найдем узел с таким именем - def find_exact_item(name): - stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())] - while stack: - node = stack.pop() - if node.text(0).lower() == name.lower(): - return node - for i in range(node.childCount()): - stack.append(node.child(i)) - return None - - node = find_exact_item(completions[0]) - if node and node.childCount() > 0: - # Используем первую подсказку, чтобы определить нужный разделитель - completions = self.update_completions(text + '.') - suggestion = completions[0] - - # Ищем, какой символ идёт после текущего текста - separator = '.' - if suggestion.startswith(text): - rest = suggestion[len(text):] - if rest.startswith(text + '->'): - separator += '->' - elif rest.startswith(text + '.'): - separator += '.' - elif '[' in rest: - separator += '[' # для массивов - else: - separator += '.' # fallback - - if not self._bckspc_pressed: - self.search_input.setText(text + separator) - completions = self.update_completions(text) - self.completer.setModel(QStringListModel(completions)) - self.completer.complete() - return True - - # Иначе просто показываем подсказки - self.completer.setModel(QStringListModel(completions)) - if completions: - self.completer.complete() - return True - - def on_search_text_changed(self, text): - self.filter_tree() - if text == None: - text = self.search_input.text().strip() - if self.autocomplete_checkbox.isChecked(): - self.run_completions(text) - else: - # Если выключено, показываем подсказки только если флаг ручного вызова True - if self.manual_completion_active: - self.run_completions(text) - else: - self.completer.popup().hide() - def on_add_clicked(self): - self.selected_names = [] - self.tree.setFocus() - - for item in self.tree.selectedItems(): - name = item.text(0) # имя переменной (в колонке 1) - type_str = item.text(1) # тип переменной (в колонке 2) + # 5. Получаем имена из виджета + selected_items = self.vars_widget.get_selected_items() + if not selected_items: + return - if not name: - continue - - self.selected_names.append((name, type_str)) + for item in selected_items: + name = item.text(0) + type_str = item.text(1) if name in self.var_map: - # Если переменная уже есть, просто включаем её и показываем var = self.var_map[name] var['show_var'] = 'true' var['enable'] = 'true' else: - # Создаём новый элемент переменной - # Получаем родительские параметры file_val = item.data(0, Qt.UserRole + 1) extern_val = item.data(0, Qt.UserRole + 2) static_val = item.data(0, Qt.UserRole + 3) - new_var = { - 'name': name, - 'type': type_str, - 'show_var': 'true', - 'enable': 'true', - 'shortname': name, - 'pt_type': '', - 'iq_type': '', - 'return_type': 'iq_none', - 'file': file_val, + 'name': name, 'type': type_str, 'show_var': 'true', + 'enable': 'true', 'shortname': name, 'pt_type': '', 'iq_type': '', + 'return_type': 'iq_none', 'file': file_val, 'extern': str(extern_val).lower() if extern_val else 'false', 'static': str(static_val).lower() if static_val else 'false', } - - # Добавляем в список переменных self.all_vars.append(new_var) - self.var_map[name] = new_var # Чтобы в будущем не добавлялось повторно + self.var_map[name] = new_var + + self.accept() # Используем accept() вместо done(QDialog.Accepted) - self.done(QDialog.Accepted) - - def on_delete_clicked(self): - selected_names = self._get_selected_var_names() + # 5. Получаем имена из виджета + selected_names = self.vars_widget.get_selected_var_names() if not selected_names: print("nothing selected") return @@ -466,46 +124,35 @@ class VariableSelectorDialog(QDialog): self.var_map[name]['show_var'] = 'false' self.var_map[name]['enable'] = 'false' - for v in self.all_vars: - if v['name'] == name: - v['show_var'] = 'false' - v['enable'] = 'false' - break + self.update_xml_vars(selected_names, 'false', 'false') + self.accept() - # Проверка пути к XML - if not hasattr(self, 'xml_path') or not self.xml_path: - from PySide2.QtWidgets import QMessageBox - QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.") + + + def update_xml_vars(self, names, show, enable): + """Обновляет флаги show_var и enable в XML файле.""" + if not self.xml_path: return - root, tree = myXML.safe_parse_xml(self.xml_path) - if root is None: - return - + if root is None: return vars_section = root.find('variables') - if vars_section is None: - return + if vars_section is None: return for var_elem in vars_section.findall('var'): - name = var_elem.attrib.get('name') - if name in selected_names: + if var_elem.attrib.get('name') in names: def set_text(tag, value): el = var_elem.find(tag) - if el is None: - el = ET.SubElement(var_elem, tag) + if el is None: el = ET.SubElement(var_elem, tag) el.text = value - set_text('show_var', 'false') - set_text('enable', 'false') - + set_text('show_var', show) + set_text('enable', enable) myXML.fwrite(root, self.xml_path) - self.done(QDialog.Accepted) + + def save_checkbox_state(self): + self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked()) - def set_tool(self, item, text): - item.setToolTip(0, text) - item.setToolTip(1, text) - def keyPressEvent(self, event): if event.key() == Qt.Key_Delete: @@ -611,84 +258,3 @@ class VariableSelectorDialog(QDialog): def save_checkbox_state(self): self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked()) - - - - def split_path(self, path): - """ - Разбивает путь на компоненты: - - 'foo[2].bar[1]->baz' → ['foo', [2]', 'bar', '[1]' 'baz'] - """ - tokens = [] - token = '' - i = 0 - while i < len(path): - c = path[i] - # Разделители: '->' и '.' - if c == '-' and path[i:i+2] == '->': - if token: - tokens.append(token) - token = '' - i += 2 - continue - elif c == '.': - if token: - tokens.append(token) - token = '' - i += 1 - continue - elif c == '[': - # Заканчиваем текущий токен, если есть - if token: - tokens.append(token) - token = '' - # Собираем индекс [N] - idx = '' - while i < len(path) and path[i] != ']': - idx += path[i] - i += 1 - if i < len(path) and path[i] == ']': - idx += ']' - i += 1 - tokens.append(idx) - continue - else: - token += c - i += 1 - if token: - tokens.append(token) - return tokens - - -def filter_vars(vars_list, path_parts): - """Рекурсивно фильтруем vars_list по path_parts по вхождению на любом уровне.""" - filtered = [] - - def matches_path(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 sp not in np: - return False - return True - - for var in vars_list: - fullname = var.get('fullname', var['name']) # желательно иметь полное имя - if not path_parts or matches_path(fullname, path_parts): - new_var = var.copy() - if 'children' in var: - new_var['children'] = filter_vars(var['children'], path_parts) - filtered.append(new_var) - else: - if 'children' in var: - child_filtered = filter_vars(var['children'], path_parts) - if child_filtered: - new_var = var.copy() - new_var['children'] = child_filtered - filtered.append(new_var) - - return filtered - - diff --git a/Src/generateVars.py b/Src/generateVars.py index d551e20..65393d3 100644 --- a/Src/generateVars.py +++ b/Src/generateVars.py @@ -348,7 +348,7 @@ def generate_vars_file(proj_path, xml_path, output_dir): f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки # Добавим комментарий после записи, если он есть comment_str = f' // {comment}' if comment else '' - line = f'{{(char *)&{f_name:<45} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}' + line = f'{{(char *)&{f_name:<57} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}' new_debug_vars[vname] = line else: diff --git a/Src/selectTable.py b/Src/selectTable.py new file mode 100644 index 0000000..bd55df4 --- /dev/null +++ b/Src/selectTable.py @@ -0,0 +1,458 @@ +# Поместите этот код перед классом VariableSelectorDialog + +import re +from PySide2.QtWidgets import ( + QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit, + QHeaderView, QCompleter +) +from PySide2.QtGui import QKeyEvent +from PySide2.QtCore import Qt, QStringListModel + +# Вспомогательные функции, которые теперь будут использоваться виджетом +def split_path(path): + """ + Разбивает путь на компоненты: + - 'foo[2].bar[1]->baz' → ['foo', [2]', 'bar', '[1]' 'baz'] + """ + tokens = [] + token = '' + i = 0 + while i < len(path): + c = path[i] + # Разделители: '->' и '.' + if c == '-' and path[i:i+2] == '->': + if token: + tokens.append(token) + token = '' + i += 2 + continue + elif c == '.': + if token: + tokens.append(token) + token = '' + i += 1 + continue + elif c == '[': + # Заканчиваем текущий токен, если есть + if token: + tokens.append(token) + token = '' + # Собираем индекс [N] + idx = '' + while i < len(path) and path[i] != ']': + idx += path[i] + i += 1 + if i < len(path) and path[i] == ']': + idx += ']' + i += 1 + tokens.append(idx) + continue + else: + token += c + i += 1 + if token: + tokens.append(token) + return tokens + +def filter_vars(vars_list, path_parts): + """Рекурсивно фильтруем vars_list по path_parts по вхождению на любом уровне.""" + filtered = [] + + def matches_path(name, search_parts): + name_parts = split_path(name) + if len(name_parts) < len(search_parts): + return False + + for sp, np in zip(search_parts, name_parts): + if sp not in np: + return False + return True + + for var in vars_list: + fullname = var.get('fullname', var['name']) # желательно иметь полное имя + if not path_parts or matches_path(fullname, path_parts): + new_var = var.copy() + if 'children' in var: + new_var['children'] = filter_vars(var['children'], path_parts) + filtered.append(new_var) + else: + if 'children' in var: + child_filtered = filter_vars(var['children'], path_parts) + if child_filtered: + new_var = var.copy() + new_var['children'] = child_filtered + filtered.append(new_var) + + return filtered + + +class VariableSelectWidget(QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.expanded_vars = [] + self.node_index = {} + self.is_autocomplete_on = True # <--- ДОБАВИТЬ ЭТУ СТРОКУ + self._bckspc_pressed = False + self.manual_completion_active = False + + # --- UI Элементы --- + self.search_input = QLineEdit() + self.search_input.setPlaceholderText("Поиск...") + + self.tree = QTreeWidget() + self.tree.setHeaderLabels(["Имя переменной", "Тип"]) + self.tree.setSelectionMode(QTreeWidget.ExtendedSelection) + self.tree.setRootIsDecorated(True) + self.tree.setUniformRowHeights(True) + self.tree.setStyleSheet(""" + QTreeWidget::item:selected { background-color: #87CEFA; color: black; } + QTreeWidget::item:hover { background-color: #D3D3D3; } + """) + + self.completer = QCompleter() + self.completer.setCompletionMode(QCompleter.PopupCompletion) + self.completer.setCaseSensitivity(Qt.CaseInsensitive) + self.completer.setFilterMode(Qt.MatchContains) + self.completer.setWidget(self.search_input) + + # --- Layout --- + layout = QVBoxLayout(self) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self.search_input) + layout.addWidget(self.tree) + + # --- Соединения --- + self.search_input.textChanged.connect(self.on_search_text_changed) + self.search_input.installEventFilter(self) + self.completer.activated[str].connect(self.insert_completion) + + # --- Публичные методы для управления виджетом снаружи --- + + def set_autocomplete(self, enabled: bool): + """Включает или выключает режим автодополнения.""" + self.is_autocomplete_on = enabled + + def set_data(self, vars_list): + """Основной метод для загрузки данных в виджет.""" + self.expanded_vars = vars_list + # self.build_completion_list() # Если нужна полная перестройка списка + self.populate_tree() + + def get_selected_items(self): + """Возвращает выделенные элементы QTreeWidget.""" + return self.tree.selectedItems() + + def get_selected_var_names(self): + """Возвращает имена выделенных переменных.""" + return [item.text(0) for item in self.tree.selectedItems() if item.text(0)] + + def expand_to_level(self, item, level, current_level=0): + """ + Рекурсивно раскрывает узлы до заданного уровня. + """ + if current_level < level: + item.setExpanded(True) + else: + item.setExpanded(False) + + for i in range(item.childCount()): + self.expand_to_level(item.child(i), level, current_level + 1) + + def populate_tree(self, vars_list=None): + if vars_list is None: + vars_list = self.expanded_vars + self.tree.clear() + self.node_index.clear() + for var in vars_list: + self.add_tree_item_recursively(None, var) + + header = self.tree.header() + header.setSectionResizeMode(QHeaderView.Interactive) # вручную можно менять + self.tree.setColumnWidth(0, 400) + self.tree.resizeColumnToContents(1) + + + def get_full_item_name(self, item): + names = [] + while item: + names.append(item.text(0)) + item = item.parent() + return '.'.join(reversed(names)) + + def add_tree_item_recursively(self, parent, var): + """ + Рекурсивно добавляет переменную и её дочерние поля в дерево. + Если parent == None, добавляет на верхний уровень. + """ + name = var['name'] + type_str = var.get('type', '') + show_var = var.get('show_var', 'false') == 'true' + + item = QTreeWidgetItem([name, type_str]) + item.setData(0, Qt.UserRole, name) + full_name = self.get_full_item_name(item) + self.node_index[full_name.lower()] = item + + # Делаем 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) + self.set_tool(item, "Уже добавлена") + + if parent is None: + self.tree.addTopLevelItem(item) + else: + parent.addChild(item) + + for child in var.get('children', []): + self.add_tree_item_recursively(item, child) + + + def filter_tree(self): + text = self.search_input.text().strip().lower() + path_parts = split_path(text) if text else [] + filtered_vars = filter_vars(self.expanded_vars, path_parts) + + # Сначала перерисовываем дерево + self.populate_tree(filtered_vars) + + # Теперь node_index уже пересоздан — можно работать + expand_level = len(path_parts) - 1 if path_parts else 0 + for i in range(self.tree.topLevelItemCount()): + item = self.tree.topLevelItem(i) + self.expand_to_level(item, expand_level) + + # Раскрываем путь до точного совпадения + if path_parts: + fullname = '.'.join(path_parts) + node = self.node_index.get(fullname.lower()) + if node: + parent = node.parent() + while parent: + parent.setExpanded(True) + parent = parent.parent() + + + + def find_node_by_path(self, root_vars, path_list): + current_level = root_vars + node = None + for part in path_list: + node = None + for var in current_level: + if var['name'] == part: + node = var + break + if node is None: + return None + current_level = node.get('children', []) + return node + + def update_completions(self, text=None): + if text is None: + text = self.search_input.text().strip() + else: + text = text.strip() + + parts = split_path(text) + path_parts = parts[:-1] if parts else [] + prefix = parts[-1].lower() if parts else '' + ends_with_sep = text.endswith('.') or text.endswith('->') or text.endswith('[') + is_index_suggestion = text.endswith('[') + + completions = [] + + def find_exact_node(parts): + if not parts: + return None + fullname = parts[0] + for p in parts[1:]: + fullname += '.' + p + return self.node_index.get(fullname.lower()) + + if is_index_suggestion: + base_text = text[:-1] # убираем '[' + parent_node = self.find_node_by_fullname(base_text) + if not parent_node: + base_text_clean = re.sub(r'\[\d+\]$', '', base_text) + parent_node = self.find_node_by_fullname(base_text_clean) + if parent_node: + seen = set() + for i in range(parent_node.childCount()): + child = parent_node.child(i) + cname = child.text(0) + m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname) + if m and cname not in seen: + completions.append(cname) + seen.add(cname) + self.completer.setModel(QStringListModel(completions)) + return completions + + if ends_with_sep: + node = self.find_node_by_fullname(text[:-1]) + if node: + completions.extend(node.child(i).text(0) for i in range(node.childCount())) + elif not path_parts: + # Первый уровень — только если имя начинается с prefix + for i in range(self.tree.topLevelItemCount()): + item = self.tree.topLevelItem(i) + name = item.text(0) + if name.lower().startswith(prefix): + completions.append(name) + else: + node = find_exact_node(path_parts) + if node: + for i in range(node.childCount()): + child = node.child(i) + name = child.text(0) + name_parts = child.data(0, Qt.UserRole + 10) + if name_parts is None: + name_parts = split_path(name) + child.setData(0, Qt.UserRole + 10, name_parts) + if not name_parts: + continue + last_part = name_parts[-1].lower() + if prefix == '' or last_part.startswith(prefix): # ← строго startswith + completions.append(name) + + self.completer.setModel(QStringListModel(completions)) + self.completer.complete() + return completions + + + + + + # Функция для поиска узла с полным именем + def find_node_by_fullname(self, name): + return self.node_index.get(name.lower()) + + def insert_completion(self, text): + node = self.find_node_by_fullname(text) + if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->') or text.endswith('[')): + # Определяем разделитель по имени первого ребёнка + child_name = node.child(0).text(0) + if child_name.startswith(text + '->'): + text += '->' + elif child_name.startswith(text + '.'): + text += '.' + elif '[' in child_name: + text += '[' # для массивов + else: + text += '.' # fallback + + if not self._bckspc_pressed: + self.search_input.setText(text) + self.search_input.setCursorPosition(len(text)) + + self.run_completions(text) + else: + self.search_input.setText(text) + self.search_input.setCursorPosition(len(text)) + + def eventFilter(self, obj, event): + if obj == self.search_input and isinstance(event, QKeyEvent): + if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier: + self.manual_completion_active = True + text = self.search_input.text().strip() + self.run_completions(text) + elif event.key() == Qt.Key_Escape: + # Esc — выключаем ручной режим и скрываем подсказки, если autocomplete выключен + if not self.is_autocomplete_on: + self.manual_completion_active = False + self.completer.popup().hide() + return True + + if event.key() == Qt.Key_Backspace: + self._bckspc_pressed = True + else: + self._bckspc_pressed = False + + return super().eventFilter(obj, event) + + def run_completions(self, text): + completions = self.update_completions(text) + + if not self.is_autocomplete_on and self._bckspc_pressed: + text = text[:-1] + + if len(completions) == 1 and completions[0].lower() == text.lower(): + # Найдем узел с таким именем + def find_exact_item(name): + stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())] + while stack: + node = stack.pop() + if node.text(0).lower() == name.lower(): + return node + for i in range(node.childCount()): + stack.append(node.child(i)) + return None + + node = find_exact_item(completions[0]) + if node and node.childCount() > 0: + # Используем первую подсказку, чтобы определить нужный разделитель + completions = self.update_completions(text + '.') + suggestion = completions[0] + + # Ищем, какой символ идёт после текущего текста + separator = '.' + if suggestion.startswith(text): + rest = suggestion[len(text):] + if rest.startswith(text + '->'): + separator += '->' + elif rest.startswith(text + '.'): + separator += '.' + elif '[' in rest: + separator += '[' # для массивов + else: + separator += '.' # fallback + + if not self._bckspc_pressed: + self.search_input.setText(text + separator) + completions = self.update_completions(text) + self.completer.setModel(QStringListModel(completions)) + self.completer.complete() + return True + + # Иначе просто показываем подсказки + self.completer.setModel(QStringListModel(completions)) + if completions: + self.completer.complete() + return True + + def on_search_text_changed(self, text): + self.filter_tree() + if text == None: + text = self.search_input.text().strip() + if self.is_autocomplete_on: + self.run_completions(text) + else: + # Если выключено, показываем подсказки только если флаг ручного вызова True + if self.manual_completion_active: + self.run_completions(text) + else: + self.completer.popup().hide() + + def build_completion_list(self): + completions = [] + + def recurse(var, prefix=''): + fullname = f"{prefix}.{var['name']}" if prefix else var['name'] + completions.append(fullname) + for child in var.get('children', []): + recurse(child, fullname) + + for v in self.expanded_vars: + recurse(v) + self.all_completions = completions + + def set_tool(self, item, text): + item.setToolTip(0, text) + item.setToolTip(1, text) \ No newline at end of file diff --git a/Src/setupVars.py b/Src/setupVars.py index f43d87d..560ed86 100644 --- a/Src/setupVars.py +++ b/Src/setupVars.py @@ -266,6 +266,12 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de else: field_type_str = None + + if '*' in field_type_str: + full_name_prefix = full_name + '*' + else: + full_name_prefix = full_name + # Обработка, если поле — строка (тип или массив) if field_type_str: base_subtype, sub_dims = parse_array_dims(field_type_str) @@ -311,7 +317,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de if isinstance(field_value, dict): # Это одиночная структура — раскрываем рекурсивно - sub_items = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1) + sub_items = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1) child = { 'name': full_name, 'type': field_type_str, @@ -353,7 +359,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de 'extern': var_attrs.get('extern'), 'static': var_attrs.get('static'), } - subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1) + subchildren = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1) if subchildren: child['children'] = subchildren children.append(child) diff --git a/debug_tools.c b/debug_tools.c index c597122..eea1237 100644 --- a/debug_tools.c +++ b/debug_tools.c @@ -1,46 +1,52 @@ #include "debug_tools.h" #include "IQmathLib.h" +DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT; + static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var); static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var); - - +///////////////////////////----EXAPLE-----////////////////////////////// long var_numb = 1; long return_var; long return_ll_var; -DebugVar_t dbg_var_ll; int result; char ext_date[] = {7, 233, 11, 07, 16, 50}; -int Debug_Test_Example(void) +void Debug_Test_Example(void) { result = Debug_ReadVar(&dbg_vars[var_numb], &return_var); if(Debug_LowLevel_Initialize(ext_date) == 0) - result = Debug_LowLevel_ReadVar(&dbg_var_ll, &return_ll_var); + result = Debug_LowLevel_ReadVar(&return_ll_var); } - -int Debug_LowLevel_ReadVar(DebugVar_t *var_ll, long *return_long) +///////////////////////////----PUBLIC-----////////////////////////////// +int Debug_LowLevel_ReadVar(long *return_long) { - if (var_ll == NULL) + if (return_long == NULL) + return 1; + if (debug_ll.isVerified == 0) return 1; - char *addr = var_ll->Ptr; + char *addr = debug_ll.dbg_var.Ptr; unsigned long addr_val = (unsigned long)addr; - // TMS320F2812 + + // ( .cmd ) if (!( - (addr_val <= 0x0007FF) || // RAMM0 + RAMM1 - (addr_val >= 0x008000 && addr_val <= 0x009FFF) || // L0 + L1 SARAM - (addr_val >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0 - (addr_val >= 0x3D8000 && addr_val <= 0x3EFFFF) || // Flash A-F - (addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) // Boot ROM / Reset + (addr_val <= 0x0007FF) || // RAMM0 + RAMM1 + (addr_val >= 0x008120 && addr_val <= 0x009FFC) || // L0 + L1 SARAM + (addr_val >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0 + (addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) || // BOOTROM + RESET + (addr_val >= 0x080002 && addr_val <= 0x09FFFF) || // RAMEX1 + (addr_val >= 0x0F0000 && addr_val <= 0x0FFEFF) || // RAMEX4 + (addr_val >= 0x100002 && addr_val <= 0x103FFF) || // RAMEX0 + RAMEX2 + RAMEX01 + (addr_val >= 0x102000 && addr_val <= 0x103FFF) // RAMEX2 )) { - return 2; // , + return 2; // } - convertDebugVarToIQx(var_ll, return_long); + convertDebugVarToIQx(&debug_ll.dbg_var, return_long); return 0; } @@ -87,31 +93,25 @@ int Debug_LowLevel_Initialize(const char* external_date) } // external_date - DateTimeHex ext; + DateTime_t ext; ext.year = (external_date[0] << 8) | external_date[1]; ext.day = external_date[2]; ext.month = external_date[3]; ext.hour = external_date[4]; ext.minute = external_date[5]; - // build (, ) - DateTimeHex build; - - build.year = BUILD_YEAR; // 2025 - build.month = BUILD_MONTH; // 7 - build.day = BUILD_DATA; // 11 - build.hour = BUILD_HOURS; // 16 - build.minute = BUILD_MINUTES;// 50 // - if (ext.year == build.year && - ext.month == build.month && - ext.day == build.day && - ext.hour == build.hour && - ext.minute == build.minute) + if (ext.year == debug_ll.build_date.year && + ext.month == debug_ll.build_date.month && + ext.day == debug_ll.build_date.day && + ext.hour == debug_ll.build_date.hour && + ext.minute == debug_ll.build_date.minute) { + debug_ll.isVerified = 1; return 0; // } + debug_ll.isVerified = 0; return 1; // } diff --git a/debug_tools.h b/debug_tools.h index 4704cf6..953a27c 100644 --- a/debug_tools.h +++ b/debug_tools.h @@ -76,23 +76,33 @@ typedef struct char name[11]; // 10 + '\0' }DebugVar_t; - typedef struct { int year; char month; char day; char hour; char minute; -} DateTimeHex; +} DateTime_t; + +typedef struct +{ + DateTime_t build_date; + unsigned int isVerified; + DebugVar_t dbg_var; +}DebugLowLevel_t; +extern DebugLowLevel_t debug_ll; +#define DATE_INIT {BUILD_YEAR, BUILD_MONTH, BUILD_DATA, BUILD_HOURS, BUILD_MINUTES} +#define DEBUG_VAR_INIT {0, pt_uint16, t_iq_none, t_iq_none, "\0"} +#define DEBUG_LOWLEVEL_INIT {DATE_INIT, 0, DEBUG_VAR_INIT} extern int DebugVar_Qnt; extern DebugVar_t dbg_vars[]; -int Debug_Test_Example(void); +void Debug_Test_Example(void); -int Debug_LowLevel_ReadVar(DebugVar_t *var_ll, long *return_long); +int Debug_LowLevel_ReadVar(long *return_long); int Debug_ReadVar(DebugVar_t *var, long *return_long); int Debug_ReadVarName(DebugVar_t *var, char *name_ptr); int Debug_LowLevel_Initialize(const char* external_date); diff --git a/debug_vars.c b/debug_vars.c index d87a70e..ae85ba8 100644 --- a/debug_vars.c +++ b/debug_vars.c @@ -315,23 +315,23 @@ extern int zero_ADC[20]; int DebugVar_Qnt = 19; #pragma DATA_SECTION(dbg_vars,".dbgvar_info") DebugVar_t dbg_vars[] = {\ -{(char *)&ADC0finishAddr, pt_int16, t_iq_none, t_iq_none, "ADC0EndAdr" }, \ -{(char *)&ADC_f[0][0], pt_int16, t_iq_none, t_iq_none, "ADC_f00" }, \ -{(char *)&ADC_f[0][1], pt_int16, t_iq_none, t_iq_none, "ADC_f01" }, \ -{(char *)&ADC_f[0][2], pt_int16, t_iq_none, t_iq_none, "ADC_f02" }, \ -{(char *)&ADC_f[0][3], pt_int16, t_iq_none, t_iq_none, "ADC_f03" }, \ -{(char *)&ADC_f[0][4], pt_int16, t_iq_none, t_iq_none, "ADC_f04" }, \ -{(char *)&ADC_f[0][5], pt_int16, t_iq_none, t_iq_none, "ADC_f05" }, \ -{(char *)&ADC_f[0][6], pt_int16, t_iq_none, t_iq_none, "ADC_f06" }, \ -{(char *)&ADC_f[0][7], pt_int16, t_iq, t_iq_none, "ADC_f07" }, \ -{(char *)&ADC_f[0][8], pt_int16, t_iq_none, t_iq_none, "ADC_f08" }, \ -{(char *)&ADC_f[0][9], pt_int16, t_iq_none, t_iq_none, "ADC_f09" }, \ -{(char *)&ADC_f[0][10], pt_int16, t_iq_none, t_iq_none, "ADC_f010" }, \ -{(char *)&ADC_f[0][11], pt_int16, t_iq_none, t_iq_none, "ADC_f011" }, \ -{(char *)&ADC_f[0][12], pt_int16, t_iq_none, t_iq_none, "ADC_f012" }, \ -{(char *)&ADC_f[0][13], pt_int16, t_iq_none, t_iq_none, "ADC_f013" }, \ -{(char *)&ADC_f[0][14], pt_int16, t_iq_none, t_iq_none, "ADC_f014" }, \ -{(char *)&ADC_f[0][15], pt_int16, t_iq_none, t_iq_none, "ADC_f015" }, \ -{(char *)&project.cds_tk[2].read.sbus.mask_protect_tk.all, pt_uint16, t_iq_none, t_iq_none, "tk2_ackcur" }, \ -{(char *)&project.cds_tk[1].plane_address, pt_uint16, t_iq_none, t_iq_none, "tk1_adr" }, \ +{(char *)&ADC0finishAddr, pt_int16, t_iq_none, t_iq_none, "ADC0EndAdr" }, \ +{(char *)&ADC_f[0][0], pt_int16, t_iq_none, t_iq_none, "ADC_f00" }, \ +{(char *)&ADC_f[0][1], pt_int16, t_iq_none, t_iq_none, "ADC_f01" }, \ +{(char *)&ADC_f[0][2], pt_int16, t_iq_none, t_iq_none, "ADC_f02" }, \ +{(char *)&ADC_f[0][3], pt_int16, t_iq_none, t_iq_none, "ADC_f03" }, \ +{(char *)&ADC_f[0][4], pt_int16, t_iq_none, t_iq_none, "ADC_f04" }, \ +{(char *)&ADC_f[0][5], pt_int16, t_iq_none, t_iq_none, "ADC_f05" }, \ +{(char *)&ADC_f[0][6], pt_int16, t_iq_none, t_iq_none, "ADC_f06" }, \ +{(char *)&ADC_f[0][7], pt_int16, t_iq, t_iq_none, "ADC_f07" }, \ +{(char *)&ADC_f[0][8], pt_int16, t_iq_none, t_iq_none, "ADC_f08" }, \ +{(char *)&ADC_f[0][9], pt_int16, t_iq_none, t_iq_none, "ADC_f09" }, \ +{(char *)&ADC_f[0][10], pt_int16, t_iq_none, t_iq_none, "ADC_f010" }, \ +{(char *)&ADC_f[0][11], pt_int16, t_iq_none, t_iq_none, "ADC_f011" }, \ +{(char *)&ADC_f[0][12], pt_int16, t_iq_none, t_iq_none, "ADC_f012" }, \ +{(char *)&ADC_f[0][13], pt_int16, t_iq_none, t_iq_none, "ADC_f013" }, \ +{(char *)&ADC_f[0][14], pt_int16, t_iq_none, t_iq_none, "ADC_f014" }, \ +{(char *)&ADC_f[0][15], pt_int16, t_iq_none, t_iq_none, "ADC_f015" }, \ +{(char *)&project.cds_tk[2].read.sbus.mask_protect_tk.all, pt_uint16, t_iq_none, t_iq_none, "tk2_ackcur" }, \ +{(char *)&project.cds_tk[1].plane_address, pt_uint16, t_iq_none, t_iq_none, "tk1_adr" }, \ };