# build command # pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp setupVars_GUI.py import sys import os import subprocess import xml.etree.ElementTree as ET from generateVars import type_map from enum import IntEnum import threading from scanVars import run_scan from generateVars import run_generate import setupVars from VariableSelector import VariableSelectorDialog from VariableTable import VariableTableWidget, rows from scanVarGUI import ProcessOutputWindowDummy import scanVars 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 ) from PySide2.QtGui import QTextCursor, QKeyEvent, QIcon 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 = "Открыть файл" # 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.initUI() def initUI(self): self.setWindowTitle(var_edit_title) base_path = scanVars.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) # Кнопка сохранения 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.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 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() makefile_path = setupVars.make_absolute_path(self.makefile_edit.text().strip(), proj_path) return makefile_path def get_struct_path(self): proj_path = self.get_proj_path() xml_path = self.get_xml_path() root, tree = scanVars.safe_parse_xml(xml_path) if root is None: return # --- structs_path из атрибута --- structs_path = root.attrib.get('structs_path', '').strip() structs_path_full = setupVars.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 = ProcessOutputWindowDummy(self.__after_scanvars_finished) self.emitting_stream = self.proc_win.emitting_stream # ключевая строка! self.proc_win.show() def run_scan_wrapper(): try: old_stdout = sys.stdout sys.stdout = self.emitting_stream run_scan(self.proj_path, self.makefile_path, self.xml_path) except Exception as e: self.emitting_stream.text_written.emit(f"\n[ОШИБКА] {e}") finally: sys.stdout = old_stdout self.emitting_stream.text_written.emit("\n--- Анализ завершён ---") self.proc_win.btn_close.setEnabled(True) threading.Thread(target=run_scan_wrapper, daemon=True).start() 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': iq_combo.currentText(), 'return_type': ret_combo.currentText() if ret_combo.currentText() else 'int', '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, "Ошибка", "Заполните все пути: проект, XML и output.") return try: run_generate(self.proj_path, self.xml_path, self.output_path) QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.") self.update() except Exception as e: QMessageBox.critical(self, "Ошибка при генерации", str(e)) def update(self): if self._updating: 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 = scanVars.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, "Внимание", "Путь к проекту (proj_path) не найден или не существует.\n" "Пожалуйста, укажите его вручную в поле 'Project Path'." ) else: if not os.path.isdir(self.proj_path): QMessageBox.warning( self, "Внимание", f"Указанный путь к проекту не существует:\n{self.proj_path}\n" "Пожалуйста, исправьте путь в поле 'Project Path'." ) if not self.makefile_path: # --- makefile_path из атрибута --- makefile_path = root.attrib.get('makefile_path', '').strip() makefile_path_full = setupVars.make_absolute_path(makefile_path, self.proj_path) if makefile_path_full and os.path.isfile(makefile_path_full): self.makefile_edit.setText(makefile_path) else: self.makefile_path = None # --- structs_path из атрибута --- structs_path = root.attrib.get('structs_path', '').strip() structs_path_full = setupVars.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 = setupVars.setupVars.parse_structs(structs_path_full) else: self.structs_path = None self.vars_list = setupVars.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 if self.makefile_path and self.proj_path: path = setupVars.make_relative_path(self.makefile_path, self.proj_path) self.makefile_edit.setText(path) self.makefile_path = path def __browse_xml_output(self): file_path, _ = QFileDialog.getSaveFileName( self, "Выберите XML файл", filter="XML files (*.xml);;All Files (*)" ) 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): file_path, _ = QFileDialog.getOpenFileName( self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)" ) if file_path and self.proj_path: path = setupVars.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() self.update() def __on_makefile_path_changed(self, _): self.makefile_path = self.get_makefile_path() if self.makefile_path and self.proj_path: path = setupVars.make_relative_path(self.makefile_path, self.proj_path) self.makefile_edit.setText(path) self.update() def __after_scanvars_finished(self): self.update_all_paths() if not os.path.isfile(self.xml_path): QMessageBox.critical(self, "Ошибка", f"Файл не найден: {self.xml_path}") return try: self.makefile_path = None self.structs_path = None self.proj_path = None self.update() except Exception as e: QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}") def delete_selected_rows(self): selected_rows = sorted(set(index.row() for index in self.table.selectedIndexes()), reverse=True) if not selected_rows: return # Удаляем из vars_list те, у кого show_var == true и имя совпадает filtered_vars = [v for v in self.vars_list if v.get('show_var', 'false') == 'true'] for row in selected_rows: if 0 <= row < len(filtered_vars): var_to_remove = filtered_vars[row] for v in self.vars_list: if v['name'] == var_to_remove['name']: v['show_var'] = 'false' break self.table.populate(self.vars_list, self.structs, self.write_to_xml) self.write_to_xml() def __open_variable_selector(self): if not self.vars_list: QMessageBox.warning(self, "Нет переменных", "Сначала загрузите или обновите переменные.") return dlg = VariableSelectorDialog(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): self.update_all_paths() 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 try: root, tree = scanVars.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 = setupVars.make_relative_path(self.makefile_path, self.proj_path) root.set("makefile_path", rel_makefile) if self.structs_path and os.path.isfile(self.structs_path): rel_struct = setupVars.make_relative_path(self.structs_path, self.proj_path) root.set("structs_path", rel_struct) 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', '') } # Читаем переменные из таблицы (активные/изменённые) 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()) for name in all_names: v = all_vars_by_name[name] v_table = table_vars.get(name) var_elem = None # Ищем уже существующий в XML for ve in vars_elem.findall('var'): if ve.attrib.get('name') == name: var_elem = ve break if var_elem is None: var_elem = ET.SubElement(vars_elem, 'var', {'name': name}) 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) 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) # Тут подтягиваем из таблицы, если есть, иначе из v shortname_val = v_table['shortname'] if v_table and 'shortname' in v_table else v.get('shortname', '') pt_type_val = v_table['pt_type'] if v_table and 'pt_type' in v_table else v.get('pt_type', '') iq_type_val = v_table['iq_type'] if v_table and 'iq_type' in v_table else v.get('iq_type', '') ret_type_val = v_table['return_type'] if v_table and 'return_type' in v_table else v.get('return_type', '') set_sub_elem_text(var_elem, 'shortname', shortname_val) set_sub_elem_text(var_elem, 'pt_type', pt_type_val) set_sub_elem_text(var_elem, 'iq_type', iq_type_val) set_sub_elem_text(var_elem, 'return_type', ret_type_val) set_sub_elem_text(var_elem, 'type', v.get('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', '') set_sub_elem_text(var_elem, 'file', file_val) set_sub_elem_text(var_elem, 'extern', extern_val) set_sub_elem_text(var_elem, 'static', static_val) # Преобразуем дерево в строку self.table.check() scanVars.indent_xml(root) ET.ElementTree(root).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}") if __name__ == "__main__": app = QApplication(sys.argv) editor = VarEditor() editor.resize(900, 600) editor.show() sys.exit(app.exec_())