таблица выбора элементов вынесена в отдельный класс
This commit is contained in:
		
							parent
							
								
									0d54031dd5
								
							
						
					
					
						commit
						4f949e9854
					
				@ -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.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)
 | 
			
		||||
        # Соединяем чекбокс с методом виджета
 | 
			
		||||
        self.autocomplete_checkbox.stateChanged.connect(self.vars_widget.set_autocomplete)
 | 
			
		||||
        # Устанавливаем начальное состояние автодополнения в виджете
 | 
			
		||||
        self.vars_widget.set_autocomplete(self.autocomplete_checkbox.isChecked())
 | 
			
		||||
 | 
			
		||||
        # --- Код в конце __init__ ---
 | 
			
		||||
        self.expanded_vars = setupVars.expand_vars(self.all_vars, self.structs, self.typedefs)
 | 
			
		||||
        self.build_completion_list()
 | 
			
		||||
        self.populate_tree()
 | 
			
		||||
 | 
			
		||||
    
 | 
			
		||||
    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()
 | 
			
		||||
        # Передаем данные в виджет
 | 
			
		||||
        self.vars_widget.set_data(self.expanded_vars)
 | 
			
		||||
 | 
			
		||||
    def on_add_clicked(self):
 | 
			
		||||
        self.selected_names = []
 | 
			
		||||
        self.tree.setFocus()
 | 
			
		||||
        # 5. Получаем имена из виджета
 | 
			
		||||
        selected_items = self.vars_widget.get_selected_items()
 | 
			
		||||
        if not selected_items:
 | 
			
		||||
            return
 | 
			
		||||
 | 
			
		||||
        for item in self.tree.selectedItems():
 | 
			
		||||
            name = item.text(0)  # имя переменной (в колонке 1)
 | 
			
		||||
            type_str = item.text(1)  # тип переменной (в колонке 2)
 | 
			
		||||
 | 
			
		||||
            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.done(QDialog.Accepted)
 | 
			
		||||
                self.var_map[name] = new_var
 | 
			
		||||
 | 
			
		||||
        self.accept() # Используем accept() вместо 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,45 +124,34 @@ 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):
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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:
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										458
									
								
								Src/selectTable.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										458
									
								
								Src/selectTable.py
									
									
									
									
									
										Normal file
									
								
							@ -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)
 | 
			
		||||
@ -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)
 | 
			
		||||
 | 
			
		||||
@ -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 >= 0x008120 && addr_val <= 0x009FFC) ||  // 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 >= 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;  // Íå ñîâïàëî
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user