доработана демо-терминалка для считывания tms переменных и встроена в DebugVarEdit
701 lines
30 KiB
Python
701 lines
30 KiB
Python
# build command
|
||
# pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp var_setup_GUI.py
|
||
|
||
import sys
|
||
import os
|
||
import subprocess
|
||
import lxml.etree as ET
|
||
from generate_debug_vars import type_map, choose_type_map
|
||
from enum import IntEnum
|
||
from tms_debugvar_term import _DemoWindow
|
||
import threading
|
||
from generate_debug_vars import run_generate
|
||
import var_setup
|
||
from var_selector_window import VariableSelectorDialog
|
||
from var_table import VariableTableWidget, rows
|
||
from scan_progress_gui import ProcessOutputWindow
|
||
import scan_vars
|
||
import myXML
|
||
import time
|
||
|
||
|
||
from PySide2.QtWidgets import (
|
||
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
|
||
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
|
||
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView,
|
||
QMenuBar, QMenu, QAction
|
||
)
|
||
from PySide2.QtGui import QTextCursor, QKeyEvent, QIcon, QFont
|
||
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
|
||
|
||
|
||
var_edit_title = "Редактор переменных для отладки"
|
||
xml_path_title = "Путь к XML:"
|
||
proj_path_title = "Путь к проекту:"
|
||
makefile_path_title = "Пусть к makefile (относительно проекта)"
|
||
output_path_title = "Путь для для debug_vars.c:"
|
||
scan_title = "Сканировать переменные"
|
||
build_title = "Сгенерировать файл"
|
||
add_vars_title = "Добавить переменные"
|
||
open_output_title = "Открыть файл"
|
||
|
||
def set_sub_elem_text(parent, tag, text):
|
||
el = parent.find(tag)
|
||
if el is None:
|
||
el = ET.SubElement(parent, tag)
|
||
el.text = str(text)
|
||
|
||
# 3. UI: таблица с переменными
|
||
class VarEditor(QWidget):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.vars_list = []
|
||
self.structs = {}
|
||
self.typedef_map = {}
|
||
|
||
self.proj_path = None
|
||
self.xml_path = None
|
||
self.makefile_path = None
|
||
self.structs_path = None
|
||
self.output_path = None
|
||
self._updating = False # Флаг блокировки рекурсии
|
||
self._resizing = False # флаг блокировки повторного вызова
|
||
self.target = 'TMS'
|
||
self.initUI()
|
||
|
||
def initUI(self):
|
||
self.setWindowTitle(var_edit_title)
|
||
|
||
base_path = scan_vars.get_base_path()
|
||
icon_path = os.path.join(base_path, "icon.ico")
|
||
if os.path.exists(icon_path):
|
||
self.setWindowIcon(QIcon(icon_path))
|
||
|
||
# --- Поля ввода пути проекта и XML ---
|
||
|
||
# XML Output
|
||
xml_layout = QHBoxLayout()
|
||
xml_layout.addWidget(QLabel(xml_path_title))
|
||
self.xml_output_edit = QLineEdit()
|
||
self.xml_output_edit.returnPressed.connect(self.update)
|
||
self.xml_output_edit.textChanged.connect(self.__on_xml_path_changed)
|
||
xml_layout.addWidget(self.xml_output_edit)
|
||
btn_xml_browse = QPushButton("...")
|
||
btn_xml_browse.setFixedWidth(30)
|
||
xml_layout.addWidget(btn_xml_browse)
|
||
btn_xml_browse.clicked.connect(self.__browse_xml_output)
|
||
|
||
# Project Path
|
||
proj_layout = QHBoxLayout()
|
||
proj_layout.addWidget(QLabel(proj_path_title))
|
||
self.proj_path_edit = QLineEdit()
|
||
self.proj_path_edit.returnPressed.connect(self.update)
|
||
self.proj_path_edit.textChanged.connect(self.__on_proj_path_changed)
|
||
proj_layout.addWidget(self.proj_path_edit)
|
||
btn_proj_browse = QPushButton("...")
|
||
btn_proj_browse.setFixedWidth(30)
|
||
proj_layout.addWidget(btn_proj_browse)
|
||
btn_proj_browse.clicked.connect(self.__browse_proj_path)
|
||
|
||
# Makefile Path
|
||
makefile_layout = QHBoxLayout()
|
||
makefile_layout.addWidget(QLabel(makefile_path_title))
|
||
self.makefile_edit = QLineEdit()
|
||
self.makefile_edit.returnPressed.connect(self.update)
|
||
self.makefile_edit.textChanged.connect(self.__on_makefile_path_changed)
|
||
makefile_layout.addWidget(self.makefile_edit)
|
||
btn_makefile_browse = QPushButton("...")
|
||
btn_makefile_browse.setFixedWidth(30)
|
||
makefile_layout.addWidget(btn_makefile_browse)
|
||
btn_makefile_browse.clicked.connect(self.__browse_makefile)
|
||
|
||
|
||
|
||
# Source Output File/Directory
|
||
source_output_layout = QHBoxLayout()
|
||
source_output_layout.addWidget(QLabel(output_path_title))
|
||
self.source_output_edit = QLineEdit()
|
||
source_output_layout.addWidget(self.source_output_edit)
|
||
btn_source_output_browse = QPushButton("...")
|
||
btn_source_output_browse.setFixedWidth(30)
|
||
source_output_layout.addWidget(btn_source_output_browse)
|
||
btn_source_output_browse.clicked.connect(self.__browse_source_output)
|
||
|
||
|
||
self.btn_update_vars = QPushButton(scan_title)
|
||
self.btn_update_vars.clicked.connect(self.update_vars_data)
|
||
|
||
# Добавляем чекбокс для выбора типовой карты
|
||
# --- Создаем верхнее меню ---
|
||
menubar = QMenuBar(self)
|
||
menubar.setToolTip('Разные размеры int и кодировки файлов')
|
||
self.target_menu = QMenu("МК:", menubar)
|
||
# Создаем действия для выбора Target
|
||
self.action_tms = QAction("TMS", self, checkable=True)
|
||
self.action_stm = QAction("STM", self, checkable=True)
|
||
# Инициализируем QSettings с именем организации и приложения
|
||
self.settings = QSettings("SET", "DebugVarEdit_MainWindow")
|
||
# Восстанавливаем сохранённое состояние, если есть
|
||
mcu = self.settings.value("mcu_choosen", True, type=str)
|
||
self.on_target_selected(mcu)
|
||
|
||
self.target_menu.setToolTip(f'TMS: Размер int 16 бит. Кодировка cp1251\nSTM: Размер int 32 бита. Кодировка utf-8')
|
||
# Группируем действия чтобы выбирался только один
|
||
self.action_tms.triggered.connect(lambda: self.on_target_selected("TMS"))
|
||
self.action_tms.setToolTip('Размер int 16 бит. Кодировка cp1251')
|
||
self.action_stm.triggered.connect(lambda: self.on_target_selected("STM"))
|
||
self.action_stm.setToolTip('Размер int 32 бита. Кодировка utf-8')
|
||
|
||
self.target_menu.addAction(self.action_tms)
|
||
self.target_menu.addAction(self.action_stm)
|
||
|
||
self.terminal_menu = QMenu("Открыть Терминал", menubar)
|
||
|
||
self.action_terminal_tms = QAction("TMS DemoTerminal", self)
|
||
self.action_terminal_modbus = QAction("Modbus DemoTerminal", self)
|
||
self.action_terminal_tms.triggered.connect(lambda: self.open_terminal("TMS"))
|
||
self.action_terminal_modbus.triggered.connect(lambda: self.open_terminal("MODBUS"))
|
||
|
||
self.terminal_menu.addAction(self.action_terminal_tms)
|
||
#self.terminal_menu.addAction(self.action_terminal_modbus)
|
||
|
||
menubar.addMenu(self.target_menu)
|
||
menubar.addMenu(self.terminal_menu)
|
||
|
||
# Кнопка сохранения
|
||
btn_save = QPushButton(build_title)
|
||
btn_save.clicked.connect(self.save_build)
|
||
|
||
# Кнопка добавления переменных
|
||
self.btn_add_vars = QPushButton(add_vars_title)
|
||
self.btn_add_vars.clicked.connect(self.__open_variable_selector)
|
||
|
||
# Кнопка открыть output-файл с выбором программы
|
||
btn_open_output = QPushButton(open_output_title)
|
||
btn_open_output.clicked.connect(self.__open_output_file_with_program)
|
||
# Таблица
|
||
self.table = VariableTableWidget()
|
||
# Основной layout
|
||
layout = QVBoxLayout()
|
||
layout.setMenuBar(menubar) # прикрепляем menubar в layout сверху
|
||
layout.addLayout(xml_layout)
|
||
layout.addLayout(proj_layout)
|
||
layout.addLayout(makefile_layout)
|
||
layout.addWidget(self.btn_update_vars)
|
||
layout.addWidget(self.table)
|
||
layout.addWidget(self.btn_add_vars)
|
||
layout.addLayout(source_output_layout)
|
||
layout.addWidget(btn_save)
|
||
layout.addWidget(btn_open_output)
|
||
|
||
self.setLayout(layout)
|
||
|
||
def open_terminal(self, target):
|
||
target = target.lower()
|
||
|
||
if target == "tms":
|
||
self.terminal_widget = _DemoWindow() # _DemoWindow наследует QWidget
|
||
self.terminal_widget.show()
|
||
elif target == "modbus":
|
||
a=1
|
||
|
||
|
||
def on_target_selected(self, target):
|
||
self.target_menu.setTitle(f'МК: {target}')
|
||
self.settings.setValue("mcu_choosen", target)
|
||
self.target = target.lower()
|
||
if self.target == "stm":
|
||
choose_type_map(True)
|
||
self.action_stm.setChecked(True)
|
||
self.action_tms.setChecked(False)
|
||
else:
|
||
choose_type_map(False)
|
||
self.action_tms.setChecked(True)
|
||
self.action_stm.setChecked(False)
|
||
|
||
|
||
def get_xml_path(self):
|
||
xml_path = self.xml_output_edit.text().strip()
|
||
return xml_path
|
||
|
||
def get_proj_path(self):
|
||
proj_path = self.proj_path_edit.text().strip()
|
||
return proj_path
|
||
|
||
def get_makefile_path(self):
|
||
proj_path = self.get_proj_path()
|
||
rel_makefile_path = self.makefile_edit.text().strip()
|
||
if not rel_makefile_path:
|
||
return None
|
||
makefile_path = myXML.make_absolute_path(rel_makefile_path, proj_path)
|
||
return makefile_path
|
||
|
||
def get_struct_path(self):
|
||
proj_path = self.get_proj_path()
|
||
xml_path = self.get_xml_path()
|
||
root, tree = myXML.safe_parse_xml(xml_path)
|
||
if root is None:
|
||
return
|
||
# --- structs_path из атрибута ---
|
||
structs_path = root.attrib.get('structs_path', '').strip()
|
||
structs_path_full = myXML.make_absolute_path(structs_path, proj_path)
|
||
if structs_path_full and os.path.isfile(structs_path_full):
|
||
structs_path = structs_path_full
|
||
else:
|
||
structs_path = None
|
||
return structs_path
|
||
|
||
def get_output_path(self):
|
||
output_path = os.path.abspath(self.source_output_edit.text().strip())
|
||
return output_path
|
||
|
||
def update_all_paths(self):
|
||
self.proj_path = self.get_proj_path()
|
||
self.xml_path = self.get_xml_path()
|
||
self.makefile_path = self.get_makefile_path()
|
||
self.structs_path = self.get_struct_path()
|
||
self.output_path = self.get_output_path()
|
||
|
||
|
||
def update_vars_data(self):
|
||
self.update_all_paths()
|
||
|
||
if not self.proj_path or not self.xml_path:
|
||
QMessageBox.warning(self, "Ошибка", "Укажите пути проекта и XML.")
|
||
return
|
||
|
||
if not os.path.isfile(self.makefile_path):
|
||
QMessageBox.warning(self, "Ошибка", f"Makefile не найден:\n{self.makefile_path}")
|
||
return
|
||
|
||
|
||
# Создаём окно с кнопкой "Готово"
|
||
self.proc_win = ProcessOutputWindow(self.proj_path, self.makefile_path, self.xml_path,
|
||
self.__after_scan_vars_finished, self)
|
||
self.proc_win.start_scan()
|
||
|
||
|
||
|
||
def save_build(self):
|
||
vars_out = []
|
||
for row in range(self.table.rowCount()):
|
||
include_cb = self.table.cellWidget(row, rows.include)
|
||
if not include_cb.isChecked():
|
||
continue
|
||
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)
|
||
|
||
var_data = {
|
||
'name': name_edit.text(),
|
||
'type': 'pt_' + pt_type_combo.currentText(),
|
||
'iq_type': 't_' + iq_combo.currentText(),
|
||
'return_type': 't_' + ret_combo.currentText() if ret_combo.currentText() else 't_iq_none',
|
||
'short_name': short_name_edit.text(),
|
||
}
|
||
vars_out.append(var_data)
|
||
|
||
self.update_all_paths()
|
||
|
||
if not self.proj_path or not self.xml_path or not self.output_path:
|
||
QMessageBox.warning(self, "Ошибка", f"Заполните: {xml_path_title[:-1]}, {proj_path_title[:-1]}, {output_path_title[:-1]}.")
|
||
return
|
||
|
||
try:
|
||
run_generate(self.proj_path, self.xml_path, self.output_path, self.table._shortname_size)
|
||
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
|
||
self.update()
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка при генерации", str(e))
|
||
|
||
|
||
def update(self, force=0):
|
||
if self._updating and (force==0):
|
||
return # Уже в процессе обновления — выходим, чтобы избежать рекурсии
|
||
self._updating = True
|
||
|
||
self.update_all_paths()
|
||
try:
|
||
if self.xml_path and not os.path.isfile(self.xml_path):
|
||
return
|
||
|
||
try:
|
||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||
if root is None:
|
||
return
|
||
|
||
if not self.proj_path:
|
||
# Если в поле ничего нет, пробуем взять из XML
|
||
proj_path_from_xml = root.attrib.get('proj_path', '').strip()
|
||
if proj_path_from_xml and os.path.isdir(proj_path_from_xml):
|
||
self.proj_path = proj_path_from_xml
|
||
self.proj_path_edit.setText(proj_path_from_xml)
|
||
else:
|
||
QMessageBox.warning(
|
||
self,
|
||
"Внимание",
|
||
"Путь к проекту не найден или не существует.\n"
|
||
f"Пожалуйста, укажите его вручную в поле '{proj_path_title[:-1]}'."
|
||
)
|
||
else:
|
||
if not os.path.isdir(self.proj_path):
|
||
QMessageBox.warning(
|
||
self,
|
||
"Внимание",
|
||
f"Указанный путь к проекту не существует:\n{self.proj_path}\n"
|
||
"Пожалуйста, исправьте путь в поле '{proj_path_title[:-1]}'."
|
||
)
|
||
|
||
if not self.makefile_path and self.proj_path and os.path.isdir(self.proj_path):
|
||
makefile_path = root.attrib.get('makefile_path', '').strip()
|
||
makefile_path_full = myXML.make_absolute_path(makefile_path, self.proj_path)
|
||
if os.path.isfile(makefile_path_full):
|
||
# Обновляем edit-поле на относительный путь, абсолют сохраняем
|
||
self.makefile_path = makefile_path_full
|
||
self.makefile_edit.setText(makefile_path)
|
||
else:
|
||
self.makefile_path = None
|
||
self.makefile_edit.setText("")
|
||
|
||
|
||
# --- structs_path из атрибута ---
|
||
structs_path = root.attrib.get('structs_path', '').strip()
|
||
structs_path_full = myXML.make_absolute_path(structs_path, self.proj_path)
|
||
if structs_path_full and os.path.isfile(structs_path_full):
|
||
self.structs_path = structs_path_full
|
||
self.structs, self.typedef_map = var_setup.parse_structs(structs_path_full)
|
||
else:
|
||
self.structs_path = None
|
||
|
||
self.vars_list = var_setup.parse_vars(self.xml_path, self.typedef_map)
|
||
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
|
||
except Exception as e:
|
||
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
|
||
|
||
|
||
finally:
|
||
self._updating = False # Снимаем блокировку при выходе из функции
|
||
|
||
def __browse_proj_path(self):
|
||
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта")
|
||
if dir_path:
|
||
self.proj_path_edit.setText(dir_path)
|
||
self.proj_path = dir_path
|
||
|
||
# Сброс makefile, если proj_path изменился
|
||
if not os.path.isdir(dir_path):
|
||
self.makefile_path = None
|
||
self.makefile_edit.setText("")
|
||
else:
|
||
if self.makefile_path and os.path.isfile(self.makefile_path):
|
||
rel_path = myXML.make_relative_path(self.makefile_path, dir_path)
|
||
self.makefile_edit.setText(rel_path)
|
||
|
||
self.update()
|
||
|
||
self.update()
|
||
if self.makefile_path and self.proj_path:
|
||
path = myXML.make_relative_path(self.makefile_path, self.proj_path)
|
||
self.makefile_edit.setText(path)
|
||
self.makefile_path = path
|
||
|
||
def __browse_xml_output(self):
|
||
dialog = QFileDialog(self, "Выберите или создайте XML-файл")
|
||
dialog.setAcceptMode(QFileDialog.AcceptSave)
|
||
dialog.setNameFilter("XML files (*.xml);;All Files (*)")
|
||
dialog.setDefaultSuffix("xml")
|
||
dialog.setOption(QFileDialog.DontConfirmOverwrite, True) # ⚠️ Не спрашивать про перезапись
|
||
if dialog.exec_():
|
||
file_path = dialog.selectedFiles()[0]
|
||
if not file_path.endswith(".xml"):
|
||
file_path += ".xml"
|
||
self.xml_output_edit.setText(file_path)
|
||
self.xml_path = file_path
|
||
|
||
def keyPressEvent(self, event: QKeyEvent):
|
||
if event.key() == Qt.Key_Delete:
|
||
self.delete_selected_rows()
|
||
else:
|
||
super().keyPressEvent(event)
|
||
|
||
def __browse_makefile(self):
|
||
if self.target == 'stm':
|
||
file_filter = "Makefile или Keil-проект (*.uvprojx *.uvproj makefile);;Все файлы (*)"
|
||
dialog_title = "Выберите Makefile или Keil-проект"
|
||
else: # 'TMS' или по умолчанию
|
||
file_filter = "Makefile (makefile);;Все файлы (*)"
|
||
dialog_title = "Выберите Makefile"
|
||
|
||
file_path, _ = QFileDialog.getOpenFileName(
|
||
self,
|
||
dialog_title,
|
||
filter=file_filter
|
||
)
|
||
if file_path:
|
||
if self.proj_path:
|
||
path = myXML.make_relative_path(file_path, self.proj_path)
|
||
else:
|
||
path = file_path
|
||
self.makefile_edit.setText(path)
|
||
self.makefile_path = path
|
||
|
||
def __browse_source_output(self):
|
||
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
|
||
if dir_path:
|
||
self.source_output_edit.setText(dir_path)
|
||
self.output_path = dir_path
|
||
else:
|
||
self.output_path = ''
|
||
|
||
|
||
def __on_xml_path_changed(self, _):
|
||
self.xml_path = self.get_xml_path()
|
||
self.update()
|
||
|
||
def __on_proj_path_changed(self, _):
|
||
self.proj_path = self.get_proj_path()
|
||
|
||
if not os.path.isdir(self.proj_path):
|
||
self.makefile_path = None
|
||
self.makefile_edit.setText("")
|
||
return # Преждевременно выходим, если проект не существует
|
||
|
||
# Обновим путь к makefile, если он уже задан и абсолютен
|
||
if self.makefile_path and os.path.isfile(self.makefile_path):
|
||
rel_path = myXML.make_relative_path(self.makefile_path, self.proj_path)
|
||
self.makefile_edit.setText(rel_path)
|
||
|
||
self.update()
|
||
|
||
def __on_makefile_path_changed(self, _):
|
||
self.makefile_path = self.get_makefile_path()
|
||
if self.makefile_path and self.proj_path:
|
||
path = myXML.make_relative_path(self.makefile_path, self.proj_path)
|
||
self.makefile_edit.setText(path)
|
||
self.update()
|
||
|
||
|
||
def __after_scan_vars_finished(self):
|
||
if not os.path.isfile(self.xml_path):
|
||
self.makefile_path = None
|
||
self.structs_path = None
|
||
self.proj_path = None
|
||
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {self.xml_path}")
|
||
return
|
||
|
||
try:
|
||
self.update(1)
|
||
|
||
except Exception as e:
|
||
self.makefile_path = None
|
||
self.structs_path = None
|
||
self.proj_path = None
|
||
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
|
||
|
||
|
||
def delete_selected_rows(self):
|
||
# Получаем имена всех выбранных переменных из первого столбца
|
||
selected_names = self.table.get_selected_var_names()
|
||
|
||
if not selected_names:
|
||
return
|
||
|
||
# Меняем флаг show_var по имени
|
||
for var in self.vars_list:
|
||
if var.get('name') in selected_names:
|
||
var['show_var'] = 'false'
|
||
|
||
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
|
||
self.write_to_xml()
|
||
|
||
|
||
def __open_variable_selector(self):
|
||
self.update()
|
||
if not self.vars_list:
|
||
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
|
||
return
|
||
|
||
dlg = VariableSelectorDialog(self.table, self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
|
||
if dlg.exec_():
|
||
self.write_to_xml()
|
||
self.update()
|
||
|
||
def write_to_xml(self, dummy=None):
|
||
t0 = time.time()
|
||
self.update_all_paths()
|
||
def get_val(name, default=''):
|
||
return str(v_table[name] if v_table and name in v_table else v.get(name, default))
|
||
|
||
def element_differs(elem, values: dict):
|
||
for tag, new_val in values.items():
|
||
current_elem = elem.find(tag)
|
||
current_val = (current_elem.text or '').strip()
|
||
new_val_str = str(new_val or '').strip()
|
||
if current_val != new_val_str:
|
||
return True
|
||
return False
|
||
|
||
if not self.xml_path or not os.path.isfile(self.xml_path):
|
||
print("XML файл не найден или путь пустой")
|
||
return
|
||
if not self.proj_path or not os.path.isdir(self.proj_path):
|
||
print("Project path не найден или путь пустой")
|
||
return
|
||
if not self.makefile_path or not os.path.isfile(self.makefile_path):
|
||
print("makefile файл не найден или путь пустой")
|
||
return
|
||
if os.path.abspath(self.makefile_path) == os.path.abspath(self.proj_path):
|
||
print("makefile_path совпадает с proj_path — игнор")
|
||
return
|
||
|
||
try:
|
||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||
if root is None:
|
||
return
|
||
|
||
|
||
root.set("proj_path", self.proj_path.replace("\\", "/"))
|
||
|
||
if self.makefile_path and os.path.isfile(self.makefile_path):
|
||
rel_makefile = myXML.make_relative_path(self.makefile_path, self.proj_path)
|
||
# Если результат — абсолютный путь, не записываем
|
||
if not os.path.isabs(rel_makefile):
|
||
root.set("makefile_path", rel_makefile)
|
||
|
||
if self.structs_path and os.path.isfile(self.structs_path):
|
||
rel_struct = myXML.make_relative_path(self.structs_path, self.proj_path)
|
||
root.set("structs_path", rel_struct)
|
||
if not os.path.isabs(rel_struct):
|
||
root.set("structs_path", rel_struct)
|
||
|
||
t1 = time.time()
|
||
|
||
vars_elem = root.find('variables')
|
||
if vars_elem is None:
|
||
vars_elem = ET.SubElement(root, 'variables')
|
||
|
||
original_info = {}
|
||
for var_elem in vars_elem.findall('var'):
|
||
name = var_elem.attrib.get('name')
|
||
if name:
|
||
original_info[name] = {
|
||
'file': var_elem.findtext('file', ''),
|
||
'extern': var_elem.findtext('extern', ''),
|
||
'static': var_elem.findtext('static', '')
|
||
}
|
||
var_elements_by_name = {ve.attrib.get('name'): ve for ve in vars_elem.findall('var')}
|
||
|
||
# Читаем переменные из таблицы (активные/изменённые)
|
||
table_vars = {v['name']: v for v in self.table.read_data()}
|
||
# Все переменные (в том числе новые, которых нет в таблице)
|
||
all_vars_by_name = {v['name']: v for v in self.vars_list}
|
||
|
||
# Объединённый список переменных для записи
|
||
all_names = list(all_vars_by_name.keys())
|
||
t2 = time.time()
|
||
for name in all_names:
|
||
v = all_vars_by_name[name]
|
||
v_table = table_vars.get(name)
|
||
var_elem = None
|
||
|
||
pt_type_val = get_val('pt_type').lower()
|
||
if 'arr' in pt_type_val or 'struct' in pt_type_val or 'union' in pt_type_val:
|
||
continue
|
||
|
||
show_var_val = str(v.get('show_var', 'false')).lower()
|
||
enable_val = get_val('enable').lower()
|
||
|
||
# Тут подтягиваем из таблицы, если есть, иначе из v
|
||
shortname_val = get_val('shortname')
|
||
iq_type_val = get_val('iq_type')
|
||
ret_type_val = get_val('return_type')
|
||
|
||
|
||
# file/extern/static: из original_info, либо из v
|
||
file_val = v.get('file') or original_info.get(name, {}).get('file', '')
|
||
extern_val = v.get('extern') or original_info.get(name, {}).get('extern', '')
|
||
static_val = v.get('static') or original_info.get(name, {}).get('static', '')
|
||
|
||
values_to_write = {
|
||
'show_var': show_var_val,
|
||
'enable': enable_val,
|
||
'shortname': shortname_val,
|
||
'pt_type': pt_type_val,
|
||
'iq_type': iq_type_val,
|
||
'return_type': ret_type_val,
|
||
'type': v.get('type', ''),
|
||
'file': file_val,
|
||
'extern': extern_val,
|
||
'static': static_val
|
||
}
|
||
|
||
# Ищем уже существующий <var> в XML
|
||
var_elem = var_elements_by_name.get(name)
|
||
# Если элемента нет, это новая переменная — сразу пишем
|
||
if var_elem is None:
|
||
var_elem = ET.SubElement(vars_elem, 'var', {'name': name})
|
||
var_elements_by_name[name] = var_elem
|
||
write_all = True # обязательно записать все поля
|
||
else:
|
||
write_all = element_differs(var_elem, values_to_write)
|
||
|
||
if not write_all:
|
||
continue # Пропускаем, если нет изменений
|
||
|
||
for tag, text in values_to_write.items():
|
||
set_sub_elem_text(var_elem, tag, text)
|
||
|
||
t3 = time.time()
|
||
# Преобразуем дерево в строку
|
||
myXML.fwrite(root, self.xml_path)
|
||
self.table.check()
|
||
t4 = time.time()
|
||
'''print(f"[T1] parse + set paths: {t1 - t0:.3f} сек")
|
||
print(f"[T2] prepare variables: {t2 - t1:.3f} сек")
|
||
print(f"[T3] loop + updates: {t3 - t2:.3f} сек")
|
||
print(f"[T4] write to file: {t4 - t3:.3f} сек")
|
||
print(f"[TOTAL] write_to_xml total: {t4 - t0:.3f} сек")'''
|
||
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,
|
||
"Ошибка",
|
||
"Путь к debug_var.c не задан."
|
||
f"Пожалуйста, укажите его в поле '{output_path_title[:-1]}'.")
|
||
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}")
|
||
|
||
|
||
|
||
|
||
if __name__ == "__main__":
|
||
app = QApplication(sys.argv)
|
||
|
||
editor = VarEditor()
|
||
editor.resize(900, 600)
|
||
editor.show()
|
||
|
||
sys.exit(app.exec_())
|
||
|