кууучу всего сделано
базово разделены gui файлы и обработки xml базово работает, надо дорабатывать и тестить
This commit is contained in:
parent
2e9592ffbb
commit
0ba81a5147
168
VariableSelector.py
Normal file
168
VariableSelector.py
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
import re
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
|
||||||
|
QLineEdit, QLabel, QHeaderView
|
||||||
|
)
|
||||||
|
from PySide6.QtCore import Qt
|
||||||
|
from setupVars import *
|
||||||
|
from scanVars import *
|
||||||
|
|
||||||
|
|
||||||
|
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
|
||||||
|
|
||||||
|
class VariableSelectorDialog(QDialog):
|
||||||
|
def __init__(self, all_vars, structs, typedefs, parent=None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.setWindowTitle("Выбор переменных")
|
||||||
|
self.resize(600, 500)
|
||||||
|
self.selected_names = []
|
||||||
|
|
||||||
|
self.all_vars = all_vars
|
||||||
|
self.structs = structs
|
||||||
|
self.typedefs = typedefs
|
||||||
|
self.expanded_vars = []
|
||||||
|
self.var_map = {v['name']: v for v in all_vars}
|
||||||
|
|
||||||
|
self.search_input = QLineEdit()
|
||||||
|
self.search_input.setPlaceholderText("Поиск по имени переменной...")
|
||||||
|
self.search_input.textChanged.connect(self.filter_tree)
|
||||||
|
|
||||||
|
self.tree = QTreeWidget()
|
||||||
|
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
|
||||||
|
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
|
||||||
|
self.tree.setRootIsDecorated(False) # без иконки "вложенность"
|
||||||
|
self.tree.setUniformRowHeights(True)
|
||||||
|
|
||||||
|
# --- Автоматическая ширина колонок
|
||||||
|
self.tree.setStyleSheet("""
|
||||||
|
QTreeWidget::item:selected {
|
||||||
|
background-color: #87CEFA;
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
QTreeWidget::item:hover {
|
||||||
|
background-color: #D3D3D3;
|
||||||
|
}
|
||||||
|
""")
|
||||||
|
|
||||||
|
self.btn_add = QPushButton("Добавить выбранные")
|
||||||
|
self.btn_add.clicked.connect(self.on_add_clicked)
|
||||||
|
|
||||||
|
layout = QVBoxLayout()
|
||||||
|
layout.addWidget(QLabel("Поиск:"))
|
||||||
|
layout.addWidget(self.search_input)
|
||||||
|
layout.addWidget(self.tree)
|
||||||
|
layout.addWidget(self.btn_add)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
self.populate_tree()
|
||||||
|
|
||||||
|
def add_tree_item_recursively(self, parent, var):
|
||||||
|
"""
|
||||||
|
Рекурсивно добавляет переменную и её дочерние поля в дерево.
|
||||||
|
Если parent == None, добавляет на верхний уровень.
|
||||||
|
"""
|
||||||
|
name = var['name']
|
||||||
|
type_str = var.get('type', '')
|
||||||
|
show_var = var.get('show_var', 'false') == 'true'
|
||||||
|
|
||||||
|
item = QTreeWidgetItem([name, type_str])
|
||||||
|
item.setData(0, Qt.UserRole, name)
|
||||||
|
|
||||||
|
for i, attr in enumerate(['file', 'extern', 'static']):
|
||||||
|
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
|
||||||
|
|
||||||
|
if show_var:
|
||||||
|
item.setForeground(0, Qt.gray)
|
||||||
|
item.setForeground(1, Qt.gray)
|
||||||
|
item.setToolTip(0, "Уже добавлена")
|
||||||
|
item.setToolTip(1, "Уже добавлена")
|
||||||
|
|
||||||
|
if parent is None:
|
||||||
|
self.tree.addTopLevelItem(item)
|
||||||
|
else:
|
||||||
|
parent.addChild(item)
|
||||||
|
|
||||||
|
for child in var.get('children', []):
|
||||||
|
self.add_tree_item_recursively(item, child)
|
||||||
|
|
||||||
|
|
||||||
|
def populate_tree(self):
|
||||||
|
self.tree.clear()
|
||||||
|
|
||||||
|
expanded_vars = expand_vars(self.all_vars, self.structs, self.typedefs)
|
||||||
|
|
||||||
|
for var in expanded_vars:
|
||||||
|
self.add_tree_item_recursively(None, var)
|
||||||
|
|
||||||
|
header = self.tree.header()
|
||||||
|
header.setSectionResizeMode(0, QHeaderView.Stretch)
|
||||||
|
header.setSectionResizeMode(1, QHeaderView.ResizeToContents)
|
||||||
|
|
||||||
|
def filter_tree(self):
|
||||||
|
text = self.search_input.text().strip().lower()
|
||||||
|
|
||||||
|
def match_recursive(item):
|
||||||
|
matched = False
|
||||||
|
for i in range(item.childCount()):
|
||||||
|
child = item.child(i)
|
||||||
|
if match_recursive(child):
|
||||||
|
matched = True
|
||||||
|
|
||||||
|
# Проверяем текущий элемент
|
||||||
|
name = item.text(0).lower()
|
||||||
|
typ = item.text(1).lower()
|
||||||
|
if text in name or text in typ:
|
||||||
|
matched = True
|
||||||
|
|
||||||
|
item.setHidden(not matched)
|
||||||
|
# Открываем только те элементы, у которых есть совпадение в потомках или в себе
|
||||||
|
item.setExpanded(matched)
|
||||||
|
return matched
|
||||||
|
|
||||||
|
for i in range(self.tree.topLevelItemCount()):
|
||||||
|
match_recursive(self.tree.topLevelItem(i))
|
||||||
|
|
||||||
|
|
||||||
|
def on_add_clicked(self):
|
||||||
|
self.selected_names = []
|
||||||
|
|
||||||
|
for item in self.tree.selectedItems():
|
||||||
|
name = item.text(0) # имя переменной (в колонке 1)
|
||||||
|
type_str = item.text(1) # тип переменной (в колонке 2)
|
||||||
|
|
||||||
|
if not name or not type_str:
|
||||||
|
continue
|
||||||
|
|
||||||
|
self.selected_names.append((name, type_str))
|
||||||
|
|
||||||
|
if name in self.var_map:
|
||||||
|
# Если переменная уже есть, просто включаем её и показываем
|
||||||
|
var = self.var_map[name]
|
||||||
|
var['show_var'] = 'true'
|
||||||
|
var['enable'] = 'true'
|
||||||
|
else:
|
||||||
|
# Создаём новый элемент переменной
|
||||||
|
# Получаем родительские параметры
|
||||||
|
file_val = item.data(0, Qt.UserRole + 1)
|
||||||
|
extern_val = item.data(0, Qt.UserRole + 2)
|
||||||
|
static_val = item.data(0, Qt.UserRole + 3)
|
||||||
|
|
||||||
|
new_var = {
|
||||||
|
'name': name,
|
||||||
|
'type': type_str,
|
||||||
|
'show_var': 'true',
|
||||||
|
'enable': 'true',
|
||||||
|
'shortname': name,
|
||||||
|
'pt_type': '',
|
||||||
|
'iq_type': '',
|
||||||
|
'return_type': 'iq_none',
|
||||||
|
'file': file_val,
|
||||||
|
'extern': str(extern_val).lower() if extern_val else 'false',
|
||||||
|
'static': str(static_val).lower() if static_val else 'false',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Добавляем в список переменных
|
||||||
|
self.all_vars.append(new_var)
|
||||||
|
self.var_map[name] = new_var # Чтобы в будущем не добавлялось повторно
|
||||||
|
|
||||||
|
self.accept()
|
BIN
__pycache__/VariableSelector.cpython-313.pyc
Normal file
BIN
__pycache__/VariableSelector.cpython-313.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
__pycache__/setupVars.cpython-313.pyc
Normal file
BIN
__pycache__/setupVars.cpython-313.pyc
Normal file
Binary file not shown.
43
debug_vars.c
43
debug_vars.c
@ -1,35 +1,35 @@
|
|||||||
// Ýòîò ôàéë ñãåíåðèðîâàí àâòîìàòè÷åñêè
|
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
#include "debug_tools.h"
|
#include "debug_tools.h"
|
||||||
|
|
||||||
|
|
||||||
// Èíêëþäû äëÿ äîñòóïà ê ïåðåìåííûì
|
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
#include "RS_Functions_modbus.h"
|
|
||||||
#include "xp_project.h"
|
#include "xp_project.h"
|
||||||
#include "v_pwm24.h"
|
#include "RS_Functions_modbus.h"
|
||||||
#include "adc_tools.h"
|
|
||||||
#include "vector.h"
|
#include "vector.h"
|
||||||
#include "errors.h"
|
#include "errors.h"
|
||||||
#include "f281xpwm.h"
|
#include "adc_tools.h"
|
||||||
#include "pwm_vector_regul.h"
|
#include "pwm_vector_regul.h"
|
||||||
#include "log_can.h"
|
|
||||||
#include "xp_write_xpwm_time.h"
|
#include "xp_write_xpwm_time.h"
|
||||||
|
#include "log_can.h"
|
||||||
|
#include "f281xpwm.h"
|
||||||
|
#include "v_pwm24.h"
|
||||||
#include "rotation_speed.h"
|
#include "rotation_speed.h"
|
||||||
#include "teta_calc.h"
|
#include "teta_calc.h"
|
||||||
#include "dq_to_alphabeta_cos.h"
|
#include "dq_to_alphabeta_cos.h"
|
||||||
#include "RS_Functions.h"
|
|
||||||
#include "x_parallel_bus.h"
|
|
||||||
#include "x_serial_bus.h"
|
|
||||||
#include "xp_rotation_sensor.h"
|
|
||||||
#include "xp_controller.h"
|
#include "xp_controller.h"
|
||||||
#include "Spartan2E_Functions.h"
|
#include "x_parallel_bus.h"
|
||||||
|
#include "xp_rotation_sensor.h"
|
||||||
#include "xPeriphSP6_loader.h"
|
#include "xPeriphSP6_loader.h"
|
||||||
#include "svgen_dq.h"
|
#include "Spartan2E_Functions.h"
|
||||||
|
#include "x_serial_bus.h"
|
||||||
|
#include "RS_Functions.h"
|
||||||
#include "detect_phase_break2.h"
|
#include "detect_phase_break2.h"
|
||||||
|
#include "log_to_memory.h"
|
||||||
#include "log_params.h"
|
#include "log_params.h"
|
||||||
#include "global_time.h"
|
#include "global_time.h"
|
||||||
#include "CAN_Setup.h"
|
#include "CAN_Setup.h"
|
||||||
#include "CRC_Functions.h"
|
#include "CRC_Functions.h"
|
||||||
#include "log_to_memory.h"
|
#include "svgen_dq.h"
|
||||||
#include "pid_reg3.h"
|
#include "pid_reg3.h"
|
||||||
#include "IQmathLib.h"
|
#include "IQmathLib.h"
|
||||||
#include "doors_control.h"
|
#include "doors_control.h"
|
||||||
@ -44,7 +44,7 @@
|
|||||||
#include "rmp_cntl_my1.h"
|
#include "rmp_cntl_my1.h"
|
||||||
|
|
||||||
|
|
||||||
// Ýêñòåðíû äëÿ äîñòóïà ê ïåðåìåííûì
|
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
extern int ADC0finishAddr;
|
extern int ADC0finishAddr;
|
||||||
extern int ADC0startAddr;
|
extern int ADC0startAddr;
|
||||||
extern int ADC1finishAddr;
|
extern int ADC1finishAddr;
|
||||||
@ -313,16 +313,9 @@ extern _iq zadan_Id_min;
|
|||||||
extern int zero_ADC[20];
|
extern int zero_ADC[20];
|
||||||
|
|
||||||
|
|
||||||
// Îïðåäåëåíèå ìàññèâà ñ óêàçàòåëÿìè íà ïåðåìåííûå äëÿ îòëàäêè
|
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||||
int DebugVar_Qnt = 8;
|
int DebugVar_Qnt = 1;
|
||||||
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
|
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
|
||||||
DebugVar_t dbg_vars[] = {\
|
DebugVar_t dbg_vars[] = {\
|
||||||
{(char *)&ADC0finishAddr , pt_int16 , t_iq_none , "ADC0finishAddr" }, \
|
{(char *)&project.cds_tk.plane_address , pt_uint16 , t_iq_none , "project.cds_tk.plane_address" }, \
|
||||||
{(char *)&ADC0startAddr , pt_int16 , t_iq_none , "ADC0startAddr" }, \
|
|
||||||
{(char *)&ADC1finishAddr , pt_int16 , t_iq_none , "ADC1finishAddr" }, \
|
|
||||||
{(char *)&ADC1startAddr , pt_int16 , t_iq_none , "ADC1startAddr" }, \
|
|
||||||
{(char *)&ADC2finishAddr , pt_int16 , t_iq_none , "ADC2finishAddr" }, \
|
|
||||||
{(char *)&ADC2startAddr , pt_int16 , t_iq_none , "ADC2startAddr" }, \
|
|
||||||
{(char *)&ADC_f , pt_int16 , t_iq_none , "ADC_f" }, \
|
|
||||||
{(char *)&ADC_sf , pt_int16 , t_iq_none , "ADC_sf" }, \
|
|
||||||
};
|
};
|
||||||
|
@ -192,7 +192,7 @@ def read_vars_from_xml(proj_path, xml_rel_path):
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def generate_vars_file(proj_path, xml_path, output_dir):
|
def generate_vars_file(proj_path, xml_path, output_dir):
|
||||||
output_dir = os.path.join(proj_path, output_dir)
|
output_dir = os.path.join(proj_path, output_dir)
|
||||||
os.makedirs(output_dir, exist_ok=True)
|
os.makedirs(output_dir, exist_ok=True)
|
||||||
output_path = os.path.join(output_dir, 'debug_vars.c')
|
output_path = os.path.join(output_dir, 'debug_vars.c')
|
||||||
@ -216,24 +216,37 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
|||||||
existing_debug_vars[varname] = line.strip()
|
existing_debug_vars[varname] = line.strip()
|
||||||
|
|
||||||
new_debug_vars = {}
|
new_debug_vars = {}
|
||||||
|
|
||||||
|
def is_true(val):
|
||||||
|
# Преобразуем значение к строке, если оно не None
|
||||||
|
# и сравниваем с 'true' в нижнем регистре
|
||||||
|
return str(val).lower() == 'true'
|
||||||
|
|
||||||
for vname, info in vars.items():
|
for vname, info in vars.items():
|
||||||
|
# Проверяем, что show_var и enable включены (строки как строки 'true')
|
||||||
|
if not is_true(info.get('show_var', 'false')):
|
||||||
|
continue
|
||||||
|
if not is_true(info.get('enable', 'false')):
|
||||||
|
continue
|
||||||
|
|
||||||
vtype = info["type"]
|
vtype = info["type"]
|
||||||
is_extern = info["extern"]
|
is_extern = info["extern"]
|
||||||
is_static = info.get("static", False)
|
is_static = info.get("static", False)
|
||||||
if is_static:
|
if is_static:
|
||||||
continue # пропускаем static переменные
|
continue # пропускаем static переменные
|
||||||
|
|
||||||
# Можно добавить проверку enable — если есть и False, пропускаем переменную
|
|
||||||
if "enable" in info and info["enable"] is False:
|
|
||||||
continue
|
|
||||||
|
|
||||||
path = info["file"]
|
path = info["file"]
|
||||||
|
|
||||||
if vname in existing_debug_vars:
|
if vname in existing_debug_vars:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
iq_type = get_iq_define(vtype)
|
iq_type = info.get('iq_type')
|
||||||
pt_type = map_type_to_pt(vtype, vname)
|
if not iq_type:
|
||||||
|
iq_type = get_iq_define(vtype)
|
||||||
|
|
||||||
|
pt_type = info.get('pt_type')
|
||||||
|
if not pt_type:
|
||||||
|
pt_type = map_type_to_pt(vtype, vname)
|
||||||
|
|
||||||
# Дополнительные поля, например комментарий
|
# Дополнительные поля, например комментарий
|
||||||
comment = info.get("comment", "")
|
comment = info.get("comment", "")
|
||||||
@ -371,4 +384,29 @@ Usage example:
|
|||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
def run_generate(proj_path, xml_path, output_dir):
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Normalize absolute paths
|
||||||
|
proj_path = os.path.abspath(proj_path)
|
||||||
|
xml_path_abs = os.path.abspath(xml_path)
|
||||||
|
output_dir_abs = os.path.abspath(output_dir)
|
||||||
|
|
||||||
|
# Проверка валидности путей
|
||||||
|
if not os.path.isdir(proj_path):
|
||||||
|
raise FileNotFoundError(f"Project path '{proj_path}' is not a directory or does not exist.")
|
||||||
|
|
||||||
|
if not xml_path_abs.startswith(proj_path + os.sep):
|
||||||
|
raise ValueError(f"XML path '{xml_path_abs}' is not inside the project path '{proj_path}'.")
|
||||||
|
|
||||||
|
if not output_dir_abs.startswith(proj_path + os.sep):
|
||||||
|
raise ValueError(f"Output directory '{output_dir_abs}' is not inside the project path '{proj_path}'.")
|
||||||
|
|
||||||
|
# Преобразуем к относительным путям относительно проекта
|
||||||
|
xml_path_rel = os.path.relpath(xml_path_abs, proj_path)
|
||||||
|
output_dir_rel = os.path.relpath(output_dir_abs, proj_path)
|
||||||
|
|
||||||
|
# Запускаем генерацию
|
||||||
|
generate_vars_file(proj_path, xml_path_rel, output_dir_rel)
|
||||||
|
140
scanVars.py
140
scanVars.py
@ -23,7 +23,7 @@ if hasattr(sys, '_MEIPASS'):
|
|||||||
dll_path = os.path.join(sys._MEIPASS, "libclang.dll")
|
dll_path = os.path.join(sys._MEIPASS, "libclang.dll")
|
||||||
cindex.Config.set_library_file(dll_path)
|
cindex.Config.set_library_file(dll_path)
|
||||||
else:
|
else:
|
||||||
cindex.Config.set_library_file(r"..\libclang.dll") # путь для запуска без упаковки
|
cindex.Config.set_library_file(r"build\libclang.dll") # путь для запуска без упаковки
|
||||||
|
|
||||||
index = cindex.Index.create()
|
index = cindex.Index.create()
|
||||||
PRINT_LEVEL = 2
|
PRINT_LEVEL = 2
|
||||||
@ -229,6 +229,23 @@ def resolve_typedef(typedefs, typename):
|
|||||||
current = typedefs[current]
|
current = typedefs[current]
|
||||||
return current
|
return current
|
||||||
|
|
||||||
|
|
||||||
|
def strip_ptr_and_array(typename):
|
||||||
|
"""
|
||||||
|
Убирает указатели и массивные скобки из типа,
|
||||||
|
чтобы найти базовый тип (например, для typedef или struct).
|
||||||
|
"""
|
||||||
|
if not isinstance(typename, str):
|
||||||
|
return typename
|
||||||
|
|
||||||
|
# Убираем [] и всё, что внутри скобок
|
||||||
|
typename = re.sub(r'\[.*?\]', '', typename)
|
||||||
|
|
||||||
|
# Убираем звёздочки и пробелы рядом
|
||||||
|
typename = typename.replace('*', '').strip()
|
||||||
|
|
||||||
|
return typename
|
||||||
|
|
||||||
def analyze_typedefs_and_struct(typedefs, structs):
|
def analyze_typedefs_and_struct(typedefs, structs):
|
||||||
optional_printf(PRINT_STATUS, "Resolving typedefs and expanding struct field types...")
|
optional_printf(PRINT_STATUS, "Resolving typedefs and expanding struct field types...")
|
||||||
|
|
||||||
@ -240,23 +257,6 @@ def analyze_typedefs_and_struct(typedefs, structs):
|
|||||||
return typename[len(prefix):]
|
return typename[len(prefix):]
|
||||||
return typename
|
return typename
|
||||||
|
|
||||||
def strip_ptr_and_array(typename):
|
|
||||||
"""
|
|
||||||
Убирает указатели и массивные скобки из типа,
|
|
||||||
чтобы найти базовый тип (например, для typedef или struct).
|
|
||||||
"""
|
|
||||||
if not isinstance(typename, str):
|
|
||||||
return typename
|
|
||||||
|
|
||||||
# Убираем [] и всё, что внутри скобок
|
|
||||||
typename = re.sub(r'\[.*?\]', '', typename)
|
|
||||||
|
|
||||||
# Убираем звёздочки и пробелы рядом
|
|
||||||
typename = typename.replace('*', '').strip()
|
|
||||||
|
|
||||||
return typename
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_typedef_rec(typename, depth=0):
|
def resolve_typedef_rec(typename, depth=0):
|
||||||
if depth > 50:
|
if depth > 50:
|
||||||
optional_printf(PRINT_ERROR, f"Possible typedef recursion limit reached on '{typename}'")
|
optional_printf(PRINT_ERROR, f"Possible typedef recursion limit reached on '{typename}'")
|
||||||
@ -458,6 +458,31 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
|
|||||||
return resolved_typedefs, resolved_structs
|
return resolved_typedefs, resolved_structs
|
||||||
|
|
||||||
|
|
||||||
|
def safe_parse_xml(xml_path):
|
||||||
|
"""
|
||||||
|
Безопасно парсит XML-файл.
|
||||||
|
|
||||||
|
Возвращает кортеж (root, tree) или (None, None) при ошибках.
|
||||||
|
"""
|
||||||
|
if not xml_path or not os.path.isfile(xml_path):
|
||||||
|
print(f"Файл '{xml_path}' не найден или путь пустой")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.getsize(xml_path) == 0:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
tree = ET.parse(xml_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
return root, tree
|
||||||
|
|
||||||
|
except ET.ParseError as e:
|
||||||
|
print(f"Ошибка парсинга XML файла '{xml_path}': {e}")
|
||||||
|
return None, None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Неожиданная ошибка при чтении XML файла '{xml_path}': {e}")
|
||||||
|
return None, None
|
||||||
|
|
||||||
def read_vars_from_xml(xml_path):
|
def read_vars_from_xml(xml_path):
|
||||||
xml_full_path = os.path.normpath(xml_path)
|
xml_full_path = os.path.normpath(xml_path)
|
||||||
vars_data = {}
|
vars_data = {}
|
||||||
@ -465,9 +490,10 @@ def read_vars_from_xml(xml_path):
|
|||||||
if not os.path.exists(xml_full_path):
|
if not os.path.exists(xml_full_path):
|
||||||
return vars_data # пусто, если файла нет
|
return vars_data # пусто, если файла нет
|
||||||
|
|
||||||
tree = ET.parse(xml_full_path)
|
root, tree = safe_parse_xml(xml_full_path)
|
||||||
root = tree.getroot()
|
if root is None:
|
||||||
|
return vars_data
|
||||||
|
|
||||||
vars_elem = root.find('variables')
|
vars_elem = root.find('variables')
|
||||||
if vars_elem is None:
|
if vars_elem is None:
|
||||||
return vars_data
|
return vars_data
|
||||||
@ -477,28 +503,20 @@ def read_vars_from_xml(xml_path):
|
|||||||
if not name:
|
if not name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
enable_text = var_elem.findtext('enable', 'false').lower()
|
def get_bool(tag, default='false'):
|
||||||
enable = (enable_text == 'true')
|
return var_elem.findtext(tag, default).lower() == 'true'
|
||||||
|
|
||||||
shortname = var_elem.findtext('shortname', name)
|
|
||||||
pt_type = var_elem.findtext('pt_type', '')
|
|
||||||
iq_type = var_elem.findtext('iq_type', '')
|
|
||||||
return_type = var_elem.findtext('return_type', 'int')
|
|
||||||
|
|
||||||
include_text = var_elem.findtext('include', 'false').lower()
|
|
||||||
include = (include_text == 'true')
|
|
||||||
|
|
||||||
extern_text = var_elem.findtext('extern', 'false').lower()
|
|
||||||
extern = (extern_text == 'true')
|
|
||||||
|
|
||||||
vars_data[name] = {
|
vars_data[name] = {
|
||||||
'enable': enable,
|
'show_var': get_bool('show_var'),
|
||||||
'shortname': shortname,
|
'enable': get_bool('enable'),
|
||||||
'pt_type': pt_type,
|
'shortname': var_elem.findtext('shortname', name),
|
||||||
'iq_type': iq_type,
|
'pt_type': var_elem.findtext('pt_type', ''),
|
||||||
'return_type': return_type,
|
'iq_type': var_elem.findtext('iq_type', ''),
|
||||||
'include': include,
|
'return_type': var_elem.findtext('return_type', 'int'),
|
||||||
'extern': extern,
|
'type': var_elem.findtext('type', 'unknown'),
|
||||||
|
'file': var_elem.findtext('file', ''),
|
||||||
|
'extern': get_bool('extern'),
|
||||||
|
'static': get_bool('static'),
|
||||||
}
|
}
|
||||||
|
|
||||||
return vars_data
|
return vars_data
|
||||||
@ -532,6 +550,7 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
|
|||||||
for name, info in unique_vars.items():
|
for name, info in unique_vars.items():
|
||||||
if name not in combined_vars:
|
if name not in combined_vars:
|
||||||
combined_vars[name] = {
|
combined_vars[name] = {
|
||||||
|
'show_var': info.get('enable', False),
|
||||||
'enable': info.get('enable', False),
|
'enable': info.get('enable', False),
|
||||||
'shortname': info.get('shortname', name),
|
'shortname': info.get('shortname', name),
|
||||||
'pt_type': info.get('pt_type', ''),
|
'pt_type': info.get('pt_type', ''),
|
||||||
@ -550,6 +569,7 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
|
|||||||
# Записываем переменные с новыми полями
|
# Записываем переменные с новыми полями
|
||||||
for name, info in combined_vars.items():
|
for name, info in combined_vars.items():
|
||||||
var_elem = ET.SubElement(vars_elem, "var", name=name)
|
var_elem = ET.SubElement(vars_elem, "var", name=name)
|
||||||
|
ET.SubElement(var_elem, "show_var").text = str(info.get('show_var', False)).lower()
|
||||||
ET.SubElement(var_elem, "enable").text = str(info.get('enable', False)).lower()
|
ET.SubElement(var_elem, "enable").text = str(info.get('enable', False)).lower()
|
||||||
ET.SubElement(var_elem, "shortname").text = info.get('shortname', name)
|
ET.SubElement(var_elem, "shortname").text = info.get('shortname', name)
|
||||||
ET.SubElement(var_elem, "pt_type").text = info.get('pt_type', '')
|
ET.SubElement(var_elem, "pt_type").text = info.get('pt_type', '')
|
||||||
@ -802,14 +822,12 @@ Usage example:
|
|||||||
externs = dict(sorted(externs.items()))
|
externs = dict(sorted(externs.items()))
|
||||||
typedefs = dict(sorted(typedefs.items()))
|
typedefs = dict(sorted(typedefs.items()))
|
||||||
structs = dict(sorted(structs.items()))
|
structs = dict(sorted(structs.items()))
|
||||||
print('create structs')
|
|
||||||
# Определяем путь к файлу с структурами рядом с output_xml
|
# Определяем путь к файлу с структурами рядом с output_xml
|
||||||
structs_xml = os.path.join(os.path.dirname(output_xml), "structs.xml")
|
structs_xml = os.path.join(os.path.dirname(output_xml), "structs.xml")
|
||||||
|
|
||||||
# Записываем структуры в structs_xml
|
# Записываем структуры в structs_xml
|
||||||
write_typedefs_and_structs_to_xml(proj_path, structs_xml, typedefs, structs)
|
write_typedefs_and_structs_to_xml(proj_path, structs_xml, typedefs, structs)
|
||||||
|
|
||||||
print('create vars')
|
|
||||||
# Передаем путь к structs.xml относительно proj_path в vars.xml
|
# Передаем путь к structs.xml относительно proj_path в vars.xml
|
||||||
# Модифицируем generate_xml_output так, чтобы принимать и путь к structs.xml (относительный)
|
# Модифицируем generate_xml_output так, чтобы принимать и путь к structs.xml (относительный)
|
||||||
generate_xml_output(proj_path, output_xml, vars, includes, externs, structs_xml, makefile_path)
|
generate_xml_output(proj_path, output_xml, vars, includes, externs, structs_xml, makefile_path)
|
||||||
@ -818,3 +836,39 @@ Usage example:
|
|||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
||||||
|
|
||||||
|
def run_scan(proj_path, makefile_path, output_xml, verbose=2):
|
||||||
|
global PRINT_LEVEL
|
||||||
|
PRINT_LEVEL = verbose
|
||||||
|
|
||||||
|
proj_path = os.path.normpath(proj_path)
|
||||||
|
makefile_path = os.path.normpath(makefile_path)
|
||||||
|
output_xml = os.path.normpath(output_xml)
|
||||||
|
|
||||||
|
# Проверка абсолютности путей
|
||||||
|
for path, name in [(proj_path, "Project path"), (makefile_path, "Makefile path"), (output_xml, "Output XML path")]:
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
raise ValueError(f"{name} '{path}' is not an absolute path.")
|
||||||
|
|
||||||
|
if not os.path.isdir(proj_path):
|
||||||
|
raise FileNotFoundError(f"Project path '{proj_path}' is not a directory or does not exist.")
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
vars = dict(sorted(vars.items()))
|
||||||
|
includes = get_sorted_headers(c_files, includes, include_dirs)
|
||||||
|
externs = dict(sorted(externs.items()))
|
||||||
|
typedefs = dict(sorted(typedefs.items()))
|
||||||
|
structs = dict(sorted(structs.items()))
|
||||||
|
|
||||||
|
print("[XML] Creating structs.xml...")
|
||||||
|
structs_xml = os.path.join(os.path.dirname(output_xml), "structs.xml")
|
||||||
|
write_typedefs_and_structs_to_xml(proj_path, structs_xml, typedefs, structs)
|
||||||
|
|
||||||
|
print("[XML] Creating vars.xml...")
|
||||||
|
generate_xml_output(proj_path, output_xml, vars, includes, externs, structs_xml, makefile_path)
|
||||||
|
708
setupVars.py
708
setupVars.py
@ -1,50 +1,78 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import re
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
from generateVars import map_type_to_pt, get_iq_define, type_map
|
from generateVars import map_type_to_pt, get_iq_define, type_map
|
||||||
from enum import IntEnum
|
from enum import IntEnum
|
||||||
|
from scanVars import *
|
||||||
|
from generateVars import *
|
||||||
|
|
||||||
from PySide6.QtWidgets import (
|
|
||||||
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
|
||||||
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
|
|
||||||
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit
|
|
||||||
)
|
|
||||||
from PySide6.QtGui import QTextCursor
|
|
||||||
from PySide6.QtCore import Qt, QProcess
|
|
||||||
|
|
||||||
class rows(IntEnum):
|
|
||||||
include = 0
|
|
||||||
name = 1
|
|
||||||
type = 2
|
|
||||||
pt_type = 3
|
|
||||||
iq_type = 4
|
|
||||||
ret_type = 5
|
|
||||||
short_name = 6
|
|
||||||
|
|
||||||
# 1. Парсим vars.xml
|
|
||||||
def make_absolute_path(path, base_path):
|
def make_absolute_path(path, base_path):
|
||||||
if not os.path.isabs(path):
|
if not os.path.isabs(path) and os.path.isdir(base_path):
|
||||||
return os.path.abspath(os.path.join(base_path, path))
|
try:
|
||||||
return os.path.abspath(path)
|
return os.path.abspath(os.path.join(base_path, path))
|
||||||
|
except Exception:
|
||||||
|
pass # На случай сбоя в os.path.join или abspath
|
||||||
|
elif os.path.isabs(path):
|
||||||
|
return os.path.abspath(path)
|
||||||
|
else:
|
||||||
|
return path
|
||||||
|
|
||||||
def parse_vars(filename):
|
|
||||||
tree = ET.parse(filename)
|
def make_relative_path(abs_path, base_path):
|
||||||
root = tree.getroot()
|
abs_path = os.path.abspath(abs_path)
|
||||||
|
base_path = os.path.abspath(base_path)
|
||||||
|
|
||||||
|
# Разбиваем на списки директорий
|
||||||
|
abs_parts = abs_path.split(os.sep)
|
||||||
|
base_parts = base_path.split(os.sep)
|
||||||
|
|
||||||
|
# Проверяем, является ли base_path настоящим префиксом пути (по папкам)
|
||||||
|
if abs_parts[:len(base_parts)] == base_parts:
|
||||||
|
rel_parts = abs_parts[len(base_parts):]
|
||||||
|
return "/".join(rel_parts)
|
||||||
|
|
||||||
|
# Иначе пробуем relpath
|
||||||
|
try:
|
||||||
|
return os.path.relpath(abs_path, base_path).replace("\\", "/")
|
||||||
|
except Exception:
|
||||||
|
return abs_path.replace("\\", "/")
|
||||||
|
|
||||||
|
|
||||||
|
def parse_vars(filename, typedef_map=None):
|
||||||
|
root, tree = safe_parse_xml(filename)
|
||||||
|
if root is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
if typedef_map is None:
|
||||||
|
typedef_map = {}
|
||||||
|
|
||||||
vars_list = []
|
vars_list = []
|
||||||
variables_elem = root.find('variables')
|
variables_elem = root.find('variables')
|
||||||
if variables_elem is not None:
|
if variables_elem is not None:
|
||||||
for var in variables_elem.findall('var'):
|
for var in variables_elem.findall('var'):
|
||||||
name = var.attrib.get('name', '')
|
name = var.attrib.get('name', '')
|
||||||
|
var_type = var.findtext('type', 'unknown').strip()
|
||||||
|
|
||||||
|
# Вычисляем 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)
|
||||||
|
|
||||||
|
iq_type = var.findtext('iq_type')
|
||||||
|
if not iq_type:
|
||||||
|
iq_type = get_iq_define(var_type)
|
||||||
|
|
||||||
vars_list.append({
|
vars_list.append({
|
||||||
'name': name,
|
'name': name,
|
||||||
|
'show_var': var.findtext('show_var', 'false'),
|
||||||
'enable': var.findtext('enable', 'false'),
|
'enable': var.findtext('enable', 'false'),
|
||||||
'shortname': var.findtext('shortname', name),
|
'shortname': var.findtext('shortname', name),
|
||||||
'pt_type': var.findtext('pt_type', 'pt_unknown'),
|
'pt_type': pt_type,
|
||||||
'iq_type': var.findtext('iq_type', 'iq_none'),
|
'iq_type': iq_type,
|
||||||
'return_type': var.findtext('return_type', 'int'),
|
'return_type': var.findtext('return_type', 'int'),
|
||||||
'type': var.findtext('type', 'unknown'),
|
'type': var_type,
|
||||||
'file': var.findtext('file', ''),
|
'file': var.findtext('file', ''),
|
||||||
'extern': var.findtext('extern', 'false') == 'true',
|
'extern': var.findtext('extern', 'false') == 'true',
|
||||||
'static': var.findtext('static', 'false') == 'true',
|
'static': var.findtext('static', 'false') == 'true',
|
||||||
@ -52,31 +80,50 @@ def parse_vars(filename):
|
|||||||
|
|
||||||
return vars_list
|
return vars_list
|
||||||
|
|
||||||
|
|
||||||
# 2. Парсим structSup.xml
|
# 2. Парсим structSup.xml
|
||||||
def parse_structs(filename):
|
def parse_structs(filename):
|
||||||
tree = ET.parse(filename)
|
root, tree = safe_parse_xml(filename)
|
||||||
root = tree.getroot()
|
if root is None:
|
||||||
|
return {}, {}
|
||||||
|
|
||||||
structs = {}
|
structs = {}
|
||||||
typedef_map = {}
|
typedef_map = {}
|
||||||
|
|
||||||
# --- Считываем структуры ---
|
def parse_struct_element(elem):
|
||||||
structs_elem = root.find('structs')
|
fields = {}
|
||||||
if structs_elem is not None:
|
|
||||||
for struct in structs_elem.findall('struct'):
|
|
||||||
name = struct.attrib['name']
|
|
||||||
fields = {}
|
|
||||||
for field in struct.findall('field'):
|
|
||||||
fname = field.attrib.get('name')
|
|
||||||
ftype = field.attrib.get('type')
|
|
||||||
if fname and ftype:
|
|
||||||
fields[fname] = ftype
|
|
||||||
structs[name] = fields
|
|
||||||
|
|
||||||
# --- Считываем typedef-ы ---
|
for field in elem.findall("field"):
|
||||||
typedefs_elem = root.find('typedefs')
|
fname = field.attrib.get("name")
|
||||||
|
ftype = field.attrib.get("type", "")
|
||||||
|
|
||||||
|
# Проверка на вложенную структуру
|
||||||
|
nested_struct_elem = field.find("struct")
|
||||||
|
if nested_struct_elem is not None:
|
||||||
|
# Рекурсивно парсим вложенную структуру и вставляем её как подсловарь
|
||||||
|
nested_fields = parse_struct_element(nested_struct_elem)
|
||||||
|
fields[fname] = nested_fields
|
||||||
|
else:
|
||||||
|
# Обычное поле
|
||||||
|
fields[fname] = ftype
|
||||||
|
|
||||||
|
return fields
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
structs_elem = root.find("structs")
|
||||||
|
if structs_elem is not None:
|
||||||
|
for struct in structs_elem.findall("struct"):
|
||||||
|
name = struct.attrib.get("name")
|
||||||
|
if name and name not in structs:
|
||||||
|
fields = parse_struct_element(struct)
|
||||||
|
structs[name] = fields
|
||||||
|
|
||||||
|
# typedefs без изменений
|
||||||
|
typedefs_elem = root.find("typedefs")
|
||||||
if typedefs_elem is not None:
|
if typedefs_elem is not None:
|
||||||
for typedef in typedefs_elem.findall('typedef'):
|
for typedef in typedefs_elem.findall("typedef"):
|
||||||
name = typedef.attrib.get('name')
|
name = typedef.attrib.get('name')
|
||||||
target_type = typedef.attrib.get('type')
|
target_type = typedef.attrib.get('type')
|
||||||
if name and target_type:
|
if name and target_type:
|
||||||
@ -85,486 +132,95 @@ def parse_structs(filename):
|
|||||||
return structs, typedef_map
|
return structs, typedef_map
|
||||||
|
|
||||||
|
|
||||||
|
def safe_parse_xml(xml_path):
|
||||||
|
"""
|
||||||
|
Безопасно парсит XML-файл.
|
||||||
|
|
||||||
|
Возвращает кортеж (root, tree) или (None, None) при ошибках.
|
||||||
|
"""
|
||||||
|
if not xml_path or not os.path.isfile(xml_path):
|
||||||
|
#print(f"Файл '{xml_path}' не найден или путь пустой")
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
try:
|
||||||
|
if os.path.getsize(xml_path) == 0:
|
||||||
|
return None, None
|
||||||
|
|
||||||
|
tree = ET.parse(xml_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
return root, tree
|
||||||
|
|
||||||
|
except ET.ParseError as e:
|
||||||
|
print(f"Ошибка парсинга XML файла '{xml_path}': {e}")
|
||||||
|
return None, None
|
||||||
|
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):
|
||||||
|
"""
|
||||||
|
Рекурсивно разворачивает структуру.
|
||||||
|
type_str может быть строкой (имя типа) или словарём (вложенная структура).
|
||||||
|
"""
|
||||||
|
if depth > 10:
|
||||||
|
return []
|
||||||
|
|
||||||
|
# Если тип уже является вложенной структурой
|
||||||
|
if isinstance(type_str, dict):
|
||||||
|
fields = type_str
|
||||||
|
else:
|
||||||
|
base_type = strip_ptr_and_array(type_str)
|
||||||
|
fields = structs.get(base_type)
|
||||||
|
if not isinstance(fields, dict):
|
||||||
|
return []
|
||||||
|
|
||||||
|
children = []
|
||||||
|
for field_name, field_type in fields.items():
|
||||||
|
full_name = f"{prefix}.{field_name}"
|
||||||
|
child = {
|
||||||
|
'name': full_name,
|
||||||
|
'type': field_type,
|
||||||
|
'pt_type': '',
|
||||||
|
'file': var_attrs.get('file'),
|
||||||
|
'extern': var_attrs.get('extern'),
|
||||||
|
'static': var_attrs.get('static'),
|
||||||
|
}
|
||||||
|
|
||||||
|
# Рекурсивно разворачиваем, если field_type — вложенная структура
|
||||||
|
subchildren = expand_struct_recursively(full_name, field_type, structs, typedefs, var_attrs, depth + 1)
|
||||||
|
if subchildren:
|
||||||
|
child['children'] = subchildren
|
||||||
|
|
||||||
|
children.append(child)
|
||||||
|
|
||||||
|
return children
|
||||||
|
|
||||||
|
|
||||||
|
def expand_vars(vars_list, structs, typedefs):
|
||||||
|
"""
|
||||||
|
Раскрывает структуры и массивы структур в деревья.
|
||||||
|
"""
|
||||||
|
expanded = []
|
||||||
|
|
||||||
|
for var in vars_list:
|
||||||
|
pt_type = var.get('pt_type', '')
|
||||||
|
raw_type = var.get('type', '')
|
||||||
|
base_type = strip_ptr_and_array(raw_type)
|
||||||
|
|
||||||
|
fields = structs.get(base_type)
|
||||||
|
|
||||||
|
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)
|
||||||
|
expanded.append(new_var)
|
||||||
|
|
||||||
|
elif pt_type == 'pt_struct' 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)
|
||||||
|
|
||||||
|
else:
|
||||||
|
expanded.append(var)
|
||||||
|
|
||||||
|
return expanded
|
||||||
|
|
||||||
class ProcessOutputWindow(QWidget):
|
|
||||||
def __init__(self, command, args):
|
|
||||||
super().__init__()
|
|
||||||
self.setWindowTitle("Поиск переменных...")
|
|
||||||
self.resize(600, 400)
|
|
||||||
|
|
||||||
self.layout = QVBoxLayout(self)
|
|
||||||
self.output_edit = QTextEdit()
|
|
||||||
self.output_edit.setReadOnly(True)
|
|
||||||
self.layout.addWidget(self.output_edit)
|
|
||||||
|
|
||||||
self.btn_close = QPushButton("Закрыть")
|
|
||||||
self.btn_close.setEnabled(False)
|
|
||||||
self.btn_close.clicked.connect(self.close)
|
|
||||||
self.layout.addWidget(self.btn_close)
|
|
||||||
|
|
||||||
self.process = QProcess(self)
|
|
||||||
self.process.setProcessChannelMode(QProcess.MergedChannels)
|
|
||||||
self.process.readyReadStandardOutput.connect(self.handle_stdout)
|
|
||||||
self.process.finished.connect(self.process_finished)
|
|
||||||
|
|
||||||
self.process.start(command, args)
|
|
||||||
|
|
||||||
def handle_stdout(self):
|
|
||||||
data = self.process.readAllStandardOutput()
|
|
||||||
text = data.data().decode('utf-8')
|
|
||||||
self.output_edit.append(text)
|
|
||||||
|
|
||||||
def process_finished(self):
|
|
||||||
self.output_edit.append("\n--- Процесс завершён ---")
|
|
||||||
self.btn_close.setEnabled(True)
|
|
||||||
|
|
||||||
# 3. UI: таблица с переменными
|
|
||||||
class VarEditor(QWidget):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
self.vars_list = []
|
|
||||||
self.structs = {}
|
|
||||||
self.typedef_map = {}
|
|
||||||
self.structs_xml_path = None # сюда запишем путь из <structs_xml>
|
|
||||||
self.initUI()
|
|
||||||
|
|
||||||
def initUI(self):
|
|
||||||
self.setWindowTitle("Variable Editor")
|
|
||||||
|
|
||||||
# --- Поля ввода пути проекта и XML ---
|
|
||||||
|
|
||||||
# XML Output
|
|
||||||
xml_layout = QHBoxLayout()
|
|
||||||
xml_layout.addWidget(QLabel("XML Output:"))
|
|
||||||
self.xml_output_edit = QLineEdit()
|
|
||||||
xml_layout.addWidget(self.xml_output_edit)
|
|
||||||
self.xml_output_edit.returnPressed.connect(self.read_xml_file)
|
|
||||||
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("Project Path:"))
|
|
||||||
self.proj_path_edit = QLineEdit()
|
|
||||||
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 (relative path):"))
|
|
||||||
self.makefile_edit = QLineEdit()
|
|
||||||
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("Source Output File:"))
|
|
||||||
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("Обновить данные о переменных")
|
|
||||||
self.btn_update_vars.clicked.connect(self.update_vars_data)
|
|
||||||
|
|
||||||
# Таблица переменных
|
|
||||||
self.table = QTableWidget(len(self.vars_list), 7)
|
|
||||||
self.table.setHorizontalHeaderLabels([
|
|
||||||
'Include',
|
|
||||||
'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)
|
|
||||||
|
|
||||||
# Основной 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(btn_save)
|
|
||||||
layout.addLayout(source_output_layout)
|
|
||||||
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
def update_vars_data(self):
|
|
||||||
proj_path = self.proj_path_edit.text().strip()
|
|
||||||
xml_path = self.xml_output_edit.text().strip()
|
|
||||||
|
|
||||||
proj_path = os.path.abspath(self.proj_path_edit.text().strip())
|
|
||||||
makefile_path = make_absolute_path(self.makefile_edit.text().strip(), proj_path)
|
|
||||||
|
|
||||||
if not proj_path or not xml_path:
|
|
||||||
QMessageBox.warning(self, "Ошибка", "Пожалуйста, укажите пути проекта и XML.")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not os.path.isfile(makefile_path):
|
|
||||||
QMessageBox.warning(self, "Ошибка", f"Makefile не найден по пути:\n{makefile_path}")
|
|
||||||
return
|
|
||||||
|
|
||||||
scanvars_exe = "scanVars.exe"
|
|
||||||
args = [proj_path, makefile_path, xml_path]
|
|
||||||
|
|
||||||
# Создаем и показываем окно с выводом процесса
|
|
||||||
self.proc_win = ProcessOutputWindow(scanvars_exe, args)
|
|
||||||
self.proc_win.show()
|
|
||||||
|
|
||||||
# Можно подписаться на сигнал завершения процесса, если хочешь обновить UI после
|
|
||||||
self.proc_win.process.finished.connect(lambda exitCode, exitStatus: self.after_scanvars_finished(exitCode, exitStatus))
|
|
||||||
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# Здесь нужно указать абсолютные пути к проекту, xml и output (замени на свои)
|
|
||||||
proj_path = self.proj_path_edit.text().strip()
|
|
||||||
xml_path = self.xml_output_edit.text().strip()
|
|
||||||
output_dir_c_file = self.source_output_edit.text().strip()
|
|
||||||
|
|
||||||
# Путь к твоему exe, например
|
|
||||||
exe_path = r"generateVars.exe"
|
|
||||||
|
|
||||||
# Формируем список аргументов
|
|
||||||
cmd = [exe_path, proj_path, xml_path, output_dir_c_file]
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Запускаем exe и ждём завершения
|
|
||||||
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print("Error calling script:", e)
|
|
||||||
print("stderr:", e.stderr)
|
|
||||||
|
|
||||||
def browse_proj_path(self):
|
|
||||||
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта")
|
|
||||||
if dir_path:
|
|
||||||
self.proj_path_edit.setText(dir_path)
|
|
||||||
|
|
||||||
def read_xml_file(self):
|
|
||||||
file_path = self.xml_output_edit.text().strip()
|
|
||||||
if file_path and not os.path.isfile(file_path):
|
|
||||||
return
|
|
||||||
|
|
||||||
self.vars_list = parse_vars(file_path)
|
|
||||||
try:
|
|
||||||
tree = ET.parse(file_path)
|
|
||||||
root = tree.getroot()
|
|
||||||
|
|
||||||
proj_path = self.proj_path_edit.text().strip()
|
|
||||||
|
|
||||||
if not 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):
|
|
||||||
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(proj_path):
|
|
||||||
QMessageBox.warning(
|
|
||||||
self,
|
|
||||||
"Внимание",
|
|
||||||
f"Указанный путь к проекту не существует:\n{proj_path}\n"
|
|
||||||
"Пожалуйста, исправьте путь в поле 'Project Path'."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# --- makefile_path из атрибута ---
|
|
||||||
makefile_path = root.attrib.get('makefile_path', '').strip()
|
|
||||||
makefile_path_full = make_absolute_path(makefile_path, proj_path)
|
|
||||||
if makefile_path_full and os.path.isfile(makefile_path_full):
|
|
||||||
self.makefile_edit.setText(makefile_path)
|
|
||||||
|
|
||||||
# --- structs_path из атрибута ---
|
|
||||||
structs_path = root.attrib.get('structs_path', '').strip()
|
|
||||||
structs_path_full = make_absolute_path(structs_path, proj_path)
|
|
||||||
if structs_path_full and os.path.isfile(structs_path_full):
|
|
||||||
self.structs_xml_path = structs_path_full
|
|
||||||
self.structs, self.typedef_map = parse_structs(structs_path_full)
|
|
||||||
else:
|
|
||||||
self.structs_xml_path = None
|
|
||||||
|
|
||||||
self.update_table()
|
|
||||||
except Exception as e:
|
|
||||||
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
|
|
||||||
|
|
||||||
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.read_xml_file()
|
|
||||||
|
|
||||||
|
|
||||||
def browse_xml_struct(self):
|
|
||||||
file_path, _ = QFileDialog.getSaveFileName(self, "Выберите XML файл", filter="XML files (*.xml);;All Files (*)")
|
|
||||||
if file_path:
|
|
||||||
self.xml_output_edit.setText(file_path)
|
|
||||||
if os.path.isfile(file_path):
|
|
||||||
self.structs, self.typedef_map = parse_structs(file_path)
|
|
||||||
|
|
||||||
def browse_makefile(self):
|
|
||||||
file_path, _ = QFileDialog.getOpenFileName(
|
|
||||||
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)"
|
|
||||||
)
|
|
||||||
if file_path:
|
|
||||||
self.makefile_edit.setText(file_path)
|
|
||||||
|
|
||||||
def browse_source_output(self):
|
|
||||||
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
|
|
||||||
if dir_path:
|
|
||||||
self.source_output_edit.setText(dir_path)
|
|
||||||
|
|
||||||
def after_scanvars_finished(self, exitCode, exitStatus):
|
|
||||||
xml_path = self.xml_output_edit.text().strip()
|
|
||||||
if not os.path.isfile(xml_path):
|
|
||||||
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {xml_path}")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Читаем структуры, если задан путь
|
|
||||||
if self.structs_xml_path and os.path.isfile(self.structs_xml_path):
|
|
||||||
try:
|
|
||||||
self.structs, self.typedef_map = parse_structs(self.structs_xml_path)
|
|
||||||
# При необходимости обновите UI или сделайте что-то с self.structs
|
|
||||||
except Exception as e:
|
|
||||||
QMessageBox.warning(self, "Внимание", f"Не удалось загрузить структуры из {self.structs_xml_path}:\n{e}")
|
|
||||||
|
|
||||||
self.vars_list = parse_vars(xml_path)
|
|
||||||
self.update_table()
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
|
|
||||||
|
|
||||||
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)]
|
|
||||||
self.table.setRowCount(len(self.vars_list))
|
|
||||||
|
|
||||||
for row, var in enumerate(self.vars_list):
|
|
||||||
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 = map_type_to_pt(var['type'], var['name'], self.typedef_map)
|
|
||||||
display_type = internal_type.replace('pt_', '')
|
|
||||||
if display_type in self.display_type_options:
|
|
||||||
pt_type_combo.setCurrentText(display_type)
|
|
||||||
else:
|
|
||||||
pt_type_combo.addItem(display_type)
|
|
||||||
pt_type_combo.setCurrentText(display_type)
|
|
||||||
self.table.setCellWidget(row, rows.pt_type, pt_type_combo)
|
|
||||||
|
|
||||||
iq_combo = QComboBox()
|
|
||||||
iq_combo.addItems(iq_types)
|
|
||||||
iq_type = get_iq_define(var['type']) # Получаем IQ-тип, например 'iq24'
|
|
||||||
display_type = iq_type.replace('t_', '')
|
|
||||||
if iq_type in iq_types:
|
|
||||||
iq_combo.setCurrentText(display_type)
|
|
||||||
else:
|
|
||||||
iq_combo.addItem(display_type)
|
|
||||||
iq_combo.setCurrentText(display_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.save_table_to_xml)
|
|
||||||
name_edit.textChanged.connect(self.save_table_to_xml)
|
|
||||||
pt_type_combo.currentTextChanged.connect(self.save_table_to_xml)
|
|
||||||
iq_combo.currentTextChanged.connect(self.save_table_to_xml)
|
|
||||||
ret_combo.currentTextChanged.connect(self.save_table_to_xml)
|
|
||||||
short_name_edit.textChanged.connect(self.save_table_to_xml)
|
|
||||||
|
|
||||||
|
|
||||||
def save_table_to_xml(self):
|
|
||||||
def make_relative_path(abs_path, base_path):
|
|
||||||
try:
|
|
||||||
return os.path.relpath(abs_path, base_path).replace("\\", "/")
|
|
||||||
except ValueError:
|
|
||||||
return abs_path.replace("\\", "/")
|
|
||||||
|
|
||||||
xml_path = self.xml_output_edit.text().strip()
|
|
||||||
proj_path = self.proj_path_edit.text().strip()
|
|
||||||
makefile_path = self.makefile_edit.text().strip()
|
|
||||||
|
|
||||||
if not xml_path or not os.path.isfile(xml_path):
|
|
||||||
print("XML файл не найден или путь пустой")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
tree = ET.parse(xml_path)
|
|
||||||
root = tree.getroot()
|
|
||||||
|
|
||||||
# Обновим атрибуты с относительными путями
|
|
||||||
if os.path.isdir(proj_path):
|
|
||||||
root.set("proj_path", proj_path.replace("\\", "/"))
|
|
||||||
|
|
||||||
if os.path.isfile(makefile_path):
|
|
||||||
rel_makefile = make_relative_path(makefile_path, proj_path)
|
|
||||||
root.set("makefile_path", rel_makefile)
|
|
||||||
|
|
||||||
if self.structs_xml_path and os.path.isfile(self.structs_xml_path):
|
|
||||||
rel_struct_path = make_relative_path(self.structs_xml_path, proj_path)
|
|
||||||
root.set("structs_path", rel_struct_path)
|
|
||||||
|
|
||||||
|
|
||||||
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] = {
|
|
||||||
'type': var_elem.findtext('type', ''),
|
|
||||||
'file': var_elem.findtext('file', ''),
|
|
||||||
'extern': var_elem.findtext('extern', ''),
|
|
||||||
'static': var_elem.findtext('static', '')
|
|
||||||
}
|
|
||||||
# Собираем данные из таблицы
|
|
||||||
updated_vars = []
|
|
||||||
for row in range(self.table.rowCount()):
|
|
||||||
cb = self.table.cellWidget(row, 0)
|
|
||||||
name_edit = self.table.cellWidget(row, 1)
|
|
||||||
pt_type_combo = self.table.cellWidget(row, 3)
|
|
||||||
iq_combo = self.table.cellWidget(row, 4)
|
|
||||||
ret_combo = self.table.cellWidget(row, 5)
|
|
||||||
short_name_edit = self.table.cellWidget(row, 6)
|
|
||||||
|
|
||||||
var_name = name_edit.text()
|
|
||||||
|
|
||||||
# Берём оригинальные type и file из словаря, если есть
|
|
||||||
orig_type = original_info.get(var_name, {}).get('type', '')
|
|
||||||
orig_file = original_info.get(var_name, {}).get('file', '')
|
|
||||||
orig_extern = original_info.get(var_name, {}).get('extern', '')
|
|
||||||
orig_static = original_info.get(var_name, {}).get('static', '')
|
|
||||||
|
|
||||||
updated_vars.append({
|
|
||||||
'name': var_name,
|
|
||||||
'enable': cb.isChecked(),
|
|
||||||
'shortname': short_name_edit.text(),
|
|
||||||
'pt_type': 'pt_' + pt_type_combo.currentText(),
|
|
||||||
'iq_type': iq_combo.currentText(),
|
|
||||||
'return_type': ret_combo.currentText() or 'int',
|
|
||||||
'type': orig_type,
|
|
||||||
'file': orig_file,
|
|
||||||
'extern': orig_extern,
|
|
||||||
'static': orig_static,
|
|
||||||
})
|
|
||||||
|
|
||||||
# Обновляем или добавляем по одному var в XML
|
|
||||||
for v in updated_vars:
|
|
||||||
var_elem = None
|
|
||||||
for ve in vars_elem.findall('var'):
|
|
||||||
if ve.attrib.get('name') == v['name']:
|
|
||||||
var_elem = ve
|
|
||||||
break
|
|
||||||
if var_elem is None:
|
|
||||||
var_elem = ET.SubElement(vars_elem, 'var', {'name': v['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)
|
|
||||||
|
|
||||||
set_sub_elem_text(var_elem, 'enable', 'true' if v['enable'] else 'false')
|
|
||||||
set_sub_elem_text(var_elem, 'shortname', v['shortname'])
|
|
||||||
set_sub_elem_text(var_elem, 'pt_type', v['pt_type'])
|
|
||||||
set_sub_elem_text(var_elem, 'iq_type', v['iq_type'])
|
|
||||||
set_sub_elem_text(var_elem, 'return_type', v['return_type'])
|
|
||||||
set_sub_elem_text(var_elem, 'type', v['type'])
|
|
||||||
set_sub_elem_text(var_elem, 'file', v['file'])
|
|
||||||
set_sub_elem_text(var_elem, 'extern', v['extern'])
|
|
||||||
set_sub_elem_text(var_elem, 'static', v['static'])
|
|
||||||
|
|
||||||
# Сохраняем изменения
|
|
||||||
tree.write(xml_path, encoding='utf-8', xml_declaration=True)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Ошибка при сохранении XML: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
app = QApplication(sys.argv)
|
|
||||||
|
|
||||||
editor = VarEditor()
|
|
||||||
editor.resize(900, 600)
|
|
||||||
editor.show()
|
|
||||||
|
|
||||||
sys.exit(app.exec())
|
|
||||||
|
|
||||||
|
|
666
setupVars_GUI.py
Normal file
666
setupVars_GUI.py
Normal file
@ -0,0 +1,666 @@
|
|||||||
|
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
|
||||||
|
from setupVars import *
|
||||||
|
from VariableSelector import *
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||||||
|
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||||
|
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
|
||||||
|
QDialog, QTreeWidget, QTreeWidgetItem
|
||||||
|
)
|
||||||
|
from PySide6.QtGui import QTextCursor, QKeyEvent
|
||||||
|
from PySide6.QtCore import Qt, QProcess, QObject, Signal, QTimer
|
||||||
|
|
||||||
|
class rows(IntEnum):
|
||||||
|
include = 0
|
||||||
|
name = 1
|
||||||
|
type = 2
|
||||||
|
pt_type = 3
|
||||||
|
iq_type = 4
|
||||||
|
ret_type = 5
|
||||||
|
short_name = 6
|
||||||
|
|
||||||
|
|
||||||
|
class EmittingStream(QObject):
|
||||||
|
text_written = Signal(str)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self._buffer = ""
|
||||||
|
|
||||||
|
def write(self, text):
|
||||||
|
self._buffer += text
|
||||||
|
while '\n' in self._buffer:
|
||||||
|
line, self._buffer = self._buffer.split('\n', 1)
|
||||||
|
# Отправляем строку без '\n'
|
||||||
|
self.text_written.emit(line)
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
if self._buffer:
|
||||||
|
self.text_written.emit(self._buffer)
|
||||||
|
self._buffer = ""
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessOutputWindowDummy(QWidget):
|
||||||
|
def __init__(self, on_done_callback):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Поиск переменных...")
|
||||||
|
self.resize(600, 400)
|
||||||
|
|
||||||
|
self.layout = QVBoxLayout(self)
|
||||||
|
self.output_edit = QTextEdit()
|
||||||
|
self.output_edit.setReadOnly(True)
|
||||||
|
self.layout.addWidget(self.output_edit)
|
||||||
|
|
||||||
|
self.btn_close = QPushButton("Закрыть")
|
||||||
|
self.btn_close.setEnabled(False)
|
||||||
|
self.layout.addWidget(self.btn_close)
|
||||||
|
|
||||||
|
self.btn_close.clicked.connect(self.__handle_done)
|
||||||
|
self._on_done_callback = on_done_callback
|
||||||
|
|
||||||
|
def __handle_done(self):
|
||||||
|
if self._on_done_callback:
|
||||||
|
self._on_done_callback()
|
||||||
|
self.close()
|
||||||
|
|
||||||
|
def append_text(self, text):
|
||||||
|
cursor = self.output_edit.textCursor()
|
||||||
|
cursor.movePosition(QTextCursor.End)
|
||||||
|
for line in text.splitlines():
|
||||||
|
self.output_edit.append(line)
|
||||||
|
self.output_edit.setTextCursor(cursor)
|
||||||
|
self.output_edit.ensureCursorVisible()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# 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.initUI()
|
||||||
|
|
||||||
|
def initUI(self):
|
||||||
|
self.setWindowTitle("Variable Editor")
|
||||||
|
|
||||||
|
# --- Поля ввода пути проекта и XML ---
|
||||||
|
|
||||||
|
# XML Output
|
||||||
|
xml_layout = QHBoxLayout()
|
||||||
|
xml_layout.addWidget(QLabel("XML Output:"))
|
||||||
|
self.xml_output_edit = QLineEdit()
|
||||||
|
self.xml_output_edit.returnPressed.connect(self.read_xml_file)
|
||||||
|
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("Project Path:"))
|
||||||
|
self.proj_path_edit = QLineEdit()
|
||||||
|
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 (relative path):"))
|
||||||
|
self.makefile_edit = QLineEdit()
|
||||||
|
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("Source Output File:"))
|
||||||
|
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("Обновить данные о переменных")
|
||||||
|
self.btn_update_vars.clicked.connect(self.update_vars_data)
|
||||||
|
|
||||||
|
# Таблица переменных
|
||||||
|
self.table = QTableWidget(len(self.vars_list), 7)
|
||||||
|
self.table.setHorizontalHeaderLabels([
|
||||||
|
'Include',
|
||||||
|
'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)
|
||||||
|
|
||||||
|
# Кнопка добавления переменных
|
||||||
|
self.btn_add_vars = QPushButton("Add Variables")
|
||||||
|
self.btn_add_vars.clicked.connect(self.__open_variable_selector)
|
||||||
|
|
||||||
|
|
||||||
|
# Основной 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)
|
||||||
|
|
||||||
|
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 = 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 = safe_parse_xml(xml_path)
|
||||||
|
if root is None:
|
||||||
|
return
|
||||||
|
# --- structs_path из атрибута ---
|
||||||
|
structs_path = root.attrib.get('structs_path', '').strip()
|
||||||
|
structs_path_full = 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.proc_win.show()
|
||||||
|
|
||||||
|
self.emitting_stream = EmittingStream()
|
||||||
|
self.emitting_stream.text_written.connect(self.proc_win.append_text)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Здесь нужно указать абсолютные пути к проекту, xml и output (замени на свои)
|
||||||
|
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 успешно сгенерирован.")
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Ошибка при генерации", str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def read_xml_file(self):
|
||||||
|
self.update_all_paths()
|
||||||
|
if self.xml_path and not os.path.isfile(self.xml_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
root, tree = 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'."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- makefile_path из атрибута ---
|
||||||
|
makefile_path = root.attrib.get('makefile_path', '').strip()
|
||||||
|
makefile_path_full = 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)
|
||||||
|
self.makefile_path = makefile_path_full
|
||||||
|
|
||||||
|
# --- structs_path из атрибута ---
|
||||||
|
structs_path = root.attrib.get('structs_path', '').strip()
|
||||||
|
structs_path_full = 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 = parse_structs(structs_path_full)
|
||||||
|
else:
|
||||||
|
self.structs_path = None
|
||||||
|
|
||||||
|
self.vars_list = parse_vars(self.xml_path, self.typedef_map)
|
||||||
|
self.update_table()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 = 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
|
||||||
|
self.read_xml_file()
|
||||||
|
|
||||||
|
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 = 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()
|
||||||
|
|
||||||
|
def __on_proj_path_changed(self):
|
||||||
|
self.proj_path = self.get_proj_path()
|
||||||
|
|
||||||
|
def __on_makefile_path_changed(self):
|
||||||
|
self.makefile_path = self.get_makefile_path()
|
||||||
|
if self.makefile_path and self.proj_path:
|
||||||
|
path = make_relative_path(self.makefile_path, self.proj_path)
|
||||||
|
self.makefile_edit.setText(path)
|
||||||
|
self.makefile_path = path
|
||||||
|
|
||||||
|
|
||||||
|
def __after_scanvars_finished(self):
|
||||||
|
xml_path = self.xml_output_edit.text().strip()
|
||||||
|
if not os.path.isfile(xml_path):
|
||||||
|
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {xml_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Читаем структуры, если задан путь
|
||||||
|
if self.structs_path and os.path.isfile(self.structs_path):
|
||||||
|
try:
|
||||||
|
self.structs, self.typedef_map = parse_structs(self.structs_path)
|
||||||
|
# При необходимости обновите UI или сделайте что-то с self.structs
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(self, "Внимание", f"Не удалось загрузить структуры из {self.structs_path}:\n{e}")
|
||||||
|
|
||||||
|
self.vars_list = parse_vars(xml_path)
|
||||||
|
self.update_table()
|
||||||
|
|
||||||
|
|
||||||
|
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.update_table()
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
if dlg.exec():
|
||||||
|
self.write_to_xml()
|
||||||
|
self.read_xml_file()
|
||||||
|
self.update_table()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
for row, var in enumerate(filtered_vars):
|
||||||
|
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()
|
||||||
|
|
||||||
|
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 = 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 = 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 = 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.read_table()}
|
||||||
|
# Все переменные (в том числе новые, которых нет в XML)
|
||||||
|
all_vars_by_name = {v['name']: v for v in self.vars_list}
|
||||||
|
|
||||||
|
# Объединённый список переменных для записи
|
||||||
|
all_names = set(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
|
||||||
|
|
||||||
|
# Ищем уже существующий <var> в 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)
|
||||||
|
|
||||||
|
set_sub_elem_text(var_elem, 'show_var', v.get('show_var', 'false'))
|
||||||
|
set_sub_elem_text(var_elem, 'enable', v.get('enable', 'false'))
|
||||||
|
set_sub_elem_text(var_elem, 'shortname', v['shortname'])
|
||||||
|
set_sub_elem_text(var_elem, 'pt_type', v['pt_type'])
|
||||||
|
set_sub_elem_text(var_elem, 'iq_type', v['iq_type'])
|
||||||
|
set_sub_elem_text(var_elem, 'return_type', v['return_type'])
|
||||||
|
set_sub_elem_text(var_elem, 'type', v['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)
|
||||||
|
|
||||||
|
|
||||||
|
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}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
editor = VarEditor()
|
||||||
|
editor.resize(900, 600)
|
||||||
|
editor.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
581
setupVars_out.py
Normal file
581
setupVars_out.py
Normal file
@ -0,0 +1,581 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from generateVars import map_type_to_pt, get_iq_define, type_map
|
||||||
|
from enum import IntEnum
|
||||||
|
from scanVars import run_scan
|
||||||
|
from generateVars import run_generate
|
||||||
|
|
||||||
|
from PySide6.QtWidgets import (
|
||||||
|
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||||||
|
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||||
|
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit
|
||||||
|
)
|
||||||
|
from PySide6.QtGui import QTextCursor
|
||||||
|
from PySide6.QtCore import Qt, QProcess
|
||||||
|
|
||||||
|
class rows(IntEnum):
|
||||||
|
include = 0
|
||||||
|
name = 1
|
||||||
|
type = 2
|
||||||
|
pt_type = 3
|
||||||
|
iq_type = 4
|
||||||
|
ret_type = 5
|
||||||
|
short_name = 6
|
||||||
|
|
||||||
|
# 1. Парсим vars.xml
|
||||||
|
def make_absolute_path(path, base_path):
|
||||||
|
if not os.path.isabs(path):
|
||||||
|
return os.path.abspath(os.path.join(base_path, path))
|
||||||
|
return os.path.abspath(path)
|
||||||
|
|
||||||
|
def parse_vars(filename):
|
||||||
|
tree = ET.parse(filename)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
vars_list = []
|
||||||
|
variables_elem = root.find('variables')
|
||||||
|
if variables_elem is not None:
|
||||||
|
for var in variables_elem.findall('var'):
|
||||||
|
name = var.attrib.get('name', '')
|
||||||
|
vars_list.append({
|
||||||
|
'name': name,
|
||||||
|
'enable': var.findtext('enable', 'false'),
|
||||||
|
'shortname': var.findtext('shortname', name),
|
||||||
|
'pt_type': var.findtext('pt_type', 'pt_unknown'),
|
||||||
|
'iq_type': var.findtext('iq_type', 'iq_none'),
|
||||||
|
'return_type': var.findtext('return_type', 'int'),
|
||||||
|
'type': var.findtext('type', 'unknown'),
|
||||||
|
'file': var.findtext('file', ''),
|
||||||
|
'extern': var.findtext('extern', 'false') == 'true',
|
||||||
|
'static': var.findtext('static', 'false') == 'true',
|
||||||
|
})
|
||||||
|
|
||||||
|
return vars_list
|
||||||
|
|
||||||
|
# 2. Парсим structSup.xml
|
||||||
|
def parse_structs(filename):
|
||||||
|
tree = ET.parse(filename)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
structs = {}
|
||||||
|
typedef_map = {}
|
||||||
|
|
||||||
|
# --- Считываем структуры ---
|
||||||
|
structs_elem = root.find('structs')
|
||||||
|
if structs_elem is not None:
|
||||||
|
for struct in structs_elem.findall('struct'):
|
||||||
|
name = struct.attrib['name']
|
||||||
|
fields = {}
|
||||||
|
for field in struct.findall('field'):
|
||||||
|
fname = field.attrib.get('name')
|
||||||
|
ftype = field.attrib.get('type')
|
||||||
|
if fname and ftype:
|
||||||
|
fields[fname] = ftype
|
||||||
|
structs[name] = fields
|
||||||
|
|
||||||
|
# --- Считываем typedef-ы ---
|
||||||
|
typedefs_elem = root.find('typedefs')
|
||||||
|
if typedefs_elem is not None:
|
||||||
|
for typedef in typedefs_elem.findall('typedef'):
|
||||||
|
name = typedef.attrib.get('name')
|
||||||
|
target_type = typedef.attrib.get('type')
|
||||||
|
if name and target_type:
|
||||||
|
typedef_map[name.strip()] = target_type.strip()
|
||||||
|
|
||||||
|
return structs, typedef_map
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessOutputWindow(QWidget):
|
||||||
|
def __init__(self, command, args):
|
||||||
|
super().__init__()
|
||||||
|
self.setWindowTitle("Поиск переменных...")
|
||||||
|
self.resize(600, 400)
|
||||||
|
|
||||||
|
self.layout = QVBoxLayout(self)
|
||||||
|
self.output_edit = QTextEdit()
|
||||||
|
self.output_edit.setReadOnly(True)
|
||||||
|
self.layout.addWidget(self.output_edit)
|
||||||
|
|
||||||
|
self.btn_close = QPushButton("Закрыть")
|
||||||
|
self.btn_close.setEnabled(False)
|
||||||
|
self.btn_close.clicked.connect(self.close)
|
||||||
|
self.layout.addWidget(self.btn_close)
|
||||||
|
|
||||||
|
self.process = QProcess(self)
|
||||||
|
self.process.setProcessChannelMode(QProcess.MergedChannels)
|
||||||
|
self.process.readyReadStandardOutput.connect(self.handle_stdout)
|
||||||
|
self.process.finished.connect(self.process_finished)
|
||||||
|
|
||||||
|
self.process.start(command, args)
|
||||||
|
|
||||||
|
def handle_stdout(self):
|
||||||
|
data = self.process.readAllStandardOutput()
|
||||||
|
text = data.data().decode('utf-8')
|
||||||
|
self.output_edit.append(text)
|
||||||
|
|
||||||
|
def process_finished(self):
|
||||||
|
self.output_edit.append("\n--- Процесс завершён ---")
|
||||||
|
self.btn_close.setEnabled(True)
|
||||||
|
|
||||||
|
# 3. UI: таблица с переменными
|
||||||
|
class VarEditor(QWidget):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
self.vars_list = []
|
||||||
|
self.structs = {}
|
||||||
|
self.typedef_map = {}
|
||||||
|
self.structs_xml_path = None # сюда запишем путь из <structs_xml>
|
||||||
|
self.initUI()
|
||||||
|
|
||||||
|
def initUI(self):
|
||||||
|
self.setWindowTitle("Variable Editor")
|
||||||
|
|
||||||
|
# --- Поля ввода пути проекта и XML ---
|
||||||
|
|
||||||
|
# XML Output
|
||||||
|
xml_layout = QHBoxLayout()
|
||||||
|
xml_layout.addWidget(QLabel("XML Output:"))
|
||||||
|
self.xml_output_edit = QLineEdit()
|
||||||
|
xml_layout.addWidget(self.xml_output_edit)
|
||||||
|
self.xml_output_edit.returnPressed.connect(self.read_xml_file)
|
||||||
|
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("Project Path:"))
|
||||||
|
self.proj_path_edit = QLineEdit()
|
||||||
|
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 (relative path):"))
|
||||||
|
self.makefile_edit = QLineEdit()
|
||||||
|
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("Source Output File:"))
|
||||||
|
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("Обновить данные о переменных")
|
||||||
|
self.btn_update_vars.clicked.connect(self.update_vars_data)
|
||||||
|
|
||||||
|
# Таблица переменных
|
||||||
|
self.table = QTableWidget(len(self.vars_list), 7)
|
||||||
|
self.table.setHorizontalHeaderLabels([
|
||||||
|
'Include',
|
||||||
|
'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)
|
||||||
|
|
||||||
|
# Основной 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(btn_save)
|
||||||
|
layout.addLayout(source_output_layout)
|
||||||
|
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
def update_vars_data(self):
|
||||||
|
proj_path = os.path.abspath(self.proj_path_edit.text().strip())
|
||||||
|
xml_path = os.path.abspath(self.xml_output_edit.text().strip())
|
||||||
|
makefile_path = make_absolute_path(self.makefile_edit.text().strip(), proj_path)
|
||||||
|
|
||||||
|
if not proj_path or not xml_path:
|
||||||
|
QMessageBox.warning(self, "Ошибка", "Пожалуйста, укажите пути проекта и XML.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if not os.path.isfile(makefile_path):
|
||||||
|
QMessageBox.warning(self, "Ошибка", f"Makefile не найден по пути:\n{makefile_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
scanvars_exe = "scanVars.exe"
|
||||||
|
args = [proj_path, makefile_path, xml_path]
|
||||||
|
|
||||||
|
# Создаем и показываем окно с выводом процесса
|
||||||
|
self.proc_win = ProcessOutputWindow("python", [])
|
||||||
|
self.proc_win.show()
|
||||||
|
self.proc_win.output_edit.append("Запуск анализа переменных...")
|
||||||
|
|
||||||
|
# Запускаем в отдельном процессе через QProcess, если нужен live-вывод:
|
||||||
|
from threading import Thread
|
||||||
|
|
||||||
|
def run_scan_wrapper():
|
||||||
|
try:
|
||||||
|
run_scan(proj_path, makefile_path, xml_path)
|
||||||
|
self.proc_win.output_edit.append("\n--- Анализ завершён ---")
|
||||||
|
except Exception as e:
|
||||||
|
self.proc_win.output_edit.append(f"\n[ОШИБКА] {e}")
|
||||||
|
self.proc_win.btn_close.setEnabled(True)
|
||||||
|
self.after_scanvars_finished(0, 0)
|
||||||
|
|
||||||
|
Thread(target=run_scan_wrapper).start()
|
||||||
|
# Можно подписаться на сигнал завершения процесса, если хочешь обновить UI после
|
||||||
|
#self.proc_win.process.finished.connect(lambda exitCode, exitStatus: self.after_scanvars_finished(exitCode, exitStatus))
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Здесь нужно указать абсолютные пути к проекту, xml и output (замени на свои)
|
||||||
|
proj_path = os.path.abspath(self.proj_path_edit.text().strip())
|
||||||
|
xml_path = os.path.abspath(self.xml_output_edit.text().strip())
|
||||||
|
output_dir_c_file = os.path.abspath(self.source_output_edit.text().strip())
|
||||||
|
|
||||||
|
if not proj_path or not xml_path or not output_dir_c_file:
|
||||||
|
QMessageBox.warning(self, "Ошибка", "Заполните все пути: проект, XML и output.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
run_generate(proj_path, xml_path, output_dir_c_file)
|
||||||
|
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Ошибка при генерации", str(e))
|
||||||
|
|
||||||
|
def browse_proj_path(self):
|
||||||
|
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта")
|
||||||
|
if dir_path:
|
||||||
|
self.proj_path_edit.setText(dir_path)
|
||||||
|
|
||||||
|
def read_xml_file(self):
|
||||||
|
file_path = self.xml_output_edit.text().strip()
|
||||||
|
if file_path and not os.path.isfile(file_path):
|
||||||
|
return
|
||||||
|
|
||||||
|
self.vars_list = parse_vars(file_path)
|
||||||
|
try:
|
||||||
|
tree = ET.parse(file_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
proj_path = self.proj_path_edit.text().strip()
|
||||||
|
|
||||||
|
if not 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):
|
||||||
|
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(proj_path):
|
||||||
|
QMessageBox.warning(
|
||||||
|
self,
|
||||||
|
"Внимание",
|
||||||
|
f"Указанный путь к проекту не существует:\n{proj_path}\n"
|
||||||
|
"Пожалуйста, исправьте путь в поле 'Project Path'."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --- makefile_path из атрибута ---
|
||||||
|
makefile_path = root.attrib.get('makefile_path', '').strip()
|
||||||
|
makefile_path_full = make_absolute_path(makefile_path, proj_path)
|
||||||
|
if makefile_path_full and os.path.isfile(makefile_path_full):
|
||||||
|
self.makefile_edit.setText(makefile_path)
|
||||||
|
|
||||||
|
# --- structs_path из атрибута ---
|
||||||
|
structs_path = root.attrib.get('structs_path', '').strip()
|
||||||
|
structs_path_full = make_absolute_path(structs_path, proj_path)
|
||||||
|
if structs_path_full and os.path.isfile(structs_path_full):
|
||||||
|
self.structs_xml_path = structs_path_full
|
||||||
|
self.structs, self.typedef_map = parse_structs(structs_path_full)
|
||||||
|
else:
|
||||||
|
self.structs_xml_path = None
|
||||||
|
|
||||||
|
self.update_table()
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
|
||||||
|
|
||||||
|
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.read_xml_file()
|
||||||
|
|
||||||
|
|
||||||
|
def browse_xml_struct(self):
|
||||||
|
file_path, _ = QFileDialog.getSaveFileName(self, "Выберите XML файл", filter="XML files (*.xml);;All Files (*)")
|
||||||
|
if file_path:
|
||||||
|
self.xml_output_edit.setText(file_path)
|
||||||
|
if os.path.isfile(file_path):
|
||||||
|
self.structs, self.typedef_map = parse_structs(file_path)
|
||||||
|
|
||||||
|
def browse_makefile(self):
|
||||||
|
file_path, _ = QFileDialog.getOpenFileName(
|
||||||
|
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)"
|
||||||
|
)
|
||||||
|
if file_path:
|
||||||
|
self.makefile_edit.setText(file_path)
|
||||||
|
|
||||||
|
def browse_source_output(self):
|
||||||
|
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
|
||||||
|
if dir_path:
|
||||||
|
self.source_output_edit.setText(dir_path)
|
||||||
|
|
||||||
|
def after_scanvars_finished(self, exitCode, exitStatus):
|
||||||
|
xml_path = self.xml_output_edit.text().strip()
|
||||||
|
if not os.path.isfile(xml_path):
|
||||||
|
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {xml_path}")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Читаем структуры, если задан путь
|
||||||
|
if self.structs_xml_path and os.path.isfile(self.structs_xml_path):
|
||||||
|
try:
|
||||||
|
self.structs, self.typedef_map = parse_structs(self.structs_xml_path)
|
||||||
|
# При необходимости обновите UI или сделайте что-то с self.structs
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.warning(self, "Внимание", f"Не удалось загрузить структуры из {self.structs_xml_path}:\n{e}")
|
||||||
|
|
||||||
|
self.vars_list = parse_vars(xml_path)
|
||||||
|
self.update_table()
|
||||||
|
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
|
||||||
|
|
||||||
|
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)]
|
||||||
|
self.table.setRowCount(len(self.vars_list))
|
||||||
|
|
||||||
|
for row, var in enumerate(self.vars_list):
|
||||||
|
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 = map_type_to_pt(var['type'], var['name'], self.typedef_map)
|
||||||
|
display_type = internal_type.replace('pt_', '')
|
||||||
|
if display_type in self.display_type_options:
|
||||||
|
pt_type_combo.setCurrentText(display_type)
|
||||||
|
else:
|
||||||
|
pt_type_combo.addItem(display_type)
|
||||||
|
pt_type_combo.setCurrentText(display_type)
|
||||||
|
self.table.setCellWidget(row, rows.pt_type, pt_type_combo)
|
||||||
|
|
||||||
|
iq_combo = QComboBox()
|
||||||
|
iq_combo.addItems(iq_types)
|
||||||
|
iq_type = get_iq_define(var['type']) # Получаем IQ-тип, например 'iq24'
|
||||||
|
display_type = iq_type.replace('t_', '')
|
||||||
|
if iq_type in iq_types:
|
||||||
|
iq_combo.setCurrentText(display_type)
|
||||||
|
else:
|
||||||
|
iq_combo.addItem(display_type)
|
||||||
|
iq_combo.setCurrentText(display_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.save_table_to_xml)
|
||||||
|
name_edit.textChanged.connect(self.save_table_to_xml)
|
||||||
|
pt_type_combo.currentTextChanged.connect(self.save_table_to_xml)
|
||||||
|
iq_combo.currentTextChanged.connect(self.save_table_to_xml)
|
||||||
|
ret_combo.currentTextChanged.connect(self.save_table_to_xml)
|
||||||
|
short_name_edit.textChanged.connect(self.save_table_to_xml)
|
||||||
|
|
||||||
|
|
||||||
|
def save_table_to_xml(self):
|
||||||
|
def make_relative_path(abs_path, base_path):
|
||||||
|
try:
|
||||||
|
return os.path.relpath(abs_path, base_path).replace("\\", "/")
|
||||||
|
except ValueError:
|
||||||
|
return abs_path.replace("\\", "/")
|
||||||
|
|
||||||
|
xml_path = self.xml_output_edit.text().strip()
|
||||||
|
proj_path = self.proj_path_edit.text().strip()
|
||||||
|
makefile_path = self.makefile_edit.text().strip()
|
||||||
|
|
||||||
|
if not xml_path or not os.path.isfile(xml_path):
|
||||||
|
print("XML файл не найден или путь пустой")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
tree = ET.parse(xml_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
# Обновим атрибуты с относительными путями
|
||||||
|
if os.path.isdir(proj_path):
|
||||||
|
root.set("proj_path", proj_path.replace("\\", "/"))
|
||||||
|
|
||||||
|
if os.path.isfile(makefile_path):
|
||||||
|
rel_makefile = make_relative_path(makefile_path, proj_path)
|
||||||
|
root.set("makefile_path", rel_makefile)
|
||||||
|
|
||||||
|
if self.structs_xml_path and os.path.isfile(self.structs_xml_path):
|
||||||
|
rel_struct_path = make_relative_path(self.structs_xml_path, proj_path)
|
||||||
|
root.set("structs_path", rel_struct_path)
|
||||||
|
|
||||||
|
|
||||||
|
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] = {
|
||||||
|
'type': var_elem.findtext('type', ''),
|
||||||
|
'file': var_elem.findtext('file', ''),
|
||||||
|
'extern': var_elem.findtext('extern', ''),
|
||||||
|
'static': var_elem.findtext('static', '')
|
||||||
|
}
|
||||||
|
# Собираем данные из таблицы
|
||||||
|
updated_vars = []
|
||||||
|
for row in range(self.table.rowCount()):
|
||||||
|
cb = self.table.cellWidget(row, 0)
|
||||||
|
name_edit = self.table.cellWidget(row, 1)
|
||||||
|
pt_type_combo = self.table.cellWidget(row, 3)
|
||||||
|
iq_combo = self.table.cellWidget(row, 4)
|
||||||
|
ret_combo = self.table.cellWidget(row, 5)
|
||||||
|
short_name_edit = self.table.cellWidget(row, 6)
|
||||||
|
|
||||||
|
var_name = name_edit.text()
|
||||||
|
|
||||||
|
# Берём оригинальные type и file из словаря, если есть
|
||||||
|
orig_type = original_info.get(var_name, {}).get('type', '')
|
||||||
|
orig_file = original_info.get(var_name, {}).get('file', '')
|
||||||
|
orig_extern = original_info.get(var_name, {}).get('extern', '')
|
||||||
|
orig_static = original_info.get(var_name, {}).get('static', '')
|
||||||
|
|
||||||
|
updated_vars.append({
|
||||||
|
'name': var_name,
|
||||||
|
'enable': cb.isChecked(),
|
||||||
|
'shortname': short_name_edit.text(),
|
||||||
|
'pt_type': 'pt_' + pt_type_combo.currentText(),
|
||||||
|
'iq_type': iq_combo.currentText(),
|
||||||
|
'return_type': ret_combo.currentText() or 'int',
|
||||||
|
'type': orig_type,
|
||||||
|
'file': orig_file,
|
||||||
|
'extern': orig_extern,
|
||||||
|
'static': orig_static,
|
||||||
|
})
|
||||||
|
|
||||||
|
# Обновляем или добавляем по одному var в XML
|
||||||
|
for v in updated_vars:
|
||||||
|
var_elem = None
|
||||||
|
for ve in vars_elem.findall('var'):
|
||||||
|
if ve.attrib.get('name') == v['name']:
|
||||||
|
var_elem = ve
|
||||||
|
break
|
||||||
|
if var_elem is None:
|
||||||
|
var_elem = ET.SubElement(vars_elem, 'var', {'name': v['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)
|
||||||
|
|
||||||
|
set_sub_elem_text(var_elem, 'enable', 'true' if v['enable'] else 'false')
|
||||||
|
set_sub_elem_text(var_elem, 'shortname', v['shortname'])
|
||||||
|
set_sub_elem_text(var_elem, 'pt_type', v['pt_type'])
|
||||||
|
set_sub_elem_text(var_elem, 'iq_type', v['iq_type'])
|
||||||
|
set_sub_elem_text(var_elem, 'return_type', v['return_type'])
|
||||||
|
set_sub_elem_text(var_elem, 'type', v['type'])
|
||||||
|
set_sub_elem_text(var_elem, 'file', v['file'])
|
||||||
|
set_sub_elem_text(var_elem, 'extern', v['extern'])
|
||||||
|
set_sub_elem_text(var_elem, 'static', v['static'])
|
||||||
|
|
||||||
|
# Сохраняем изменения
|
||||||
|
tree.write(xml_path, encoding='utf-8', xml_declaration=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при сохранении XML: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app = QApplication(sys.argv)
|
||||||
|
|
||||||
|
editor = VarEditor()
|
||||||
|
editor.resize(900, 600)
|
||||||
|
editor.show()
|
||||||
|
|
||||||
|
sys.exit(app.exec())
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user