Compare commits
No commits in common. "3343428796ccbc38d6bde4a0ad97c401a44ff827" and "0b50c31aa8a3b7be174ad7024baba51984f29cd4" have entirely different histories.
3343428796
...
0b50c31aa8
BIN
DebugVarEdit.exe
BIN
DebugVarEdit.exe
Binary file not shown.
@ -1,10 +1,9 @@
|
||||
import re
|
||||
from PySide6.QtWidgets import (
|
||||
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
|
||||
QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout
|
||||
QLineEdit, QLabel, QHeaderView
|
||||
)
|
||||
from PySide6.QtGui import QKeySequence, QKeyEvent
|
||||
from PySide6.QtCore import Qt, QStringListModel, QSettings
|
||||
from PySide6.QtCore import Qt
|
||||
from setupVars import *
|
||||
from scanVars import *
|
||||
|
||||
@ -26,21 +25,9 @@ class VariableSelectorDialog(QDialog):
|
||||
|
||||
self.xml_path = xml_path # сохраняем путь к xml
|
||||
|
||||
# --- Добавляем чекбокс для автодополнения ---
|
||||
self.autocomplete_checkbox = QCheckBox("Включить автодополнение")
|
||||
self.autocomplete_checkbox.setChecked(True)
|
||||
|
||||
# Инициализируем QSettings с именем организации и приложения
|
||||
self.settings = QSettings("SET", "DebugVarEdit_VarsSelector")
|
||||
# Восстанавливаем сохранённое состояние чекбокса, если есть
|
||||
checked = self.settings.value("autocomplete_enabled", True, type=bool)
|
||||
self.autocomplete_checkbox.setChecked(checked)
|
||||
# При изменении состояния чекбокса сохраняем его
|
||||
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.search_input.textChanged.connect(self.filter_tree)
|
||||
|
||||
self.tree = QTreeWidget()
|
||||
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
|
||||
@ -64,33 +51,17 @@ class VariableSelectorDialog(QDialog):
|
||||
self.btn_delete = QPushButton("Удалить выбранные")
|
||||
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(QLabel("Поиск:"))
|
||||
layout.addWidget(self.search_input)
|
||||
layout.addWidget(self.tree)
|
||||
layout.addWidget(self.btn_add)
|
||||
layout.addWidget(self.btn_delete)
|
||||
layout.addWidget(self.btn_delete) # Кнопка удаления
|
||||
self.setLayout(layout)
|
||||
|
||||
self.populate_tree()
|
||||
|
||||
|
||||
def add_tree_item_recursively(self, parent, var):
|
||||
"""
|
||||
Рекурсивно добавляет переменную и её дочерние поля в дерево.
|
||||
@ -142,7 +113,7 @@ class VariableSelectorDialog(QDialog):
|
||||
|
||||
def filter_tree(self):
|
||||
text = self.search_input.text().strip().lower()
|
||||
path_parts = self.split_path(text) if text else []
|
||||
path_parts = text.split('.') if text else []
|
||||
|
||||
def hide_all(item):
|
||||
item.setHidden(True)
|
||||
@ -150,7 +121,7 @@ class VariableSelectorDialog(QDialog):
|
||||
hide_all(item.child(i))
|
||||
|
||||
def path_matches_search(name, search_parts):
|
||||
name_parts = self.split_path(name.lower())
|
||||
name_parts = name.lower().split('.')
|
||||
if len(name_parts) < len(search_parts):
|
||||
return False
|
||||
for sp, np in zip(search_parts, name_parts):
|
||||
@ -185,189 +156,11 @@ class VariableSelectorDialog(QDialog):
|
||||
|
||||
return matched or matched_any_child
|
||||
|
||||
# Если в поиске нет точки — особая логика для первого уровня
|
||||
if '.' not in text and '->' not in text and text != '':
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
name = item.text(0).lower()
|
||||
if text in name:
|
||||
item.setHidden(False)
|
||||
item.setExpanded(False) # НЕ раскрываем потомков
|
||||
else:
|
||||
hide_all(item)
|
||||
item.setHidden(True)
|
||||
else:
|
||||
# Обычная логика с поиском по пути
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
hide_all(item)
|
||||
show_matching_path(item, 0)
|
||||
|
||||
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]
|
||||
prefix = parts[-1].lower() if not text.endswith(('.', '>')) else ''
|
||||
|
||||
# Если путь есть (например: project.adc или project.adc.), ищем внутри него
|
||||
search_deep = len(path_parts) > 0
|
||||
|
||||
def find_path_items(path_parts):
|
||||
items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
|
||||
for part in path_parts:
|
||||
part_lower = part.lower()
|
||||
matched = []
|
||||
|
||||
for item in items:
|
||||
# Берём последний фрагмент имени item, разделённого точками
|
||||
item_name_part = self.split_path(item.text(0))[-1].lower()
|
||||
|
||||
if item_name_part == part_lower:
|
||||
matched.append(item)
|
||||
|
||||
if not matched:
|
||||
return []
|
||||
items = []
|
||||
# Собираем детей для следующего уровня поиска
|
||||
for node in matched:
|
||||
for i in range(node.childCount()):
|
||||
items.append(node.child(i))
|
||||
|
||||
return matched
|
||||
|
||||
if not search_deep:
|
||||
# Без точки — ищем только в топ-уровне, фильтруя по prefix
|
||||
items = []
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
name_part = self.split_path(item.text(0))[-1].lower()
|
||||
if name_part.startswith(prefix):
|
||||
items.append(item)
|
||||
completions = [item.text(0) for item in items]
|
||||
else:
|
||||
# С точкой — углубляемся по пути и показываем имена детей
|
||||
if len(path_parts) == 0:
|
||||
items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
else:
|
||||
items = find_path_items(path_parts)
|
||||
|
||||
completions = []
|
||||
for item in items:
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
name_part = self.split_path(child.text(0))[-1].lower()
|
||||
if prefix == '' or name_part.startswith(prefix):
|
||||
completions.append(child.text(0))
|
||||
|
||||
|
||||
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
return completions
|
||||
|
||||
|
||||
|
||||
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:
|
||||
completions = self.update_completions()
|
||||
self.completer.complete()
|
||||
text = self.search_input.text().strip()
|
||||
|
||||
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('->'):
|
||||
separator = '->'
|
||||
elif rest.startswith('.'):
|
||||
separator = '.'
|
||||
|
||||
self.search_input.setText(text + separator)
|
||||
completions = self.update_completions()
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
# Иначе просто показываем подсказки
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
if completions:
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
# Функция для поиска узла с полным именем
|
||||
def find_node_by_fullname(self, 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
|
||||
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('->')):
|
||||
# Определяем разделитель по имени первого ребёнка
|
||||
child_name = node.child(0).text(0)
|
||||
if child_name.startswith(text + '->'):
|
||||
text += '->'
|
||||
else:
|
||||
text += '.'
|
||||
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
self.update_completions()
|
||||
self.completer.complete()
|
||||
else:
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
|
||||
def on_search_text_changed(self, text):
|
||||
if self.autocomplete_checkbox.isChecked():
|
||||
completions = self.update_completions(text)
|
||||
node = self.find_node_by_fullname(text)
|
||||
|
||||
should_show = False
|
||||
|
||||
if completions:
|
||||
if len(completions) > 1:
|
||||
should_show = True
|
||||
elif len(completions) == 1:
|
||||
single_node = self.find_node_by_fullname(completions[0])
|
||||
if single_node and single_node.childCount() > 0:
|
||||
should_show = True
|
||||
elif node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->')):
|
||||
should_show = True
|
||||
|
||||
if should_show:
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
self.filter_tree()
|
||||
|
||||
def on_add_clicked(self):
|
||||
self.selected_names = []
|
||||
@ -413,63 +206,16 @@ class VariableSelectorDialog(QDialog):
|
||||
|
||||
self.accept()
|
||||
|
||||
|
||||
def on_delete_clicked(self):
|
||||
selected_names = self._get_selected_var_names()
|
||||
if not selected_names:
|
||||
return
|
||||
|
||||
# Обновляем var_map и all_vars
|
||||
for name in selected_names:
|
||||
# Деактивируем (удаляем из видимых) выбранные переменные
|
||||
for item in self.tree.selectedItems():
|
||||
name = item.text(0)
|
||||
if not name:
|
||||
continue
|
||||
if name in self.var_map:
|
||||
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
|
||||
|
||||
# Проверка пути к XML
|
||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
|
||||
return
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
tree = ET.parse(self.xml_path)
|
||||
root = tree.getroot()
|
||||
if root is None:
|
||||
return
|
||||
|
||||
vars_section = root.find('variables')
|
||||
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:
|
||||
def set_text(tag, value):
|
||||
el = var_elem.find(tag)
|
||||
if el is None:
|
||||
el = ET.SubElement(var_elem, tag)
|
||||
el.text = value
|
||||
set_text('show_var', 'false')
|
||||
set_text('enable', 'false')
|
||||
|
||||
# Преобразуем дерево в строку
|
||||
rough_string = ET.tostring(root, encoding="utf-8")
|
||||
|
||||
# Парсим и форматируем с отступами
|
||||
reparsed = minidom.parseString(rough_string)
|
||||
pretty_xml = reparsed.toprettyxml(indent=" ")
|
||||
|
||||
# Записываем в файл
|
||||
with open(self.xml_path, "w", encoding="utf-8") as f:
|
||||
f.write(pretty_xml)
|
||||
|
||||
self.populate_tree()
|
||||
var = self.var_map[name]
|
||||
var['show_var'] = 'false'
|
||||
var['enable'] = 'false'
|
||||
self.accept()
|
||||
|
||||
|
||||
@ -485,11 +231,16 @@ class VariableSelectorDialog(QDialog):
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def delete_selected_vars(self):
|
||||
selected_names = self._get_selected_var_names()
|
||||
if not selected_names:
|
||||
return
|
||||
# Деактивируем (удаляем из видимых) выбранные переменные
|
||||
for item in self.tree.selectedItems():
|
||||
name = item.text(0)
|
||||
if not name:
|
||||
continue
|
||||
if name in self.var_map:
|
||||
var = self.var_map[name]
|
||||
var['show_var'] = 'false'
|
||||
var['enable'] = 'false'
|
||||
|
||||
# Проверка пути к XML
|
||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.")
|
||||
@ -498,47 +249,31 @@ class VariableSelectorDialog(QDialog):
|
||||
import xml.etree.ElementTree as ET
|
||||
tree = ET.parse(self.xml_path)
|
||||
root = tree.getroot()
|
||||
|
||||
if root is None:
|
||||
return
|
||||
|
||||
vars_section = root.find('variables')
|
||||
if vars_section is None:
|
||||
return
|
||||
return # Нет секции variables — ничего удалять
|
||||
|
||||
selected_names = [item.text(0) for item in self.tree.selectedItems() if item.text(0)]
|
||||
|
||||
removed_any = False
|
||||
for var_elem in list(vars_section.findall('var')):
|
||||
for var_elem in vars_section.findall('var'):
|
||||
name = var_elem.attrib.get('name')
|
||||
if name in selected_names:
|
||||
vars_section.remove(var_elem)
|
||||
removed_any = True
|
||||
self.var_map.pop(name, None)
|
||||
if name in self.var_map:
|
||||
del self.var_map[name]
|
||||
# Удаляем элементы из списка на месте
|
||||
self.all_vars[:] = [v for v in self.all_vars if v['name'] != name]
|
||||
|
||||
# Удаляем из all_vars (глобально)
|
||||
self.all_vars[:] = [v for v in self.all_vars if v['name'] not in selected_names]
|
||||
|
||||
if removed_any:
|
||||
# Преобразуем дерево в строку
|
||||
rough_string = ET.tostring(root, encoding="utf-8")
|
||||
ET.indent(tree, space=" ", level=0)
|
||||
tree.write(self.xml_path, encoding='utf-8', xml_declaration=True)
|
||||
|
||||
# Парсим и форматируем с отступами
|
||||
reparsed = minidom.parseString(rough_string)
|
||||
pretty_xml = reparsed.toprettyxml(indent=" ")
|
||||
|
||||
# Записываем в файл
|
||||
with open(self.xml_path, "w", encoding="utf-8") as f:
|
||||
f.write(pretty_xml)
|
||||
|
||||
self.populate_tree()
|
||||
self.filter_tree()
|
||||
|
||||
def _get_selected_var_names(self):
|
||||
return [item.text(0) for item in self.tree.selectedItems() if item.text(0)]
|
||||
|
||||
|
||||
def save_checkbox_state(self):
|
||||
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
|
||||
|
||||
|
||||
def split_path(self, path):
|
||||
# Разбиваем по точке или по -> (учитываем, что -> длиной 2 символа)
|
||||
return re.split(r'\.|->', path)
|
@ -1,184 +0,0 @@
|
||||
from PySide6.QtWidgets import (
|
||||
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
|
||||
QAbstractItemView, QHeaderView
|
||||
)
|
||||
from PySide6.QtCore import Qt
|
||||
from enum import IntEnum
|
||||
from generateVars import type_map
|
||||
|
||||
class rows(IntEnum):
|
||||
No = 0
|
||||
include = 1
|
||||
name = 2
|
||||
type = 3
|
||||
pt_type = 4
|
||||
iq_type = 5
|
||||
ret_type = 6
|
||||
short_name = 7
|
||||
|
||||
|
||||
class VariableTableWidget(QTableWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(0, 8, parent)
|
||||
# Таблица переменных
|
||||
self.setHorizontalHeaderLabels([
|
||||
'№', # новый столбец
|
||||
'En',
|
||||
'Name',
|
||||
'Origin Type',
|
||||
'Pointer Type',
|
||||
'IQ Type',
|
||||
'Return Type',
|
||||
'Short Name'
|
||||
])
|
||||
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||||
|
||||
|
||||
self.type_options = list(dict.fromkeys(type_map.values()))
|
||||
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
|
||||
self.iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
|
||||
|
||||
header = self.horizontalHeader()
|
||||
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
|
||||
|
||||
for col in range(self.columnCount()):
|
||||
if col == self.columnCount() - 1:
|
||||
header.setSectionResizeMode(col, QHeaderView.Stretch)
|
||||
else:
|
||||
header.setSectionResizeMode(col, QHeaderView.Interactive)
|
||||
|
||||
parent_widget = self.parentWidget()
|
||||
# Сделаем колонки с номерами фиксированной ширины
|
||||
self.setColumnWidth(rows.No, 30)
|
||||
self.setColumnWidth(rows.include, 30)
|
||||
self.setColumnWidth(rows.pt_type, 85)
|
||||
self.setColumnWidth(rows.iq_type, 85)
|
||||
self.setColumnWidth(rows.ret_type, 85)
|
||||
|
||||
self.setColumnWidth(rows.name, 300)
|
||||
self.setColumnWidth(rows.type, 100)
|
||||
self._resizing = False
|
||||
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
|
||||
|
||||
|
||||
def populate(self, vars_list, structs, on_change_callback):
|
||||
self.type_options = list(dict.fromkeys(type_map.values()))
|
||||
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
|
||||
iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
|
||||
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
|
||||
self.setRowCount(len(filtered_vars))
|
||||
self.verticalHeader().setVisible(False)
|
||||
|
||||
for row, var in enumerate(filtered_vars):
|
||||
# №
|
||||
no_item = QTableWidgetItem(str(row))
|
||||
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.setItem(row, rows.No, no_item)
|
||||
|
||||
# Enable
|
||||
cb = QCheckBox()
|
||||
cb.setChecked(var.get('enable', 'false') == 'true')
|
||||
cb.stateChanged.connect(on_change_callback)
|
||||
self.setCellWidget(row, rows.include, cb)
|
||||
|
||||
# Name
|
||||
name_edit = QLineEdit(var['name'])
|
||||
if var['type'] in structs:
|
||||
completer = QCompleter(structs[var['type']].keys())
|
||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
name_edit.setCompleter(completer)
|
||||
name_edit.textChanged.connect(on_change_callback)
|
||||
self.setCellWidget(row, rows.name, name_edit)
|
||||
|
||||
# Origin Type (readonly)
|
||||
origin_item = QTableWidgetItem(var.get('type', ''))
|
||||
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.setItem(row, rows.type, origin_item)
|
||||
|
||||
# pt_type
|
||||
pt_combo = QComboBox()
|
||||
pt_combo.addItems(self.display_type_options)
|
||||
value = var['pt_type'].replace('pt_', '')
|
||||
if value not in self.display_type_options:
|
||||
pt_combo.addItem(value)
|
||||
pt_combo.setCurrentText(value)
|
||||
pt_combo.currentTextChanged.connect(on_change_callback)
|
||||
self.setCellWidget(row, rows.pt_type, pt_combo)
|
||||
|
||||
# iq_type
|
||||
iq_combo = QComboBox()
|
||||
iq_combo.addItems(self.iq_types)
|
||||
value = var['iq_type'].replace('t_', '')
|
||||
if value not in self.iq_types:
|
||||
iq_combo.addItem(value)
|
||||
iq_combo.setCurrentText(value)
|
||||
iq_combo.currentTextChanged.connect(on_change_callback)
|
||||
self.setCellWidget(row, rows.iq_type, iq_combo)
|
||||
|
||||
# return_type
|
||||
ret_combo = QComboBox()
|
||||
ret_combo.addItems(self.iq_types)
|
||||
ret_combo.setCurrentText(var.get('return_type', ''))
|
||||
ret_combo.currentTextChanged.connect(on_change_callback)
|
||||
self.setCellWidget(row, rows.ret_type, ret_combo)
|
||||
|
||||
# short_name
|
||||
short_name_edit = QLineEdit(var.get('shortname', var['name']))
|
||||
short_name_edit.textChanged.connect(on_change_callback)
|
||||
self.setCellWidget(row, rows.short_name, short_name_edit)
|
||||
|
||||
|
||||
|
||||
def read_data(self):
|
||||
result = []
|
||||
for row in range(self.rowCount()):
|
||||
cb = self.cellWidget(row, rows.include)
|
||||
name = self.cellWidget(row, rows.name).text()
|
||||
pt = self.cellWidget(row, rows.pt_type).currentText()
|
||||
iq = self.cellWidget(row, rows.iq_type).currentText()
|
||||
ret = self.cellWidget(row, rows.ret_type).currentText()
|
||||
shortname = self.cellWidget(row, rows.short_name).text()
|
||||
origin_type = self.item(row, rows.type).text()
|
||||
|
||||
result.append({
|
||||
'show_var': True,
|
||||
'enable': cb.isChecked(),
|
||||
'name': name,
|
||||
'pt_type': f'pt_{pt}',
|
||||
'iq_type': iq,
|
||||
'return_type': ret,
|
||||
'shortname': shortname,
|
||||
'type': origin_type,
|
||||
})
|
||||
return result
|
||||
|
||||
def on_section_resized(self, logicalIndex, oldSize, newSize):
|
||||
if self._resizing:
|
||||
return # предотвращаем рекурсию
|
||||
|
||||
min_width = 50
|
||||
delta = newSize - oldSize
|
||||
right_index = logicalIndex + 1
|
||||
|
||||
if right_index >= self.columnCount():
|
||||
# Если правая колока - нет соседа, ограничиваем минимальную ширину
|
||||
if newSize < min_width:
|
||||
self._resizing = True
|
||||
self.setColumnWidth(logicalIndex, min_width)
|
||||
self._resizing = False
|
||||
return
|
||||
|
||||
self._resizing = True
|
||||
try:
|
||||
right_width = self.columnWidth(right_index)
|
||||
new_right_width = right_width - delta
|
||||
|
||||
# Если соседняя колонка станет уже минимальной - подкорректируем левую
|
||||
if new_right_width < min_width:
|
||||
new_right_width = min_width
|
||||
newSize = oldSize + (right_width - min_width)
|
||||
self.setColumnWidth(logicalIndex, newSize)
|
||||
|
||||
self.setColumnWidth(right_index, new_right_width)
|
||||
finally:
|
||||
self._resizing = False
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -8,7 +8,6 @@ import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from pathlib import Path
|
||||
from xml.dom import minidom
|
||||
import argparse
|
||||
|
||||
|
||||
@ -215,16 +214,8 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
|
||||
added_count += 1
|
||||
|
||||
if added_count > 0:
|
||||
# Преобразуем дерево в строку
|
||||
rough_string = ET.tostring(root, encoding="utf-8")
|
||||
|
||||
# Парсим и форматируем с отступами
|
||||
reparsed = minidom.parseString(rough_string)
|
||||
pretty_xml = reparsed.toprettyxml(indent=" ")
|
||||
|
||||
# Записываем в файл
|
||||
with open(xml_full_path, "w", encoding="utf-8") as f:
|
||||
f.write(pretty_xml)
|
||||
ET.indent(tree, space=" ", level=0)
|
||||
tree.write(xml_full_path, encoding="utf-8", xml_declaration=True)
|
||||
print(f"[INFO] В XML добавлено новых переменных: {added_count}")
|
||||
return True
|
||||
else:
|
||||
@ -339,13 +330,12 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
||||
|
||||
# Дополнительные поля, например комментарий
|
||||
comment = info.get("comment", "")
|
||||
short_name = info.get("shortname", f'"{vname}"')
|
||||
|
||||
if pt_type not in ('pt_struct', 'pt_union'):
|
||||
formated_name = f'"{vname}"'
|
||||
# Добавим комментарий после записи, если он есть
|
||||
comment_str = f' // {comment}' if comment else ''
|
||||
line = f'{{(char *)&{vname:<41} , {pt_type:<21} , {iq_type:<21} , {short_name:<42}}}, \\{comment_str}'
|
||||
line = f'{{(char *)&{vname:<41} , {pt_type:<21} , {iq_type:<21} , {formated_name:<42}}}, \\{comment_str}'
|
||||
new_debug_vars[vname] = line
|
||||
|
||||
else:
|
||||
|
@ -7,14 +7,11 @@ def strip_single_line_comments(code):
|
||||
return re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
|
||||
|
||||
def read_file_try_encodings(filepath):
|
||||
if not os.path.isfile(filepath):
|
||||
# Файл не существует — просто вернуть пустую строку или None
|
||||
return "", None
|
||||
for enc in ['utf-8', 'cp1251']:
|
||||
try:
|
||||
with open(filepath, 'r', encoding=enc) as f:
|
||||
content = f.read()
|
||||
content = strip_single_line_comments(content)
|
||||
content = strip_single_line_comments(content) # <=== ВАЖНО
|
||||
return content, enc
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
@ -41,8 +38,6 @@ def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
|
||||
processed_files.add(norm_path)
|
||||
|
||||
content, _ = read_file_try_encodings(cfile)
|
||||
if content is None:
|
||||
continue
|
||||
includes = include_pattern.findall(content)
|
||||
for inc in includes:
|
||||
# Ищем полный путь к include-файлу в include_dirs
|
||||
@ -66,9 +61,9 @@ def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
|
||||
return include_files
|
||||
|
||||
|
||||
def parse_makefile(makefile_path, proj_path):
|
||||
def parse_makefile(makefile_path):
|
||||
makefile_dir = os.path.dirname(makefile_path)
|
||||
project_root = proj_path
|
||||
project_root = os.path.dirname(makefile_dir) # поднялись из Debug
|
||||
|
||||
with open(makefile_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
@ -120,8 +115,6 @@ def parse_makefile(makefile_path, proj_path):
|
||||
continue
|
||||
if "v120" in obj_path:
|
||||
continue
|
||||
if "v100" in obj_path:
|
||||
continue
|
||||
|
||||
if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"):
|
||||
rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/")
|
||||
@ -136,10 +129,6 @@ def parse_makefile(makefile_path, proj_path):
|
||||
else:
|
||||
c_path = abs_path
|
||||
|
||||
# Проверяем существование файла, если нет — пропускаем
|
||||
if not os.path.isfile(c_path):
|
||||
continue
|
||||
|
||||
# Сохраняем только .c файлы
|
||||
if c_path.lower().endswith(".c"):
|
||||
c_files.append(c_path)
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -812,7 +812,7 @@ Usage example:
|
||||
print(f"Error: Makefile path '{makefile_path}' does not exist.")
|
||||
sys.exit(1)
|
||||
|
||||
c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path)
|
||||
c_files, h_files, include_dirs = parse_makefile(makefile_path)
|
||||
|
||||
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs)
|
||||
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs)
|
||||
@ -855,7 +855,7 @@ def run_scan(proj_path, makefile_path, output_xml, verbose=2):
|
||||
if not os.path.isfile(makefile_path):
|
||||
raise FileNotFoundError(f"Makefile path '{makefile_path}' does not exist.")
|
||||
|
||||
c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path)
|
||||
c_files, h_files, include_dirs = parse_makefile(makefile_path)
|
||||
|
||||
vars, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs)
|
||||
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs)
|
||||
|
@ -63,21 +63,6 @@ def parse_vars(filename, typedef_map=None):
|
||||
iq_type = var.findtext('iq_type')
|
||||
if not iq_type:
|
||||
iq_type = get_iq_define(var_type)
|
||||
# Записываем iq_type в XML
|
||||
iq_type_elem = var.find('iq_type')
|
||||
if iq_type_elem is None:
|
||||
iq_type_elem = ET.SubElement(var, 'iq_type')
|
||||
iq_type_elem.text = iq_type
|
||||
|
||||
# Вычисляем pt_type и iq_type
|
||||
pt_type = var.findtext('pt_type')
|
||||
if not pt_type:
|
||||
pt_type = map_type_to_pt(var_type, name, typedef_map)
|
||||
# Записываем pt_type в XML
|
||||
pt_type_elem = var.find('pt_type')
|
||||
if pt_type_elem is None:
|
||||
pt_type_elem = ET.SubElement(var, 'pt_type')
|
||||
pt_type_elem.text = pt_type
|
||||
|
||||
vars_list.append({
|
||||
'name': name,
|
||||
@ -93,17 +78,6 @@ def parse_vars(filename, typedef_map=None):
|
||||
'static': var.findtext('static', 'false') == 'true',
|
||||
})
|
||||
|
||||
# Преобразуем дерево в строку
|
||||
rough_string = ET.tostring(root, encoding="utf-8")
|
||||
|
||||
# Парсим и форматируем с отступами
|
||||
reparsed = minidom.parseString(rough_string)
|
||||
pretty_xml = reparsed.toprettyxml(indent=" ")
|
||||
|
||||
# Записываем в файл
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
f.write(pretty_xml)
|
||||
|
||||
return vars_list
|
||||
|
||||
|
||||
@ -185,9 +159,6 @@ def safe_parse_xml(xml_path):
|
||||
except Exception as e:
|
||||
print(f"Неожиданная ошибка при чтении XML файла '{xml_path}': {e}")
|
||||
return None, None
|
||||
|
||||
|
||||
|
||||
def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0):
|
||||
if depth > 10:
|
||||
return []
|
||||
@ -207,15 +178,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
if field_name == 'type':
|
||||
continue
|
||||
|
||||
# Определяем разделитель между prefix и полем
|
||||
if prefix.endswith('*'):
|
||||
separator = '->'
|
||||
# Для красоты можно убрать пробелы у указателя
|
||||
# например, если prefix="ptr*" -> "ptr->field"
|
||||
full_name = f"{prefix[:-1]}{separator}{field_name}"
|
||||
else:
|
||||
separator = '.'
|
||||
full_name = f"{prefix}{separator}{field_name}"
|
||||
full_name = f"{prefix}.{field_name}"
|
||||
|
||||
if isinstance(field_value, dict):
|
||||
# Если вложенная структура — берем её имя типа из поля 'type' или пустую строку
|
||||
@ -224,14 +187,10 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
'name': full_name,
|
||||
'type': type_name,
|
||||
'pt_type': '',
|
||||
'iq_type': '',
|
||||
'return_type': '',
|
||||
'file': var_attrs.get('file'),
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
}
|
||||
if '*' in type_name and not full_name.endswith('*'):
|
||||
full_name += '*'
|
||||
# Рекурсивно раскрываем вложенные поля
|
||||
subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1)
|
||||
if subchildren:
|
||||
@ -246,8 +205,6 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
'name': full_name,
|
||||
'type': field_value,
|
||||
'pt_type': '',
|
||||
'iq_type': '',
|
||||
'return_type': '',
|
||||
'file': var_attrs.get('file'),
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
@ -258,7 +215,6 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
return children
|
||||
|
||||
|
||||
|
||||
def expand_vars(vars_list, structs, typedefs):
|
||||
"""
|
||||
Раскрывает структуры и массивы структур в деревья.
|
||||
@ -272,11 +228,6 @@ def expand_vars(vars_list, structs, typedefs):
|
||||
|
||||
fields = structs.get(base_type)
|
||||
|
||||
if pt_type.startswith('pt_ptr_') and isinstance(fields, dict):
|
||||
new_var = var.copy()
|
||||
new_var['children'] = expand_struct_recursively(var['name']+'*', raw_type, structs, typedefs, var)
|
||||
expanded.append(new_var)
|
||||
|
||||
if pt_type.startswith('pt_arr_') and isinstance(fields, dict):
|
||||
new_var = var.copy()
|
||||
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
|
||||
|
@ -12,18 +12,27 @@ from scanVars import run_scan
|
||||
from generateVars import run_generate
|
||||
from setupVars import *
|
||||
from VariableSelector import *
|
||||
from VariableTable import VariableTableWidget, rows
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||||
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
|
||||
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView
|
||||
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy
|
||||
)
|
||||
from PySide6.QtGui import QTextCursor, QKeyEvent
|
||||
from PySide6.QtCore import Qt, QProcess, QObject, Signal, QSettings
|
||||
from PySide6.QtCore import Qt, QProcess, QObject, Signal, QTimer
|
||||
|
||||
|
||||
class rows(IntEnum):
|
||||
No = 0
|
||||
include = 1
|
||||
name = 2
|
||||
type = 3
|
||||
pt_type = 4
|
||||
iq_type = 5
|
||||
ret_type = 6
|
||||
short_name = 7
|
||||
|
||||
|
||||
class EmittingStream(QObject):
|
||||
text_written = Signal(str)
|
||||
@ -45,12 +54,11 @@ class EmittingStream(QObject):
|
||||
self._buffer = ""
|
||||
|
||||
|
||||
class ProcessOutputWindowDummy(QDialog):
|
||||
class ProcessOutputWindowDummy(QWidget):
|
||||
def __init__(self, on_done_callback):
|
||||
super().__init__()
|
||||
self.setWindowTitle("Поиск переменных...")
|
||||
self.resize(600, 400)
|
||||
self.setModal(True) # сделаем окно модальным
|
||||
|
||||
self.layout = QVBoxLayout(self)
|
||||
self.output_edit = QTextEdit()
|
||||
@ -67,7 +75,7 @@ class ProcessOutputWindowDummy(QDialog):
|
||||
def __handle_done(self):
|
||||
if self._on_done_callback:
|
||||
self._on_done_callback()
|
||||
self.accept() # закрыть диалог
|
||||
self.close()
|
||||
|
||||
def append_text(self, text):
|
||||
cursor = self.output_edit.textCursor()
|
||||
@ -153,6 +161,20 @@ class VarEditor(QWidget):
|
||||
self.btn_update_vars = QPushButton("Обновить данные о переменных")
|
||||
self.btn_update_vars.clicked.connect(self.update_vars_data)
|
||||
|
||||
# Таблица переменных
|
||||
self.table = QTableWidget(len(self.vars_list), 8)
|
||||
self.table.setHorizontalHeaderLabels([
|
||||
'№', # новый столбец
|
||||
'En',
|
||||
'Name',
|
||||
'Origin Type',
|
||||
'Pointer Type',
|
||||
'IQ Type',
|
||||
'Return Type',
|
||||
'Short Name'
|
||||
])
|
||||
self.table.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||||
|
||||
# Кнопка сохранения
|
||||
btn_save = QPushButton("Build")
|
||||
btn_save.clicked.connect(self.save_build)
|
||||
@ -161,11 +183,7 @@ class VarEditor(QWidget):
|
||||
self.btn_add_vars = QPushButton("Add Variables")
|
||||
self.btn_add_vars.clicked.connect(self.__open_variable_selector)
|
||||
|
||||
# Кнопка открыть output-файл с выбором программы
|
||||
btn_open_output = QPushButton("Открыть Output File...")
|
||||
btn_open_output.clicked.connect(self.__open_output_file_with_program)
|
||||
# Таблица
|
||||
self.table = VariableTableWidget()
|
||||
|
||||
# Основной layout
|
||||
layout = QVBoxLayout()
|
||||
layout.addLayout(xml_layout)
|
||||
@ -176,11 +194,69 @@ class VarEditor(QWidget):
|
||||
layout.addWidget(self.btn_add_vars)
|
||||
layout.addLayout(source_output_layout)
|
||||
layout.addWidget(btn_save)
|
||||
layout.addWidget(btn_open_output)
|
||||
|
||||
|
||||
header = self.table.horizontalHeader()
|
||||
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
|
||||
|
||||
for col in range(self.table.columnCount()):
|
||||
if col == self.table.columnCount() - 1:
|
||||
header.setSectionResizeMode(col, QHeaderView.Stretch)
|
||||
else:
|
||||
header.setSectionResizeMode(col, QHeaderView.Interactive)
|
||||
|
||||
parent_widget = self.table.parentWidget()
|
||||
if parent_widget:
|
||||
w = parent_widget.width()
|
||||
h = parent_widget.height()
|
||||
viewport_width = self.table.viewport().width()
|
||||
# Сделаем колонки с номерами фиксированной ширины
|
||||
self.table.setColumnWidth(rows.No, 30)
|
||||
self.table.setColumnWidth(rows.include, 30)
|
||||
self.table.setColumnWidth(rows.pt_type, 85)
|
||||
self.table.setColumnWidth(rows.iq_type, 85)
|
||||
self.table.setColumnWidth(rows.ret_type, 85)
|
||||
|
||||
self.table.setColumnWidth(rows.name, 300)
|
||||
self.table.setColumnWidth(rows.type, 100)
|
||||
|
||||
self.table.horizontalHeader().sectionResized.connect(self.on_section_resized)
|
||||
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
def on_section_resized(self, logicalIndex, oldSize, newSize):
|
||||
if self._resizing:
|
||||
return # предотвращаем рекурсию
|
||||
|
||||
min_width = 50
|
||||
delta = newSize - oldSize
|
||||
right_index = logicalIndex + 1
|
||||
|
||||
if right_index >= self.table.columnCount():
|
||||
# Если правая колока - нет соседа, ограничиваем минимальную ширину
|
||||
if newSize < min_width:
|
||||
self._resizing = True
|
||||
self.table.setColumnWidth(logicalIndex, min_width)
|
||||
self._resizing = False
|
||||
return
|
||||
|
||||
self._resizing = True
|
||||
try:
|
||||
right_width = self.table.columnWidth(right_index)
|
||||
new_right_width = right_width - delta
|
||||
|
||||
# Если соседняя колонка станет уже минимальной - подкорректируем левую
|
||||
if new_right_width < min_width:
|
||||
new_right_width = min_width
|
||||
newSize = oldSize + (right_width - min_width)
|
||||
self.table.setColumnWidth(logicalIndex, newSize)
|
||||
|
||||
self.table.setColumnWidth(right_index, new_right_width)
|
||||
finally:
|
||||
self._resizing = False
|
||||
|
||||
|
||||
|
||||
def get_xml_path(self):
|
||||
xml_path = self.xml_output_edit.text().strip()
|
||||
@ -340,6 +416,7 @@ class VarEditor(QWidget):
|
||||
else:
|
||||
self.makefile_path = None
|
||||
|
||||
if not self.structs_path:
|
||||
# --- structs_path из атрибута ---
|
||||
structs_path = root.attrib.get('structs_path', '').strip()
|
||||
structs_path_full = make_absolute_path(structs_path, self.proj_path)
|
||||
@ -350,7 +427,7 @@ class VarEditor(QWidget):
|
||||
self.structs_path = None
|
||||
|
||||
self.vars_list = parse_vars(self.xml_path, self.typedef_map)
|
||||
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
|
||||
self.update_table()
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
|
||||
|
||||
@ -451,7 +528,7 @@ class VarEditor(QWidget):
|
||||
v['show_var'] = 'false'
|
||||
break
|
||||
|
||||
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
|
||||
self.update_table()
|
||||
|
||||
|
||||
def __open_variable_selector(self):
|
||||
@ -465,6 +542,102 @@ class VarEditor(QWidget):
|
||||
self.update()
|
||||
|
||||
|
||||
|
||||
def update_table(self):
|
||||
self.type_options = list(dict.fromkeys(type_map.values()))
|
||||
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
|
||||
iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
|
||||
filtered_vars = [v for v in self.vars_list if v.get('show_var', 'false') == 'true']
|
||||
self.table.setRowCount(len(filtered_vars))
|
||||
self.table.verticalHeader().setVisible(False)
|
||||
|
||||
for row, var in enumerate(filtered_vars):
|
||||
# Добавляем номер строки в колонку No (0)
|
||||
no_item = QTableWidgetItem(str(row))
|
||||
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # readonly
|
||||
self.table.setItem(row, rows.No, no_item)
|
||||
|
||||
cb = QCheckBox()
|
||||
enable_str = var.get('enable', 'false')
|
||||
cb.setChecked(enable_str.lower() == 'true')
|
||||
self.table.setCellWidget(row, rows.include, cb)
|
||||
|
||||
name_edit = QLineEdit(var['name'])
|
||||
if var['type'] in self.structs:
|
||||
completer = QCompleter(self.structs[var['type']].keys())
|
||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
name_edit.setCompleter(completer)
|
||||
self.table.setCellWidget(row, rows.name, name_edit)
|
||||
|
||||
# Type (origin)
|
||||
origin_type = var.get('type', '').strip()
|
||||
origin_item = QTableWidgetItem(origin_type)
|
||||
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled) # read-only
|
||||
self.table.setItem(row, rows.type, origin_item)
|
||||
|
||||
pt_type_combo = QComboBox()
|
||||
pt_type_combo.addItems(self.display_type_options)
|
||||
internal_type = var['pt_type'].replace('pt_', '')
|
||||
if internal_type in self.display_type_options:
|
||||
pt_type_combo.setCurrentText(internal_type)
|
||||
else:
|
||||
pt_type_combo.addItem(internal_type)
|
||||
pt_type_combo.setCurrentText(internal_type)
|
||||
self.table.setCellWidget(row, rows.pt_type, pt_type_combo)
|
||||
|
||||
iq_combo = QComboBox()
|
||||
iq_combo.addItems(iq_types)
|
||||
iq_type = var['iq_type'].replace('t_', '')
|
||||
if iq_type in iq_types:
|
||||
iq_combo.setCurrentText(iq_type)
|
||||
else:
|
||||
iq_combo.addItem(iq_type)
|
||||
iq_combo.setCurrentText(iq_type)
|
||||
self.table.setCellWidget(row, rows.iq_type, iq_combo)
|
||||
|
||||
ret_combo = QComboBox()
|
||||
ret_combo.addItems(iq_types)
|
||||
self.table.setCellWidget(row, rows.ret_type, ret_combo)
|
||||
|
||||
short_name_edit = QLineEdit(var['name'])
|
||||
self.table.setCellWidget(row, rows.short_name, short_name_edit)
|
||||
|
||||
cb.stateChanged.connect(self.write_to_xml)
|
||||
name_edit.textChanged.connect(self.write_to_xml)
|
||||
pt_type_combo.currentTextChanged.connect(self.write_to_xml)
|
||||
iq_combo.currentTextChanged.connect(self.write_to_xml)
|
||||
ret_combo.currentTextChanged.connect(self.write_to_xml)
|
||||
short_name_edit.textChanged.connect(self.write_to_xml)
|
||||
|
||||
|
||||
self.write_to_xml()
|
||||
|
||||
|
||||
def read_table(self):
|
||||
vars_data = []
|
||||
for row in range(self.table.rowCount()):
|
||||
cb = self.table.cellWidget(row, rows.include)
|
||||
name_edit = self.table.cellWidget(row, rows.name)
|
||||
pt_type_combo = self.table.cellWidget(row, rows.pt_type)
|
||||
iq_combo = self.table.cellWidget(row, rows.iq_type)
|
||||
ret_combo = self.table.cellWidget(row, rows.ret_type)
|
||||
short_name_edit = self.table.cellWidget(row, rows.short_name)
|
||||
origin_item = self.table.item(row, rows.type)
|
||||
|
||||
vars_data.append({
|
||||
'show_var': True,
|
||||
'enable': cb.isChecked() if cb else False,
|
||||
'name': name_edit.text() if name_edit else '',
|
||||
'pt_type': 'pt_' + pt_type_combo.currentText() if pt_type_combo else '',
|
||||
'iq_type': iq_combo.currentText() if iq_combo else '',
|
||||
'return_type': ret_combo.currentText() if ret_combo else '',
|
||||
'shortname': short_name_edit.text() if short_name_edit else '',
|
||||
'type': origin_item.text() if origin_item else '',
|
||||
})
|
||||
return vars_data
|
||||
|
||||
|
||||
|
||||
def write_to_xml(self):
|
||||
self.update_all_paths()
|
||||
|
||||
@ -509,7 +682,7 @@ class VarEditor(QWidget):
|
||||
}
|
||||
|
||||
# Читаем переменные из таблицы (активные/изменённые)
|
||||
table_vars = {v['name']: v for v in self.table.read_data()}
|
||||
table_vars = {v['name']: v for v in self.read_table()}
|
||||
# Все переменные (в том числе новые, которых нет в таблице)
|
||||
all_vars_by_name = {v['name']: v for v in self.vars_list}
|
||||
|
||||
@ -534,11 +707,8 @@ class VarEditor(QWidget):
|
||||
el = ET.SubElement(parent, tag)
|
||||
el.text = str(text)
|
||||
|
||||
show_var_val = str(v.get('show_var', 'false')).lower()
|
||||
enable_val = str(v_table['enable'] if v_table and 'enable' in v_table else v.get('enable', 'false')).lower()
|
||||
|
||||
set_sub_elem_text(var_elem, 'show_var', show_var_val)
|
||||
set_sub_elem_text(var_elem, 'enable', enable_val)
|
||||
set_sub_elem_text(var_elem, 'show_var', v.get('show_var', 'false'))
|
||||
set_sub_elem_text(var_elem, 'enable', v.get('enable', 'false'))
|
||||
|
||||
# Тут подтягиваем из таблицы, если есть, иначе из v
|
||||
shortname_val = v_table['shortname'] if v_table and 'shortname' in v_table else v.get('shortname', '')
|
||||
@ -561,40 +731,13 @@ class VarEditor(QWidget):
|
||||
set_sub_elem_text(var_elem, 'extern', extern_val)
|
||||
set_sub_elem_text(var_elem, 'static', static_val)
|
||||
|
||||
# Преобразуем дерево в строку
|
||||
rough_string = ET.tostring(root, encoding="utf-8")
|
||||
|
||||
# Парсим и форматируем с отступами
|
||||
reparsed = minidom.parseString(rough_string)
|
||||
pretty_xml = reparsed.toprettyxml(indent=" ")
|
||||
|
||||
# Записываем в файл
|
||||
with open(self.xml_path, "w", encoding="utf-8") as f:
|
||||
f.write(pretty_xml)
|
||||
ET.indent(tree, space=" ", level=0)
|
||||
tree.write(self.xml_path, encoding='utf-8', xml_declaration=True)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка при сохранении XML: {e}")
|
||||
|
||||
def __open_output_file_with_program(self):
|
||||
output_path = self.get_output_path()
|
||||
if not output_path:
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к output-файлу не задан.")
|
||||
return
|
||||
|
||||
output_file = os.path.join(output_path, "debug_vars.c")
|
||||
if not os.path.isfile(output_file):
|
||||
QMessageBox.warning(self, "Ошибка", f"Файл не найден:\n{output_file}")
|
||||
return
|
||||
|
||||
try:
|
||||
# Открыть стандартное окно Windows "Открыть с помощью..."
|
||||
subprocess.run([
|
||||
"rundll32.exe",
|
||||
"shell32.dll,OpenAs_RunDLL",
|
||||
output_file
|
||||
], shell=False)
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Ошибка", f"Не удалось открыть диалог 'Открыть с помощью':\n{e}")
|
||||
|
||||
|
||||
|
||||
|
@ -1,119 +1,34 @@
|
||||
import subprocess
|
||||
import shutil
|
||||
import os
|
||||
import PySide6
|
||||
|
||||
USE_NUITKA = True # переключатель: True — сборка через Nuitka, False — через PyInstaller
|
||||
|
||||
pyside6_path = os.path.dirname(PySide6.__file__)
|
||||
plugins_platforms_path = os.path.join(pyside6_path, "plugins", "platforms")
|
||||
|
||||
dist_path = os.path.abspath(".") # итоговая папка с exe
|
||||
work_path = os.path.abspath("./build_temp") # временная папка для PyInstaller
|
||||
spec_path = os.path.abspath("./build_temp") # папка exдля spec-файлов PyInstaller
|
||||
# Пути
|
||||
dist_path = os.path.abspath("./") # текущая папка — exe будет тут
|
||||
work_path = os.path.abspath("./build_temp")
|
||||
spec_path = os.path.abspath("./build_temp")
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
libclang_path = os.path.join(script_dir, "libclang.dll")
|
||||
libssl_orig = os.path.join(script_dir, "libssl-3-x64.dll")
|
||||
libcrypto_orig = os.path.join(script_dir, "libcrypto-3-x64.dll")
|
||||
|
||||
# Собираем бинарники и данные для PyInstaller
|
||||
add_binary_list = [f"{libclang_path};."]
|
||||
|
||||
if os.path.exists(libssl_orig):
|
||||
add_binary_list.append(f"{libssl_orig};libssl.dll")
|
||||
else:
|
||||
print("WARNING: libssl-3-x64.dll не найден, OpenSSL не будет включён")
|
||||
|
||||
if os.path.exists(libcrypto_orig):
|
||||
add_binary_list.append(f"{libcrypto_orig};libcrypto.dll")
|
||||
else:
|
||||
print("WARNING: libcrypto-3-x64.dll не найден, OpenSSL не будет включён")
|
||||
|
||||
from PyInstaller.utils.hooks import collect_data_files
|
||||
datas = []
|
||||
datas += collect_data_files('PySide6', includes=['plugins/platforms/*'])
|
||||
datas += collect_data_files('PySide6', includes=['plugins/styles/*'])
|
||||
datas += collect_data_files('PySide6', includes=['plugins/imageformats/*'])
|
||||
add_data_list = [f"{src};{dest}" for src, dest in datas]
|
||||
|
||||
script_path = "./Src/setupVars_GUI.py"
|
||||
output_name = "DebugVarEdit"
|
||||
|
||||
env = os.environ.copy()
|
||||
env["CC"] = r"C:\Users\I\AppData\Local\Nuitka\Nuitka\Cache\DOWNLOADS\gcc\x86_64\14.2.0posix-19.1.1-12.0.0-msvcrt-r2\bin\gcc.exe"
|
||||
env["CXX"] = r"C:\Users\I\AppData\Local\Nuitka\Nuitka\Cache\DOWNLOADS\gcc\x86_64\14.2.0posix-19.1.1-12.0.0-msvcrt-r2\bin\g++.exe"
|
||||
|
||||
if not USE_NUITKA:
|
||||
# PyInstaller команда
|
||||
# Запуск PyInstaller с нужными параметрами
|
||||
cmd = [
|
||||
"pyinstaller",
|
||||
"--onefile",
|
||||
"--name", output_name,
|
||||
*[item for add_bin in add_binary_list for item in ("--add-binary", add_bin)],
|
||||
*[item for add_dat in add_data_list for item in ("--add-data", add_dat)],
|
||||
"--windowed",
|
||||
"--name", "DebugVarEdit",
|
||||
"--add-binary", f"{libclang_path};.",
|
||||
"--distpath", dist_path,
|
||||
"--workpath", work_path,
|
||||
"--specpath", spec_path,
|
||||
"--hidden-import", "PySide6.QtWidgets",
|
||||
"--hidden-import", "PySide6.QtGui",
|
||||
"--hidden-import", "PySide6.QtCore",
|
||||
"--windowed",
|
||||
script_path
|
||||
]
|
||||
else:
|
||||
# Nuitka команда
|
||||
|
||||
|
||||
# Формируем --include-data-dir для плагинов PySide6 (datas как в PyInstaller)
|
||||
# Было — неправильный способ: передаёт файлы
|
||||
plugin_dirs = set()
|
||||
for src, dest in datas:
|
||||
src_dir = os.path.dirname(src)
|
||||
dest_root = dest.split("/", 1)[0] if "/" in dest else dest
|
||||
plugin_dirs.add((src_dir, dest_root))
|
||||
|
||||
include_data_dirs = [
|
||||
f"--include-data-dir={src}={dest}" for src, dest in plugin_dirs
|
||||
"./Src/setupVars_GUI.py"
|
||||
]
|
||||
|
||||
include_data_files = []
|
||||
for dll_path, dll_name in [(libclang_path, "libclang.dll"),
|
||||
(libssl_orig, "libssl.dll"),
|
||||
(libcrypto_orig, "libcrypto.dll")]:
|
||||
if os.path.exists(dll_path):
|
||||
include_data_files.append(f"--include-data-file={dll_path}={dll_name}")
|
||||
|
||||
|
||||
cmd = [
|
||||
"python", "-m", "nuitka",
|
||||
"--standalone", # полностью независимый билд (аналог PyInstaller --onefile/--onedir)
|
||||
"--onefile", # упаковать в один exe (экспериментально)
|
||||
"--enable-plugin=pyside6",
|
||||
"--windows-console-mode=disable",
|
||||
f"--output-dir={dist_path}",
|
||||
f"--output-filename={output_name}.exe",
|
||||
] + include_data_dirs + [
|
||||
script_path
|
||||
]
|
||||
|
||||
# Удаляем временные папки, если они у тебя есть
|
||||
temp_dirs = ["build_temp", "__pycache__"] # добавь свои папки с временными файлами
|
||||
for d in temp_dirs:
|
||||
if os.path.exists(d):
|
||||
shutil.rmtree(d)
|
||||
|
||||
print("Выполняется сборка:")
|
||||
print(" ".join(cmd))
|
||||
|
||||
result = subprocess.run(cmd, env=env)
|
||||
|
||||
result = subprocess.run(cmd)
|
||||
if result.returncode == 0:
|
||||
# Чистим временные папки, только для PyInstaller (для Nuitka обычно не нужно)
|
||||
if not USE_NUITKA:
|
||||
# Удаляем временные папки
|
||||
for folder in ["build_temp", "__pycache__"]:
|
||||
if os.path.exists(folder):
|
||||
shutil.rmtree(folder)
|
||||
|
||||
print("Сборка успешно завершена!")
|
||||
else:
|
||||
print("Сборка завершилась с ошибкой.")
|
||||
|
Binary file not shown.
Binary file not shown.
40
debug_vars.c
40
debug_vars.c
@ -3,34 +3,34 @@
|
||||
|
||||
|
||||
// Èíêëþäû äëÿ äîñòóïà ê ïåðåìåííûì
|
||||
#include "RS_Functions_modbus.h"
|
||||
#include "vector.h"
|
||||
#include "dq_to_alphabeta_cos.h"
|
||||
#include "teta_calc.h"
|
||||
#include "v_pwm24.h"
|
||||
#include "errors.h"
|
||||
#include "pwm_vector_regul.h"
|
||||
#include "xp_project.h"
|
||||
#include "xp_write_xpwm_time.h"
|
||||
#include "rotation_speed.h"
|
||||
#include "f281xpwm.h"
|
||||
#include "RS_Functions_modbus.h"
|
||||
#include "adc_tools.h"
|
||||
#include "errors.h"
|
||||
#include "v_pwm24.h"
|
||||
#include "f281xpwm.h"
|
||||
#include "xp_project.h"
|
||||
#include "rotation_speed.h"
|
||||
#include "teta_calc.h"
|
||||
#include "dq_to_alphabeta_cos.h"
|
||||
#include "xp_write_xpwm_time.h"
|
||||
#include "log_can.h"
|
||||
#include "pwm_vector_regul.h"
|
||||
#include "RS_Functions.h"
|
||||
#include "svgen_dq.h"
|
||||
#include "detect_phase_break2.h"
|
||||
#include "pid_reg3.h"
|
||||
#include "xp_rotation_sensor.h"
|
||||
#include "svgen_dq.h"
|
||||
#include "x_parallel_bus.h"
|
||||
#include "Spartan2E_Functions.h"
|
||||
#include "x_serial_bus.h"
|
||||
#include "xp_controller.h"
|
||||
#include "Spartan2E_Functions.h"
|
||||
#include "xp_rotation_sensor.h"
|
||||
#include "xPeriphSP6_loader.h"
|
||||
#include "x_parallel_bus.h"
|
||||
#include "CRC_Functions.h"
|
||||
#include "log_params.h"
|
||||
#include "CAN_Setup.h"
|
||||
#include "global_time.h"
|
||||
#include "log_to_memory.h"
|
||||
#include "global_time.h"
|
||||
#include "CAN_Setup.h"
|
||||
#include "log_params.h"
|
||||
#include "CRC_Functions.h"
|
||||
#include "pid_reg3.h"
|
||||
#include "IQmathLib.h"
|
||||
#include "doors_control.h"
|
||||
#include "isolation.h"
|
||||
@ -317,5 +317,5 @@ extern int zero_ADC[20];
|
||||
int DebugVar_Qnt = 1;
|
||||
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
|
||||
DebugVar_t dbg_vars[] = {\
|
||||
{(char *)&ADC0startAddr , pt_int64 , iq12 , ADCdr }, \
|
||||
{(char *)&Bender.KOhms , pt_uint16 , iq_none , "Bender.KOhms" }, \
|
||||
};
|
||||
|
BIN
dist/DebugVarEdit.exe
vendored
BIN
dist/DebugVarEdit.exe
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/PySide6/QtCore.pyd
vendored
BIN
dist/setupVars_GUI.dist/PySide6/QtCore.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/PySide6/QtGui.pyd
vendored
BIN
dist/setupVars_GUI.dist/PySide6/QtGui.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/PySide6/QtNetwork.pyd
vendored
BIN
dist/setupVars_GUI.dist/PySide6/QtNetwork.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/PySide6/QtWidgets.pyd
vendored
BIN
dist/setupVars_GUI.dist/PySide6/QtWidgets.pyd
vendored
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
dist/setupVars_GUI.dist/_bz2.pyd
vendored
BIN
dist/setupVars_GUI.dist/_bz2.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/_ctypes.pyd
vendored
BIN
dist/setupVars_GUI.dist/_ctypes.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/_decimal.pyd
vendored
BIN
dist/setupVars_GUI.dist/_decimal.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/_elementtree.pyd
vendored
BIN
dist/setupVars_GUI.dist/_elementtree.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/_hashlib.pyd
vendored
BIN
dist/setupVars_GUI.dist/_hashlib.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/_lzma.pyd
vendored
BIN
dist/setupVars_GUI.dist/_lzma.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/_socket.pyd
vendored
BIN
dist/setupVars_GUI.dist/_socket.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/_ssl.pyd
vendored
BIN
dist/setupVars_GUI.dist/_ssl.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/_wmi.pyd
vendored
BIN
dist/setupVars_GUI.dist/_wmi.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/libcrypto-3.dll
vendored
BIN
dist/setupVars_GUI.dist/libcrypto-3.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/libffi-8.dll
vendored
BIN
dist/setupVars_GUI.dist/libffi-8.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/libssl-3.dll
vendored
BIN
dist/setupVars_GUI.dist/libssl-3.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/msvcp140.dll
vendored
BIN
dist/setupVars_GUI.dist/msvcp140.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/msvcp140_1.dll
vendored
BIN
dist/setupVars_GUI.dist/msvcp140_1.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/msvcp140_2.dll
vendored
BIN
dist/setupVars_GUI.dist/msvcp140_2.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/pyexpat.pyd
vendored
BIN
dist/setupVars_GUI.dist/pyexpat.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/pyside6.abi3.dll
vendored
BIN
dist/setupVars_GUI.dist/pyside6.abi3.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/python3.dll
vendored
BIN
dist/setupVars_GUI.dist/python3.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/python312.dll
vendored
BIN
dist/setupVars_GUI.dist/python312.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/qt6core.dll
vendored
BIN
dist/setupVars_GUI.dist/qt6core.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/qt6gui.dll
vendored
BIN
dist/setupVars_GUI.dist/qt6gui.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/qt6network.dll
vendored
BIN
dist/setupVars_GUI.dist/qt6network.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/qt6pdf.dll
vendored
BIN
dist/setupVars_GUI.dist/qt6pdf.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/qt6svg.dll
vendored
BIN
dist/setupVars_GUI.dist/qt6svg.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/qt6widgets.dll
vendored
BIN
dist/setupVars_GUI.dist/qt6widgets.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/select.pyd
vendored
BIN
dist/setupVars_GUI.dist/select.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/setupVars_GUI.dll
vendored
BIN
dist/setupVars_GUI.dist/setupVars_GUI.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/shiboken6.abi3.dll
vendored
BIN
dist/setupVars_GUI.dist/shiboken6.abi3.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/shiboken6/Shiboken.pyd
vendored
BIN
dist/setupVars_GUI.dist/shiboken6/Shiboken.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/shiboken6/msvcp140.dll
vendored
BIN
dist/setupVars_GUI.dist/shiboken6/msvcp140.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/shiboken6/msvcp140_1.dll
vendored
BIN
dist/setupVars_GUI.dist/shiboken6/msvcp140_1.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/shiboken6/msvcp140_2.dll
vendored
BIN
dist/setupVars_GUI.dist/shiboken6/msvcp140_2.dll
vendored
Binary file not shown.
Binary file not shown.
BIN
dist/setupVars_GUI.dist/unicodedata.pyd
vendored
BIN
dist/setupVars_GUI.dist/unicodedata.pyd
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/vcruntime140.dll
vendored
BIN
dist/setupVars_GUI.dist/vcruntime140.dll
vendored
Binary file not shown.
BIN
dist/setupVars_GUI.dist/vcruntime140_1.dll
vendored
BIN
dist/setupVars_GUI.dist/vcruntime140_1.dll
vendored
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user