таблица выбора элементов вынесена в отдельный класс
This commit is contained in:
parent
0d54031dd5
commit
4f949e9854
@ -10,6 +10,7 @@ import VariableTable
|
|||||||
import setupVars
|
import setupVars
|
||||||
import myXML
|
import myXML
|
||||||
import time
|
import time
|
||||||
|
import selectTable
|
||||||
|
|
||||||
|
|
||||||
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
|
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
|
||||||
@ -44,418 +45,75 @@ class VariableSelectorDialog(QDialog):
|
|||||||
# При изменении состояния чекбокса сохраняем его
|
# При изменении состояния чекбокса сохраняем его
|
||||||
self.autocomplete_checkbox.stateChanged.connect(self.save_checkbox_state)
|
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 = QPushButton("Добавить выбранные")
|
||||||
self.btn_add.clicked.connect(self.on_add_clicked)
|
|
||||||
|
|
||||||
self.btn_delete = QPushButton("Удалить выбранные")
|
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.btn_delete.clicked.connect(self.on_delete_clicked)
|
||||||
|
|
||||||
self.completer = QCompleter()
|
# Соединяем чекбокс с методом виджета
|
||||||
self.completer.setCompletionMode(QCompleter.PopupCompletion) # важно!
|
self.autocomplete_checkbox.stateChanged.connect(self.vars_widget.set_autocomplete)
|
||||||
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
# Устанавливаем начальное состояние автодополнения в виджете
|
||||||
self.completer.setFilterMode(Qt.MatchContains)
|
self.vars_widget.set_autocomplete(self.autocomplete_checkbox.isChecked())
|
||||||
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)
|
|
||||||
|
|
||||||
|
# --- Код в конце __init__ ---
|
||||||
self.expanded_vars = setupVars.expand_vars(self.all_vars, self.structs, self.typedefs)
|
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):
|
def on_add_clicked(self):
|
||||||
self.selected_names = []
|
# 5. Получаем имена из виджета
|
||||||
self.tree.setFocus()
|
selected_items = self.vars_widget.get_selected_items()
|
||||||
|
if not selected_items:
|
||||||
|
return
|
||||||
|
|
||||||
for item in self.tree.selectedItems():
|
for item in selected_items:
|
||||||
name = item.text(0) # имя переменной (в колонке 1)
|
name = item.text(0)
|
||||||
type_str = item.text(1) # тип переменной (в колонке 2)
|
type_str = item.text(1)
|
||||||
|
|
||||||
if not name:
|
|
||||||
continue
|
|
||||||
|
|
||||||
self.selected_names.append((name, type_str))
|
|
||||||
|
|
||||||
if name in self.var_map:
|
if name in self.var_map:
|
||||||
# Если переменная уже есть, просто включаем её и показываем
|
|
||||||
var = self.var_map[name]
|
var = self.var_map[name]
|
||||||
var['show_var'] = 'true'
|
var['show_var'] = 'true'
|
||||||
var['enable'] = 'true'
|
var['enable'] = 'true'
|
||||||
else:
|
else:
|
||||||
# Создаём новый элемент переменной
|
|
||||||
# Получаем родительские параметры
|
|
||||||
file_val = item.data(0, Qt.UserRole + 1)
|
file_val = item.data(0, Qt.UserRole + 1)
|
||||||
extern_val = item.data(0, Qt.UserRole + 2)
|
extern_val = item.data(0, Qt.UserRole + 2)
|
||||||
static_val = item.data(0, Qt.UserRole + 3)
|
static_val = item.data(0, Qt.UserRole + 3)
|
||||||
|
|
||||||
new_var = {
|
new_var = {
|
||||||
'name': name,
|
'name': name, 'type': type_str, 'show_var': 'true',
|
||||||
'type': type_str,
|
'enable': 'true', 'shortname': name, 'pt_type': '', 'iq_type': '',
|
||||||
'show_var': 'true',
|
'return_type': 'iq_none', 'file': file_val,
|
||||||
'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',
|
'extern': str(extern_val).lower() if extern_val else 'false',
|
||||||
'static': str(static_val).lower() if static_val else 'false',
|
'static': str(static_val).lower() if static_val else 'false',
|
||||||
}
|
}
|
||||||
|
|
||||||
# Добавляем в список переменных
|
|
||||||
self.all_vars.append(new_var)
|
self.all_vars.append(new_var)
|
||||||
self.var_map[name] = new_var # Чтобы в будущем не добавлялось повторно
|
self.var_map[name] = new_var
|
||||||
|
|
||||||
self.done(QDialog.Accepted)
|
|
||||||
|
|
||||||
|
self.accept() # Используем accept() вместо done(QDialog.Accepted)
|
||||||
|
|
||||||
def on_delete_clicked(self):
|
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:
|
if not selected_names:
|
||||||
print("nothing selected")
|
print("nothing selected")
|
||||||
return
|
return
|
||||||
@ -466,45 +124,34 @@ class VariableSelectorDialog(QDialog):
|
|||||||
self.var_map[name]['show_var'] = 'false'
|
self.var_map[name]['show_var'] = 'false'
|
||||||
self.var_map[name]['enable'] = 'false'
|
self.var_map[name]['enable'] = 'false'
|
||||||
|
|
||||||
for v in self.all_vars:
|
self.update_xml_vars(selected_names, 'false', 'false')
|
||||||
if v['name'] == name:
|
self.accept()
|
||||||
v['show_var'] = 'false'
|
|
||||||
v['enable'] = 'false'
|
|
||||||
break
|
|
||||||
|
|
||||||
# Проверка пути к XML
|
|
||||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
|
||||||
from PySide2.QtWidgets import QMessageBox
|
def update_xml_vars(self, names, show, enable):
|
||||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
|
"""Обновляет флаги show_var и enable в XML файле."""
|
||||||
|
if not self.xml_path:
|
||||||
return
|
return
|
||||||
|
|
||||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||||
if root is None:
|
if root is None: return
|
||||||
return
|
|
||||||
|
|
||||||
vars_section = root.find('variables')
|
vars_section = root.find('variables')
|
||||||
if vars_section is None:
|
if vars_section is None: return
|
||||||
return
|
|
||||||
|
|
||||||
for var_elem in vars_section.findall('var'):
|
for var_elem in vars_section.findall('var'):
|
||||||
name = var_elem.attrib.get('name')
|
if var_elem.attrib.get('name') in names:
|
||||||
if name in selected_names:
|
|
||||||
def set_text(tag, value):
|
def set_text(tag, value):
|
||||||
el = var_elem.find(tag)
|
el = var_elem.find(tag)
|
||||||
if el is None:
|
if el is None: el = ET.SubElement(var_elem, tag)
|
||||||
el = ET.SubElement(var_elem, tag)
|
|
||||||
el.text = value
|
el.text = value
|
||||||
set_text('show_var', 'false')
|
set_text('show_var', show)
|
||||||
set_text('enable', 'false')
|
set_text('enable', enable)
|
||||||
|
|
||||||
myXML.fwrite(root, self.xml_path)
|
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):
|
def keyPressEvent(self, event):
|
||||||
@ -611,84 +258,3 @@ class VariableSelectorDialog(QDialog):
|
|||||||
|
|
||||||
def save_checkbox_state(self):
|
def save_checkbox_state(self):
|
||||||
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
|
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}"' # оборачиваем в кавычки
|
f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки
|
||||||
# Добавим комментарий после записи, если он есть
|
# Добавим комментарий после записи, если он есть
|
||||||
comment_str = f' // {comment}' if comment else ''
|
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
|
new_debug_vars[vname] = line
|
||||||
|
|
||||||
else:
|
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:
|
else:
|
||||||
field_type_str = None
|
field_type_str = None
|
||||||
|
|
||||||
|
|
||||||
|
if '*' in field_type_str:
|
||||||
|
full_name_prefix = full_name + '*'
|
||||||
|
else:
|
||||||
|
full_name_prefix = full_name
|
||||||
|
|
||||||
# Обработка, если поле — строка (тип или массив)
|
# Обработка, если поле — строка (тип или массив)
|
||||||
if field_type_str:
|
if field_type_str:
|
||||||
base_subtype, sub_dims = parse_array_dims(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):
|
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 = {
|
child = {
|
||||||
'name': full_name,
|
'name': full_name,
|
||||||
'type': field_type_str,
|
'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'),
|
'extern': var_attrs.get('extern'),
|
||||||
'static': var_attrs.get('static'),
|
'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:
|
if subchildren:
|
||||||
child['children'] = subchildren
|
child['children'] = subchildren
|
||||||
children.append(child)
|
children.append(child)
|
||||||
|
@ -1,46 +1,52 @@
|
|||||||
#include "debug_tools.h"
|
#include "debug_tools.h"
|
||||||
#include "IQmathLib.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 getDebugVar(DebugVar_t *var, long *int_var, float *float_var);
|
||||||
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var);
|
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var);
|
||||||
|
|
||||||
|
///////////////////////////----EXAPLE-----//////////////////////////////
|
||||||
|
|
||||||
long var_numb = 1;
|
long var_numb = 1;
|
||||||
long return_var;
|
long return_var;
|
||||||
long return_ll_var;
|
long return_ll_var;
|
||||||
DebugVar_t dbg_var_ll;
|
|
||||||
int result;
|
int result;
|
||||||
char ext_date[] = {7, 233, 11, 07, 16, 50};
|
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);
|
result = Debug_ReadVar(&dbg_vars[var_numb], &return_var);
|
||||||
|
|
||||||
|
|
||||||
if(Debug_LowLevel_Initialize(ext_date) == 0)
|
if(Debug_LowLevel_Initialize(ext_date) == 0)
|
||||||
result = Debug_LowLevel_ReadVar(&dbg_var_ll, &return_ll_var);
|
result = Debug_LowLevel_ReadVar(&return_ll_var);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////----PUBLIC-----//////////////////////////////
|
||||||
int Debug_LowLevel_ReadVar(DebugVar_t *var_ll, long *return_long)
|
int Debug_LowLevel_ReadVar(long *return_long)
|
||||||
{
|
{
|
||||||
if (var_ll == NULL)
|
if (return_long == NULL)
|
||||||
|
return 1;
|
||||||
|
if (debug_ll.isVerified == 0)
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
char *addr = var_ll->Ptr;
|
char *addr = debug_ll.dbg_var.Ptr;
|
||||||
unsigned long addr_val = (unsigned long)addr;
|
unsigned long addr_val = (unsigned long)addr;
|
||||||
// Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè íà TMS320F2812
|
|
||||||
|
// Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè (èç .cmd ôàéëà)
|
||||||
if (!(
|
if (!(
|
||||||
(addr_val <= 0x0007FF) || // RAMM0 + RAMM1
|
(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 >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0
|
||||||
(addr_val >= 0x3D8000 && addr_val <= 0x3EFFFF) || // Flash A-F
|
(addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) || // BOOTROM + RESET
|
||||||
(addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) // Boot ROM / 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;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -87,31 +93,25 @@ int Debug_LowLevel_Initialize(const char* external_date)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ïðåîáðàçóåì external_date â ñòðóêòóðó
|
// Ïðåîáðàçóåì external_date â ñòðóêòóðó
|
||||||
DateTimeHex ext;
|
DateTime_t ext;
|
||||||
ext.year = (external_date[0] << 8) | external_date[1];
|
ext.year = (external_date[0] << 8) | external_date[1];
|
||||||
ext.day = external_date[2];
|
ext.day = external_date[2];
|
||||||
ext.month = external_date[3];
|
ext.month = external_date[3];
|
||||||
ext.hour = external_date[4];
|
ext.hour = external_date[4];
|
||||||
ext.minute = external_date[5];
|
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 &&
|
if (ext.year == debug_ll.build_date.year &&
|
||||||
ext.month == build.month &&
|
ext.month == debug_ll.build_date.month &&
|
||||||
ext.day == build.day &&
|
ext.day == debug_ll.build_date.day &&
|
||||||
ext.hour == build.hour &&
|
ext.hour == debug_ll.build_date.hour &&
|
||||||
ext.minute == build.minute)
|
ext.minute == debug_ll.build_date.minute)
|
||||||
{
|
{
|
||||||
|
debug_ll.isVerified = 1;
|
||||||
return 0; // Ñîâïàëî
|
return 0; // Ñîâïàëî
|
||||||
}
|
}
|
||||||
|
debug_ll.isVerified = 0;
|
||||||
|
|
||||||
return 1; // Íå ñîâïàëî
|
return 1; // Íå ñîâïàëî
|
||||||
}
|
}
|
||||||
|
@ -76,23 +76,33 @@ typedef struct
|
|||||||
char name[11]; // 10 ñèìâîëîâ + '\0'
|
char name[11]; // 10 ñèìâîëîâ + '\0'
|
||||||
}DebugVar_t;
|
}DebugVar_t;
|
||||||
|
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int year;
|
int year;
|
||||||
char month;
|
char month;
|
||||||
char day;
|
char day;
|
||||||
char hour;
|
char hour;
|
||||||
char minute;
|
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 int DebugVar_Qnt;
|
||||||
extern DebugVar_t dbg_vars[];
|
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_ReadVar(DebugVar_t *var, long *return_long);
|
||||||
int Debug_ReadVarName(DebugVar_t *var, char *name_ptr);
|
int Debug_ReadVarName(DebugVar_t *var, char *name_ptr);
|
||||||
int Debug_LowLevel_Initialize(const char* external_date);
|
int Debug_LowLevel_Initialize(const char* external_date);
|
||||||
|
Loading…
Reference in New Issue
Block a user