Compare commits
18 Commits
0d54031dd5
...
v1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
043359fe66 | ||
|
|
c55f38ef1c | ||
|
|
ae2c90160e | ||
|
|
4de53090a1 | ||
|
|
742c4e9e1b | ||
|
|
369cfa808c | ||
|
|
c32dc161f8 | ||
|
|
abfc507e4e | ||
|
|
cb496bca0f | ||
|
|
7b720cbdf4 | ||
|
|
6428e523df | ||
|
|
c738acd871 | ||
|
|
05bde87c38 | ||
|
|
02f3124224 | ||
|
|
21082a38e0 | ||
|
|
42ac3eb65d | ||
|
|
69c0bf1574 | ||
|
|
4f949e9854 |
BIN
DebugVarEdit.exe
BIN
DebugVarEdit.exe
Binary file not shown.
172
README.md
Normal file
172
README.md
Normal file
@@ -0,0 +1,172 @@
|
||||
# DebugTools - Просмотр переменных по указателям
|
||||
Модуль состоит из трех файлов:
|
||||
- **debug_tools.c** - реализация считывания переменных
|
||||
- **debug_tools.h** - объявление всякого для считывания переменных
|
||||
- **debug_vars.c** - определение массива считываемых переменных
|
||||
|
||||
Этот модуль предоставляет функциональность для чтения значений переменных во встроенной системе, включая работу с IQ-форматами, защиту доступа и проверку диапазонов памяти.
|
||||
|
||||
Для чтения переменных можно использовать функции:
|
||||
|
||||
```c
|
||||
int Debug_ReadVar(int var_ind, int32_t *return_long);
|
||||
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr);
|
||||
```
|
||||
|
||||
|
||||
Переменные доступные для чтения определяются в **debug_vars.c** (их можно прописывать вручную или генерировать через **DebugVarEdit**):
|
||||
|
||||
```c
|
||||
// Определение массива с указателями на переменные для отладки
|
||||
int DebugVar_Qnt = 5;
|
||||
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
|
||||
// pointer_type iq_type return_iq_type short_name
|
||||
DebugVar_t dbg_vars[] = {\
|
||||
{(uint8_t *)&freqTerm, pt_float, t_iq_none, t_iq10, "freqT" }, \
|
||||
{(uint8_t *)&ADC_sf[0][0], pt_int16, t_iq_none, t_iq_none, "ADC_sf00" }, \
|
||||
{(uint8_t *)&ADC_sf[0][1], pt_int16, t_iq_none, t_iq_none, "ADC_sf01" }, \
|
||||
{(uint8_t *)&ADC_sf[0][2], pt_int16, t_iq_none, t_iq_none, "ADC_sf02" }, \
|
||||
{(uint8_t *)&ADC_sf[0][3], pt_int16, t_iq_none, t_iq_none, "ADC_sf03" }, \
|
||||
{(uint8_t *)&Bender[0].KOhms, pt_uint16, t_iq, t_iq10, "Bend0.KOhm" }, \
|
||||
{(uint8_t *)&Bender[0].Times, pt_uint16, t_iq_none, t_iq_none, "Bend0.Time" }, \
|
||||
};
|
||||
```
|
||||
|
||||
# DebugVarEdit - Настройка переменных
|
||||
**DebugVarEdit** — графическое приложение для Windows, предназначенное для настройки и генерации отладочных переменных (`debug_vars.c`) на основе исходного C-проекта. Работает с `makefile` проекта, сохраняет изменения в XML и позволяет удобно редактировать переменные и их типы через интерфейс.
|
||||
|
||||
Программа — один исполняемый файл `DebugVarEdit.exe`, не требующий установки и дополнительных зависимостей.
|
||||
|
||||
> Требуется Windows 7 или новее.
|
||||
|
||||
---
|
||||
|
||||
## Как использовать приложение
|
||||
|
||||
1. Запустите **DebugVarEdit.exe.**
|
||||
|
||||
2. Укажите пути и контроллер:
|
||||
- **Путь к XML** — новый или существующий файл настроек переменных;
|
||||
- **Путь к проекту** — путь к корню проека (папка, которая является корнем для проекта в Code Composer);
|
||||
- **Путь к makefile** — путь к `makefile` относительно корня проекта;
|
||||
- **Путь для debug_vars.c** — папка, куда будет сгенерирован файл `debug_vars.c` с переменными.
|
||||
- **МК** — TMS или STM. Они отличаются размером int, и также принятыми кодировками файлов (utf-8/cp1251)
|
||||
|
||||
3. Нажмите **Сканировать переменные**:
|
||||
- Программа проанализирует исходники, указанные в makefile, и найдёт все переменные.
|
||||
- Результат сохранится в двух XML-файлах:
|
||||
- `structs.xml` — информация обо всех структурах и typedef;
|
||||
- `<ваш_файл>.xml` — список всех найденных переменных.
|
||||
|
||||
4. Нажмите **Добавить переменные**:
|
||||
- В **левой таблице** отображаются все найденные переменные.
|
||||
Для добавления переменной в проект дважды кликните по ней или нажмите кнопку `>`, чтобы переместить в правую таблицу.
|
||||
- В **правой таблице** находятся переменные, выбранные для использования.
|
||||
Чтобы убрать переменную из проекта, переместите её обратно в левую таблицу двойным кликом или кнопкой `<`.
|
||||
- После выбора переменных нажмите **Применить**, чтобы обновить основной список переменных и включить их в проект.
|
||||
|
||||
5. Настройте параметры выбранных переменных:
|
||||
- **En** — включение или отключение переменной для генерации;
|
||||
- **Base Type** — базовый тип переменной (например, int8, uint16 и т.д.);
|
||||
- **IQ Type** — формат IQ, если применимо;
|
||||
- **Return Type** — формат возвращаемого значения (IQ-тип или целочисленный);
|
||||
- **Shortname** — короткое имя переменной для для терминалки.
|
||||
|
||||
Все параметры выбираются из выпадающих списков, которые можно настроить, чтобы отображались только нужные опции.
|
||||
|
||||
6. Нажмите **Сгенерировать файл** для создания файла `debug_vars.c` с выбранными переменными.
|
||||
---
|
||||
|
||||
## Возможности
|
||||
|
||||
- Загрузка и сохранение настроек переменных в XML-файлах.
|
||||
- Автоматическое определение исходных файлов с переменными для удобства работы.
|
||||
- Редактирование переменных: включение, короткого имени и типов через удобные списки.
|
||||
- Подсветка ошибок при вводе (неправильные имена, слишком длинные короткие имена).
|
||||
- Быстрая фильтрация переменных по столбцам.
|
||||
- Автоматическая генерация файла debug_vars.c с выбранными переменными.
|
||||
- Возможность сразу открыть сгенерированный файл в редакторе.
|
||||
- Умное автодополнение имён переменных и полей структур.
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Пример XML-файла
|
||||
|
||||
```xml
|
||||
<project proj_path="C:/myproj" makefile_path="Debug/Makefile" structs_path="Src/DebugTools/structs.xml">
|
||||
<variables>
|
||||
<var name="g_myvar">
|
||||
<enable>true</enable>
|
||||
<show_var>true</show_var>
|
||||
<shortname>myv</shortname>
|
||||
<pt_type>pt_float</pt_type>
|
||||
<iq_type>t_iq24</iq_type>
|
||||
<return_type>t_iq24</return_type>
|
||||
<type>float</type>
|
||||
<file>Src/main/main.c</file>
|
||||
<extern>true</extern>
|
||||
<static>false</static>
|
||||
</var>
|
||||
</variables>
|
||||
</project>
|
||||
```
|
||||
|
||||
---
|
||||
---
|
||||
|
||||
# Для разработчиков
|
||||
|
||||
### Структура проекта:
|
||||
|
||||
```bash
|
||||
Src
|
||||
├── build/
|
||||
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
|
||||
├── DebugVarEdit_GUI.py # Главное окно
|
||||
├── var_table.py # Таблица выбранных переменных
|
||||
├── var_selector_window.py # Окно выбора переменных
|
||||
├── var_selector_table.py # Таблица переменных в окне выбора переменных
|
||||
├── scan_progress_gui.py # Отображение процесса сканирования переменных
|
||||
├── scan_vars.py # Сканирование переменных среди .c/.h файлов
|
||||
├── generate_debug_vars.py # Генерация debug_vars.c
|
||||
├── myXML.py # Утилиты для XML
|
||||
├── makefile_parser.py # Парсинг makefile на .c/.h файлы
|
||||
├── var_setup.py # Подготовка переменных для окна выбора переменных
|
||||
├── libclang.dll # Бибилиотека clang
|
||||
├── icon.ico # Иконка
|
||||
```
|
||||
|
||||
### Зависимости
|
||||
|
||||
Для запуска приложения:
|
||||
- **Python 3.7+**
|
||||
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`, лежит в папке Src)
|
||||
- **PySide2** — GUI-фреймворк
|
||||
- **lxml** — работа с XML
|
||||
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
|
||||
|
||||
Для сборки `.exe`:
|
||||
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
|
||||
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен для запуска `.exe`)
|
||||
|
||||
|
||||
### Сборка:
|
||||
Если вы хотите собрать `DebugVarEdit.exe` самостоятельно из исходников, используйте скрипт **build/build_and_clean.py**. Он автоматически собирает проект с помощью Nuitka или PyInstaller:
|
||||
- Собирает проект в `DebugVarEdit.exe` в корневой папке.
|
||||
- Включает:
|
||||
- все необходимые `.dll` (например, `libclang.dll`),
|
||||
- иконку (`icon.ico`),
|
||||
- плагины PySide2.
|
||||
- Очищает временные папки после сборки:
|
||||
- `build_temp`
|
||||
- `__pycache__`
|
||||
- `DebugVarEdit_GUI.*`
|
||||
|
||||
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
|
||||
|
||||
### Установка зависимостей
|
||||
|
||||
```bash
|
||||
pip install clang PySide2 lxml nuitka pyinstaller
|
||||
```
|
||||
@@ -1,19 +1,19 @@
|
||||
# build command
|
||||
# pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp setupVars_GUI.py
|
||||
# pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp var_setup_GUI.py
|
||||
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import xml.etree.ElementTree as ET
|
||||
from generateVars import type_map
|
||||
import lxml.etree as ET
|
||||
from generate_debug_vars import type_map, choose_type_map
|
||||
from enum import IntEnum
|
||||
import threading
|
||||
from generateVars import run_generate
|
||||
import setupVars
|
||||
from VariableSelector import VariableSelectorDialog
|
||||
from VariableTable import VariableTableWidget, rows
|
||||
from scanVarGUI import ProcessOutputWindow
|
||||
import scanVars
|
||||
from generate_debug_vars import run_generate
|
||||
import var_setup
|
||||
from var_selector_window import VariableSelectorDialog
|
||||
from var_table import VariableTableWidget, rows
|
||||
from scan_progress_gui import ProcessOutputWindow
|
||||
import scan_vars
|
||||
import myXML
|
||||
import time
|
||||
|
||||
@@ -21,8 +21,9 @@ from PySide2.QtWidgets import (
|
||||
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||||
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
|
||||
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
|
||||
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView
|
||||
)
|
||||
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy, QHeaderView,
|
||||
QMenuBar, QMenu, QAction
|
||||
)
|
||||
from PySide2.QtGui import QTextCursor, QKeyEvent, QIcon, QFont
|
||||
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
|
||||
|
||||
@@ -31,12 +32,18 @@ var_edit_title = "Редактор переменных для отладки"
|
||||
xml_path_title = "Путь к XML:"
|
||||
proj_path_title = "Путь к проекту:"
|
||||
makefile_path_title = "Пусть к makefile (относительно проекта)"
|
||||
output_path_title = "Папка для debug_vars.c:"
|
||||
output_path_title = "Путь для для debug_vars.c:"
|
||||
scan_title = "Сканировать переменные"
|
||||
build_title = "Сгенерировать файл"
|
||||
add_vars_title = "Добавить переменные"
|
||||
open_output_title = "Открыть файл"
|
||||
|
||||
def set_sub_elem_text(parent, tag, text):
|
||||
el = parent.find(tag)
|
||||
if el is None:
|
||||
el = ET.SubElement(parent, tag)
|
||||
el.text = str(text)
|
||||
|
||||
# 3. UI: таблица с переменными
|
||||
class VarEditor(QWidget):
|
||||
def __init__(self):
|
||||
@@ -52,12 +59,13 @@ class VarEditor(QWidget):
|
||||
self.output_path = None
|
||||
self._updating = False # Флаг блокировки рекурсии
|
||||
self._resizing = False # флаг блокировки повторного вызова
|
||||
self.target = 'TMS'
|
||||
self.initUI()
|
||||
|
||||
def initUI(self):
|
||||
self.setWindowTitle(var_edit_title)
|
||||
|
||||
base_path = scanVars.get_base_path()
|
||||
base_path = scan_vars.get_base_path()
|
||||
icon_path = os.path.join(base_path, "icon.ico")
|
||||
if os.path.exists(icon_path):
|
||||
self.setWindowIcon(QIcon(icon_path))
|
||||
@@ -116,6 +124,32 @@ class VarEditor(QWidget):
|
||||
self.btn_update_vars = QPushButton(scan_title)
|
||||
self.btn_update_vars.clicked.connect(self.update_vars_data)
|
||||
|
||||
# Добавляем чекбокс для выбора типовой карты
|
||||
# --- Создаем верхнее меню ---
|
||||
menubar = QMenuBar(self)
|
||||
menubar.setToolTip('Разные размеры int и кодировки файлов')
|
||||
self.target_menu = QMenu("МК:", menubar)
|
||||
# Создаем действия для выбора Target
|
||||
self.action_tms = QAction("TMS", self, checkable=True)
|
||||
self.action_stm = QAction("STM", self, checkable=True)
|
||||
# Инициализируем QSettings с именем организации и приложения
|
||||
self.settings = QSettings("SET", "DebugVarEdit_MainWindow")
|
||||
# Восстанавливаем сохранённое состояние, если есть
|
||||
mcu = self.settings.value("mcu_choosen", True, type=str)
|
||||
self.on_target_selected(mcu)
|
||||
|
||||
self.target_menu.setToolTip(f'TMS: Размер int 16 бит. Кодировка cp1251\nSTM: Размер int 32 бита. Кодировка utf-8')
|
||||
# Группируем действия чтобы выбирался только один
|
||||
self.action_tms.triggered.connect(lambda: self.on_target_selected("TMS"))
|
||||
self.action_tms.setToolTip('Размер int 16 бит. Кодировка cp1251')
|
||||
self.action_stm.triggered.connect(lambda: self.on_target_selected("STM"))
|
||||
self.action_stm.setToolTip('Размер int 32 бита. Кодировка utf-8')
|
||||
|
||||
self.target_menu.addAction(self.action_tms)
|
||||
self.target_menu.addAction(self.action_stm)
|
||||
|
||||
menubar.addMenu(self.target_menu)
|
||||
|
||||
# Кнопка сохранения
|
||||
btn_save = QPushButton(build_title)
|
||||
btn_save.clicked.connect(self.save_build)
|
||||
@@ -131,6 +165,7 @@ class VarEditor(QWidget):
|
||||
self.table = VariableTableWidget()
|
||||
# Основной layout
|
||||
layout = QVBoxLayout()
|
||||
layout.setMenuBar(menubar) # прикрепляем menubar в layout сверху
|
||||
layout.addLayout(xml_layout)
|
||||
layout.addLayout(proj_layout)
|
||||
layout.addLayout(makefile_layout)
|
||||
@@ -144,7 +179,20 @@ class VarEditor(QWidget):
|
||||
self.setLayout(layout)
|
||||
|
||||
|
||||
|
||||
def on_target_selected(self, target):
|
||||
self.target_menu.setTitle(f'МК: {target}')
|
||||
self.settings.setValue("mcu_choosen", target)
|
||||
self.target = target.lower()
|
||||
if self.target == "stm":
|
||||
choose_type_map(True)
|
||||
self.action_stm.setChecked(True)
|
||||
self.action_tms.setChecked(False)
|
||||
else:
|
||||
choose_type_map(False)
|
||||
self.action_tms.setChecked(True)
|
||||
self.action_stm.setChecked(False)
|
||||
|
||||
|
||||
def get_xml_path(self):
|
||||
xml_path = self.xml_output_edit.text().strip()
|
||||
return xml_path
|
||||
@@ -202,7 +250,7 @@ class VarEditor(QWidget):
|
||||
|
||||
# Создаём окно с кнопкой "Готово"
|
||||
self.proc_win = ProcessOutputWindow(self.proj_path, self.makefile_path, self.xml_path,
|
||||
on_done_callback=self.__after_scanvars_finished)
|
||||
self.__after_scan_vars_finished, self)
|
||||
self.proc_win.start_scan()
|
||||
|
||||
|
||||
@@ -222,8 +270,8 @@ class VarEditor(QWidget):
|
||||
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',
|
||||
'iq_type': 't_' + iq_combo.currentText(),
|
||||
'return_type': 't_' + ret_combo.currentText() if ret_combo.currentText() else 't_iq_none',
|
||||
'short_name': short_name_edit.text(),
|
||||
}
|
||||
vars_out.append(var_data)
|
||||
@@ -235,15 +283,15 @@ class VarEditor(QWidget):
|
||||
return
|
||||
|
||||
try:
|
||||
run_generate(self.proj_path, self.xml_path, self.output_path)
|
||||
run_generate(self.proj_path, self.xml_path, self.output_path, self.table._shortname_size)
|
||||
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
|
||||
self.update()
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Ошибка при генерации", str(e))
|
||||
|
||||
|
||||
def update(self):
|
||||
if self._updating:
|
||||
def update(self, force=0):
|
||||
if self._updating and (force==0):
|
||||
return # Уже в процессе обновления — выходим, чтобы избежать рекурсии
|
||||
self._updating = True
|
||||
|
||||
@@ -296,11 +344,11 @@ class VarEditor(QWidget):
|
||||
structs_path_full = myXML.make_absolute_path(structs_path, self.proj_path)
|
||||
if structs_path_full and os.path.isfile(structs_path_full):
|
||||
self.structs_path = structs_path_full
|
||||
self.structs, self.typedef_map = setupVars.parse_structs(structs_path_full)
|
||||
self.structs, self.typedef_map = var_setup.parse_structs(structs_path_full)
|
||||
else:
|
||||
self.structs_path = None
|
||||
|
||||
self.vars_list = setupVars.parse_vars(self.xml_path, self.typedef_map)
|
||||
self.vars_list = var_setup.parse_vars(self.xml_path, self.typedef_map)
|
||||
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
|
||||
except Exception as e:
|
||||
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
|
||||
@@ -352,10 +400,20 @@ class VarEditor(QWidget):
|
||||
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:
|
||||
if self.target == 'stm':
|
||||
file_filter = "Makefile или Keil-проект (*.uvprojx *.uvproj makefile);;Все файлы (*)"
|
||||
dialog_title = "Выберите Makefile или Keil-проект"
|
||||
else: # 'TMS' или по умолчанию
|
||||
file_filter = "Makefile (makefile);;Все файлы (*)"
|
||||
dialog_title = "Выберите Makefile"
|
||||
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
dialog_title,
|
||||
filter=file_filter
|
||||
)
|
||||
if file_path:
|
||||
if self.proj_path:
|
||||
path = myXML.make_relative_path(file_path, self.proj_path)
|
||||
else:
|
||||
path = file_path
|
||||
@@ -398,50 +456,66 @@ class VarEditor(QWidget):
|
||||
self.update()
|
||||
|
||||
|
||||
def __after_scanvars_finished(self):
|
||||
self.update_all_paths()
|
||||
def __after_scan_vars_finished(self):
|
||||
if not os.path.isfile(self.xml_path):
|
||||
self.makefile_path = None
|
||||
self.structs_path = None
|
||||
self.proj_path = None
|
||||
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {self.xml_path}")
|
||||
return
|
||||
|
||||
try:
|
||||
self.update(1)
|
||||
|
||||
except Exception as e:
|
||||
self.makefile_path = None
|
||||
self.structs_path = None
|
||||
self.proj_path = None
|
||||
self.update()
|
||||
|
||||
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Ошибка", f"Не удалось загрузить переменные:\n{e}")
|
||||
|
||||
|
||||
|
||||
def delete_selected_rows(self):
|
||||
selected_rows = sorted(set(index.row() for index in self.table.selectedIndexes()), reverse=True)
|
||||
if not selected_rows:
|
||||
# Получаем имена всех выбранных переменных из первого столбца
|
||||
selected_names = self.table.get_selected_var_names()
|
||||
|
||||
if not selected_names:
|
||||
return
|
||||
|
||||
for row in selected_rows:
|
||||
if 0 <= row < len(self.vars_list):
|
||||
# Меняем флаг show_var для переменной с этим индексом
|
||||
self.vars_list[row]['show_var'] = 'false'
|
||||
# Меняем флаг show_var по имени
|
||||
for var in self.vars_list:
|
||||
if var.get('name') in selected_names:
|
||||
var['show_var'] = 'false'
|
||||
|
||||
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
|
||||
self.write_to_xml()
|
||||
|
||||
|
||||
def __open_variable_selector(self):
|
||||
def __open_variable_selector(self):
|
||||
self.update()
|
||||
if not self.vars_list:
|
||||
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
|
||||
return
|
||||
|
||||
|
||||
dlg = VariableSelectorDialog(self.table, self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
|
||||
if dlg.exec_():
|
||||
self.write_to_xml()
|
||||
self.update()
|
||||
|
||||
|
||||
def write_to_xml(self, dummy=None):
|
||||
t0 = time.time()
|
||||
self.update_all_paths()
|
||||
|
||||
def get_val(name, default=''):
|
||||
return str(v_table[name] if v_table and name in v_table else v.get(name, default))
|
||||
|
||||
def element_differs(elem, values: dict):
|
||||
for tag, new_val in values.items():
|
||||
current_elem = elem.find(tag)
|
||||
current_val = (current_elem.text or '').strip()
|
||||
new_val_str = str(new_val or '').strip()
|
||||
if current_val != new_val_str:
|
||||
return True
|
||||
return False
|
||||
|
||||
if not self.xml_path or not os.path.isfile(self.xml_path):
|
||||
print("XML файл не найден или путь пустой")
|
||||
return
|
||||
@@ -465,7 +539,6 @@ class VarEditor(QWidget):
|
||||
|
||||
if self.makefile_path and os.path.isfile(self.makefile_path):
|
||||
rel_makefile = myXML.make_relative_path(self.makefile_path, self.proj_path)
|
||||
root.set("makefile_path", rel_makefile)
|
||||
# Если результат — абсолютный путь, не записываем
|
||||
if not os.path.isabs(rel_makefile):
|
||||
root.set("makefile_path", rel_makefile)
|
||||
@@ -476,6 +549,7 @@ class VarEditor(QWidget):
|
||||
if not os.path.isabs(rel_struct):
|
||||
root.set("structs_path", rel_struct)
|
||||
|
||||
t1 = time.time()
|
||||
|
||||
vars_elem = root.find('variables')
|
||||
if vars_elem is None:
|
||||
@@ -490,6 +564,7 @@ class VarEditor(QWidget):
|
||||
'extern': var_elem.findtext('extern', ''),
|
||||
'static': var_elem.findtext('static', '')
|
||||
}
|
||||
var_elements_by_name = {ve.attrib.get('name'): ve for ve in vars_elem.findall('var')}
|
||||
|
||||
# Читаем переменные из таблицы (активные/изменённые)
|
||||
table_vars = {v['name']: v for v in self.table.read_data()}
|
||||
@@ -498,59 +573,73 @@ class VarEditor(QWidget):
|
||||
|
||||
# Объединённый список переменных для записи
|
||||
all_names = list(all_vars_by_name.keys())
|
||||
t2 = time.time()
|
||||
for name in all_names:
|
||||
v = all_vars_by_name[name]
|
||||
v_table = table_vars.get(name)
|
||||
var_elem = None
|
||||
|
||||
# Ищем уже существующий <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)
|
||||
pt_type_val = get_val('pt_type').lower()
|
||||
if 'arr' in pt_type_val or 'struct' in pt_type_val or 'union' in pt_type_val:
|
||||
continue
|
||||
|
||||
show_var_val = str(v.get('show_var', 'false')).lower()
|
||||
enable_val = str(v_table['enable'] if v_table and 'enable' in v_table else v.get('enable', 'false')).lower()
|
||||
|
||||
set_sub_elem_text(var_elem, 'show_var', show_var_val)
|
||||
set_sub_elem_text(var_elem, 'enable', enable_val)
|
||||
enable_val = get_val('enable').lower()
|
||||
|
||||
# Тут подтягиваем из таблицы, если есть, иначе из v
|
||||
shortname_val = v_table['shortname'] if v_table and 'shortname' in v_table else v.get('shortname', '')
|
||||
pt_type_val = v_table['pt_type'] if v_table and 'pt_type' in v_table else v.get('pt_type', '')
|
||||
iq_type_val = v_table['iq_type'] if v_table and 'iq_type' in v_table else v.get('iq_type', '')
|
||||
ret_type_val = v_table['return_type'] if v_table and 'return_type' in v_table else v.get('return_type', '')
|
||||
shortname_val = get_val('shortname')
|
||||
iq_type_val = get_val('iq_type')
|
||||
ret_type_val = get_val('return_type')
|
||||
|
||||
set_sub_elem_text(var_elem, 'shortname', shortname_val)
|
||||
set_sub_elem_text(var_elem, 'pt_type', pt_type_val)
|
||||
set_sub_elem_text(var_elem, 'iq_type', iq_type_val)
|
||||
set_sub_elem_text(var_elem, 'return_type', ret_type_val)
|
||||
set_sub_elem_text(var_elem, 'type', v.get('type', ''))
|
||||
|
||||
# file/extern/static: из original_info, либо из v
|
||||
file_val = v.get('file') or original_info.get(name, {}).get('file', '')
|
||||
extern_val = v.get('extern') or original_info.get(name, {}).get('extern', '')
|
||||
static_val = v.get('static') or original_info.get(name, {}).get('static', '')
|
||||
|
||||
set_sub_elem_text(var_elem, 'file', file_val)
|
||||
set_sub_elem_text(var_elem, 'extern', extern_val)
|
||||
set_sub_elem_text(var_elem, 'static', static_val)
|
||||
values_to_write = {
|
||||
'show_var': show_var_val,
|
||||
'enable': enable_val,
|
||||
'shortname': shortname_val,
|
||||
'pt_type': pt_type_val,
|
||||
'iq_type': iq_type_val,
|
||||
'return_type': ret_type_val,
|
||||
'type': v.get('type', ''),
|
||||
'file': file_val,
|
||||
'extern': extern_val,
|
||||
'static': static_val
|
||||
}
|
||||
|
||||
# Ищем уже существующий <var> в XML
|
||||
var_elem = var_elements_by_name.get(name)
|
||||
# Если элемента нет, это новая переменная — сразу пишем
|
||||
if var_elem is None:
|
||||
var_elem = ET.SubElement(vars_elem, 'var', {'name': name})
|
||||
var_elements_by_name[name] = var_elem
|
||||
write_all = True # обязательно записать все поля
|
||||
else:
|
||||
write_all = element_differs(var_elem, values_to_write)
|
||||
|
||||
if not write_all:
|
||||
continue # Пропускаем, если нет изменений
|
||||
|
||||
for tag, text in values_to_write.items():
|
||||
set_sub_elem_text(var_elem, tag, text)
|
||||
|
||||
t3 = time.time()
|
||||
# Преобразуем дерево в строку
|
||||
self.table.check()
|
||||
myXML.fwrite(root, self.xml_path)
|
||||
|
||||
self.table.check()
|
||||
t4 = time.time()
|
||||
'''print(f"[T1] parse + set paths: {t1 - t0:.3f} сек")
|
||||
print(f"[T2] prepare variables: {t2 - t1:.3f} сек")
|
||||
print(f"[T3] loop + updates: {t3 - t2:.3f} сек")
|
||||
print(f"[T4] write to file: {t4 - t3:.3f} сек")
|
||||
print(f"[TOTAL] write_to_xml total: {t4 - t0:.3f} сек")'''
|
||||
except Exception as e:
|
||||
print(f"Ошибка при сохранении XML: {e}")
|
||||
|
||||
|
||||
def __open_output_file_with_program(self):
|
||||
output_path = self.get_output_path()
|
||||
if not output_path:
|
||||
|
||||
55
Src/README_DEVELOP.md
Normal file
55
Src/README_DEVELOP.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Для разработчиков
|
||||
|
||||
### Структура проекта:
|
||||
|
||||
```bash
|
||||
Src
|
||||
├── build/
|
||||
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
|
||||
├── DebugVarEdit_GUI.py # Главное окно
|
||||
├── var_table.py # Таблица выбранных переменных
|
||||
├── var_selector_window.py # Окно выбора переменных
|
||||
├── var_selector_table.py # Таблица переменных в окне выбора переменных
|
||||
├── scan_progress_gui.py # Отображение процесса сканирования переменных
|
||||
├── scan_vars.py # Сканирование переменных среди .c/.h файлов
|
||||
├── generate_debug_vars.py # Генерация debug_vars.c
|
||||
├── myXML.py # Утилиты для XML
|
||||
├── makefile_parser.py # Парсинг makefile на .c/.h файлы
|
||||
├── var_setup.py # Подготовка переменных для окна выбора переменных
|
||||
├── libclang.dll # Бибилиотека clang
|
||||
├── icon.ico # Иконка
|
||||
```
|
||||
|
||||
### Зависимости
|
||||
|
||||
Для запуска приложения:
|
||||
- **Python 3.7+**
|
||||
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`)
|
||||
- **PySide2** — GUI-фреймворк
|
||||
- **lxml** — работа с XML
|
||||
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
|
||||
|
||||
Для сборки `.exe`:
|
||||
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
|
||||
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен)
|
||||
|
||||
|
||||
### Сборка:
|
||||
Если вы хотите собрать `DebugVarEdit.exe` самостоятельно из исходников, используйте скрипт **build/build_and_clean.py**. Он автоматически собирает проект с помощью Nuitka или PyInstaller:
|
||||
- Собирает проект в `DebugVarEdit.exe` в корневой папке.
|
||||
- Включает:
|
||||
- все необходимые `.dll` (например, `libclang.dll`),
|
||||
- иконку (`icon.ico`),
|
||||
- плагины PySide2.
|
||||
- Очищает временные папки после сборки:
|
||||
- `build_temp`
|
||||
- `__pycache__`
|
||||
- `DebugVarEdit_GUI.*`
|
||||
|
||||
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
|
||||
|
||||
### Установка зависимостей
|
||||
|
||||
```bash
|
||||
pip install PySide2 lxml nuitka pyinstaller
|
||||
```
|
||||
@@ -1,694 +0,0 @@
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from PySide2.QtWidgets import (
|
||||
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
|
||||
QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout
|
||||
)
|
||||
from PySide2.QtGui import QKeySequence, QKeyEvent
|
||||
from PySide2.QtCore import Qt, QStringListModel, QSettings
|
||||
import VariableTable
|
||||
import setupVars
|
||||
import myXML
|
||||
import time
|
||||
|
||||
|
||||
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
|
||||
|
||||
class VariableSelectorDialog(QDialog):
|
||||
def __init__(self, table, all_vars, structs, typedefs, xml_path=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Выбор переменных")
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.resize(600, 500)
|
||||
self.selected_names = []
|
||||
self._bckspc_pressed = False # флаг подавления добавления разделителя
|
||||
self.table = table
|
||||
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.node_index = {}
|
||||
self.xml_path = xml_path # сохраняем путь к xml
|
||||
self.manual_completion_active = False
|
||||
|
||||
# --- Добавляем чекбокс для автодополнения ---
|
||||
self.autocomplete_checkbox = QCheckBox("Включить автодополнение")
|
||||
self.autocomplete_checkbox.setChecked(True)
|
||||
|
||||
# Инициализируем QSettings с именем организации и приложения
|
||||
self.settings = QSettings("SET", "DebugVarEdit_VarsSelector")
|
||||
# Восстанавливаем сохранённое состояние чекбокса, если есть
|
||||
checked = self.settings.value("autocomplete_enabled", True, type=bool)
|
||||
self.autocomplete_checkbox.setChecked(checked)
|
||||
# При изменении состояния чекбокса сохраняем его
|
||||
self.autocomplete_checkbox.stateChanged.connect(self.save_checkbox_state)
|
||||
|
||||
self.search_input = QLineEdit()
|
||||
self.search_input.setPlaceholderText("Поиск по имени переменной...")
|
||||
self.search_input.textChanged.connect(self.on_search_text_changed)
|
||||
|
||||
self.tree = QTreeWidget()
|
||||
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
|
||||
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
|
||||
self.tree.setRootIsDecorated(True)
|
||||
self.tree.setUniformRowHeights(True)
|
||||
|
||||
self.tree.setStyleSheet("""
|
||||
QTreeWidget::item:selected {
|
||||
background-color: #87CEFA;
|
||||
color: black;
|
||||
}
|
||||
QTreeWidget::item:hover {
|
||||
background-color: #D3D3D3;
|
||||
}
|
||||
""")
|
||||
|
||||
self.btn_add = QPushButton("Добавить выбранные")
|
||||
self.btn_add.clicked.connect(self.on_add_clicked)
|
||||
|
||||
self.btn_delete = QPushButton("Удалить выбранные")
|
||||
self.btn_delete.clicked.connect(self.on_delete_clicked)
|
||||
|
||||
self.completer = QCompleter()
|
||||
self.completer.setCompletionMode(QCompleter.PopupCompletion) # важно!
|
||||
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
self.completer.setFilterMode(Qt.MatchContains)
|
||||
self.completer.setWidget(self.search_input)
|
||||
|
||||
|
||||
self.search_input.installEventFilter(self)
|
||||
|
||||
# Создаем горизонтальный layout для "Поиск:" и чекбокса справа
|
||||
search_layout = QHBoxLayout()
|
||||
label_search = QLabel("Поиск:")
|
||||
search_layout.addWidget(label_search, alignment=Qt.AlignLeft)
|
||||
search_layout.addStretch() # чтобы чекбокс прижался вправо
|
||||
search_layout.addWidget(self.autocomplete_checkbox, alignment=Qt.AlignRight)
|
||||
self.completer.activated[str].connect(self.insert_completion)
|
||||
|
||||
layout = QVBoxLayout()
|
||||
layout.addLayout(search_layout) # заменили label и чекбокс
|
||||
layout.addWidget(self.search_input)
|
||||
layout.addWidget(self.tree)
|
||||
layout.addWidget(self.btn_add)
|
||||
layout.addWidget(self.btn_delete)
|
||||
self.setLayout(layout)
|
||||
|
||||
self.expanded_vars = setupVars.expand_vars(self.all_vars, self.structs, self.typedefs)
|
||||
self.build_completion_list()
|
||||
self.populate_tree()
|
||||
|
||||
|
||||
def get_full_item_name(self, item):
|
||||
names = []
|
||||
while item:
|
||||
names.append(item.text(0))
|
||||
item = item.parent()
|
||||
return '.'.join(reversed(names))
|
||||
|
||||
def build_completion_list(self):
|
||||
# Собираем список полных имён всех переменных и вложенных полей
|
||||
completions = []
|
||||
|
||||
def recurse(var, prefix=''):
|
||||
fullname = f"{prefix}.{var['name']}" if prefix else var['name']
|
||||
completions.append(fullname)
|
||||
for child in var.get('children', []):
|
||||
recurse(child, fullname)
|
||||
|
||||
for v in self.expanded_vars:
|
||||
recurse(v)
|
||||
|
||||
self.all_completions = completions
|
||||
|
||||
def add_tree_item_recursively(self, parent, var):
|
||||
"""
|
||||
Рекурсивно добавляет переменную и её дочерние поля в дерево.
|
||||
Если parent == None, добавляет на верхний уровень.
|
||||
"""
|
||||
name = var['name']
|
||||
type_str = var.get('type', '')
|
||||
show_var = var.get('show_var', 'false') == 'true'
|
||||
|
||||
item = QTreeWidgetItem([name, type_str])
|
||||
item.setData(0, Qt.UserRole, name)
|
||||
full_name = self.get_full_item_name(item)
|
||||
self.node_index[full_name.lower()] = item
|
||||
|
||||
# Делаем bitfield-поля неактивными
|
||||
if "(bitfield:" in type_str:
|
||||
item.setDisabled(True)
|
||||
self.set_tool(item, "Битовые поля недоступны для выбора")
|
||||
|
||||
for i, attr in enumerate(['file', 'extern', 'static']):
|
||||
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
|
||||
|
||||
if show_var:
|
||||
item.setForeground(0, Qt.gray)
|
||||
item.setForeground(1, Qt.gray)
|
||||
self.set_tool(item, "Уже добавлена")
|
||||
|
||||
if parent is None:
|
||||
self.tree.addTopLevelItem(item)
|
||||
else:
|
||||
parent.addChild(item)
|
||||
|
||||
for child in var.get('children', []):
|
||||
self.add_tree_item_recursively(item, child)
|
||||
|
||||
|
||||
def populate_tree(self, vars_list=None):
|
||||
if vars_list is None:
|
||||
vars_list = self.expanded_vars
|
||||
self.tree.clear()
|
||||
self.node_index.clear()
|
||||
for var in vars_list:
|
||||
self.add_tree_item_recursively(None, var)
|
||||
|
||||
header = self.tree.header()
|
||||
header.setSectionResizeMode(QHeaderView.Interactive) # вручную можно менять
|
||||
self.tree.setColumnWidth(0, 400)
|
||||
self.tree.resizeColumnToContents(1)
|
||||
|
||||
def expand_to_level(self, item, level, current_level=0):
|
||||
"""
|
||||
Рекурсивно раскрывает узлы до заданного уровня.
|
||||
"""
|
||||
if current_level < level:
|
||||
item.setExpanded(True)
|
||||
else:
|
||||
item.setExpanded(False)
|
||||
|
||||
for i in range(item.childCount()):
|
||||
self.expand_to_level(item.child(i), level, current_level + 1)
|
||||
|
||||
def filter_tree(self):
|
||||
text = self.search_input.text().strip().lower()
|
||||
path_parts = text.split('.') if text else []
|
||||
filtered_vars = filter_vars(self.expanded_vars, path_parts)
|
||||
|
||||
# Сначала перерисовываем дерево
|
||||
self.populate_tree(filtered_vars)
|
||||
|
||||
# Теперь node_index уже пересоздан — можно работать
|
||||
expand_level = len(path_parts) - 1 if path_parts else 0
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
self.expand_to_level(item, expand_level)
|
||||
|
||||
# Раскрываем путь до точного совпадения
|
||||
if path_parts:
|
||||
fullname = '.'.join(path_parts)
|
||||
node = self.node_index.get(fullname.lower())
|
||||
if node:
|
||||
parent = node.parent()
|
||||
while parent:
|
||||
parent.setExpanded(True)
|
||||
parent = parent.parent()
|
||||
|
||||
|
||||
|
||||
def find_node_by_path(self, root_vars, path_list):
|
||||
current_level = root_vars
|
||||
node = None
|
||||
for part in path_list:
|
||||
node = None
|
||||
for var in current_level:
|
||||
if var['name'] == part:
|
||||
node = var
|
||||
break
|
||||
if node is None:
|
||||
return None
|
||||
current_level = node.get('children', [])
|
||||
return node
|
||||
|
||||
def update_completions(self, text=None):
|
||||
if text is None:
|
||||
text = self.search_input.text().strip()
|
||||
else:
|
||||
text = text.strip()
|
||||
|
||||
parts = self.split_path(text)
|
||||
path_parts = parts[:-1] if parts else []
|
||||
prefix = parts[-1].lower() if parts else ''
|
||||
ends_with_sep = text.endswith('.') or text.endswith('->') or text.endswith('[')
|
||||
is_index_suggestion = text.endswith('[')
|
||||
|
||||
completions = []
|
||||
|
||||
def find_exact_node(parts):
|
||||
if not parts:
|
||||
return None
|
||||
fullname = parts[0]
|
||||
for p in parts[1:]:
|
||||
fullname += '.' + p
|
||||
return self.node_index.get(fullname.lower())
|
||||
|
||||
if is_index_suggestion:
|
||||
base_text = text[:-1] # убираем '['
|
||||
parent_node = self.find_node_by_fullname(base_text)
|
||||
if not parent_node:
|
||||
base_text_clean = re.sub(r'\[\d+\]$', '', base_text)
|
||||
parent_node = self.find_node_by_fullname(base_text_clean)
|
||||
if parent_node:
|
||||
seen = set()
|
||||
for i in range(parent_node.childCount()):
|
||||
child = parent_node.child(i)
|
||||
cname = child.text(0)
|
||||
m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname)
|
||||
if m and cname not in seen:
|
||||
completions.append(cname)
|
||||
seen.add(cname)
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
return completions
|
||||
|
||||
if ends_with_sep:
|
||||
node = self.find_node_by_fullname(text[:-1])
|
||||
if node:
|
||||
completions.extend(node.child(i).text(0) for i in range(node.childCount()))
|
||||
elif not path_parts:
|
||||
# Первый уровень — только если имя начинается с prefix
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
name = item.text(0)
|
||||
if name.lower().startswith(prefix):
|
||||
completions.append(name)
|
||||
else:
|
||||
node = find_exact_node(path_parts)
|
||||
if node:
|
||||
for i in range(node.childCount()):
|
||||
child = node.child(i)
|
||||
name = child.text(0)
|
||||
name_parts = child.data(0, Qt.UserRole + 10)
|
||||
if name_parts is None:
|
||||
name_parts = self.split_path(name)
|
||||
child.setData(0, Qt.UserRole + 10, name_parts)
|
||||
if not name_parts:
|
||||
continue
|
||||
last_part = name_parts[-1].lower()
|
||||
if prefix == '' or last_part.startswith(prefix): # ← строго startswith
|
||||
completions.append(name)
|
||||
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
return completions
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# Функция для поиска узла с полным именем
|
||||
def find_node_by_fullname(self, name):
|
||||
return self.node_index.get(name.lower())
|
||||
|
||||
def insert_completion(self, text):
|
||||
node = self.find_node_by_fullname(text)
|
||||
if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->') or text.endswith('[')):
|
||||
# Определяем разделитель по имени первого ребёнка
|
||||
child_name = node.child(0).text(0)
|
||||
if child_name.startswith(text + '->'):
|
||||
text += '->'
|
||||
elif child_name.startswith(text + '.'):
|
||||
text += '.'
|
||||
elif '[' in child_name:
|
||||
text += '[' # для массивов
|
||||
else:
|
||||
text += '.' # fallback
|
||||
|
||||
if not self._bckspc_pressed:
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
|
||||
self.run_completions(text)
|
||||
else:
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if obj == self.search_input and isinstance(event, QKeyEvent):
|
||||
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
|
||||
self.manual_completion_active = True
|
||||
text = self.search_input.text().strip()
|
||||
self.run_completions(text)
|
||||
elif event.key() == Qt.Key_Escape:
|
||||
# Esc — выключаем ручной режим и скрываем подсказки, если autocomplete выключен
|
||||
if not self.autocomplete_checkbox.isChecked():
|
||||
self.manual_completion_active = False
|
||||
self.completer.popup().hide()
|
||||
return True
|
||||
|
||||
if event.key() == Qt.Key_Backspace:
|
||||
self._bckspc_pressed = True
|
||||
else:
|
||||
self._bckspc_pressed = False
|
||||
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
def run_completions(self, text):
|
||||
completions = self.update_completions(text)
|
||||
|
||||
if not self.autocomplete_checkbox.isChecked() and self._bckspc_pressed:
|
||||
text = text[:-1]
|
||||
|
||||
if len(completions) == 1 and completions[0].lower() == text.lower():
|
||||
# Найдем узел с таким именем
|
||||
def find_exact_item(name):
|
||||
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
if node.text(0).lower() == name.lower():
|
||||
return node
|
||||
for i in range(node.childCount()):
|
||||
stack.append(node.child(i))
|
||||
return None
|
||||
|
||||
node = find_exact_item(completions[0])
|
||||
if node and node.childCount() > 0:
|
||||
# Используем первую подсказку, чтобы определить нужный разделитель
|
||||
completions = self.update_completions(text + '.')
|
||||
suggestion = completions[0]
|
||||
|
||||
# Ищем, какой символ идёт после текущего текста
|
||||
separator = '.'
|
||||
if suggestion.startswith(text):
|
||||
rest = suggestion[len(text):]
|
||||
if rest.startswith(text + '->'):
|
||||
separator += '->'
|
||||
elif rest.startswith(text + '.'):
|
||||
separator += '.'
|
||||
elif '[' in rest:
|
||||
separator += '[' # для массивов
|
||||
else:
|
||||
separator += '.' # fallback
|
||||
|
||||
if not self._bckspc_pressed:
|
||||
self.search_input.setText(text + separator)
|
||||
completions = self.update_completions(text)
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
# Иначе просто показываем подсказки
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
if completions:
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
def on_search_text_changed(self, text):
|
||||
self.filter_tree()
|
||||
if text == None:
|
||||
text = self.search_input.text().strip()
|
||||
if self.autocomplete_checkbox.isChecked():
|
||||
self.run_completions(text)
|
||||
else:
|
||||
# Если выключено, показываем подсказки только если флаг ручного вызова True
|
||||
if self.manual_completion_active:
|
||||
self.run_completions(text)
|
||||
else:
|
||||
self.completer.popup().hide()
|
||||
|
||||
def on_add_clicked(self):
|
||||
self.selected_names = []
|
||||
self.tree.setFocus()
|
||||
|
||||
for item in self.tree.selectedItems():
|
||||
name = item.text(0) # имя переменной (в колонке 1)
|
||||
type_str = item.text(1) # тип переменной (в колонке 2)
|
||||
|
||||
if not name:
|
||||
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.done(QDialog.Accepted)
|
||||
|
||||
|
||||
def on_delete_clicked(self):
|
||||
selected_names = self._get_selected_var_names()
|
||||
if not selected_names:
|
||||
print("nothing selected")
|
||||
return
|
||||
|
||||
# Обновляем var_map и all_vars
|
||||
for name in selected_names:
|
||||
if name in self.var_map:
|
||||
self.var_map[name]['show_var'] = 'false'
|
||||
self.var_map[name]['enable'] = 'false'
|
||||
|
||||
for v in self.all_vars:
|
||||
if v['name'] == name:
|
||||
v['show_var'] = 'false'
|
||||
v['enable'] = 'false'
|
||||
break
|
||||
|
||||
# Проверка пути к XML
|
||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
|
||||
return
|
||||
|
||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||
if root is None:
|
||||
return
|
||||
|
||||
vars_section = root.find('variables')
|
||||
if vars_section is None:
|
||||
return
|
||||
|
||||
for var_elem in vars_section.findall('var'):
|
||||
name = var_elem.attrib.get('name')
|
||||
if name in selected_names:
|
||||
def set_text(tag, value):
|
||||
el = var_elem.find(tag)
|
||||
if el is None:
|
||||
el = ET.SubElement(var_elem, tag)
|
||||
el.text = value
|
||||
set_text('show_var', 'false')
|
||||
set_text('enable', 'false')
|
||||
|
||||
myXML.fwrite(root, self.xml_path)
|
||||
|
||||
self.done(QDialog.Accepted)
|
||||
|
||||
|
||||
def set_tool(self, item, text):
|
||||
item.setToolTip(0, text)
|
||||
item.setToolTip(1, text)
|
||||
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == Qt.Key_Delete:
|
||||
self.delete_selected_vars()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def delete_selected_vars(self):
|
||||
selected_names = self._get_selected_var_names()
|
||||
if not selected_names:
|
||||
print("nothing selected")
|
||||
return
|
||||
|
||||
# Обновляем var_map и all_vars
|
||||
for name in selected_names:
|
||||
if name in self.var_map:
|
||||
self.var_map[name]['show_var'] = 'false'
|
||||
self.var_map[name]['enable'] = 'false'
|
||||
|
||||
for v in self.all_vars:
|
||||
if v['name'] == name:
|
||||
v['show_var'] = 'false'
|
||||
v['enable'] = 'false'
|
||||
break
|
||||
|
||||
# Проверка пути к XML
|
||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
|
||||
return
|
||||
|
||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||
if root is None:
|
||||
return
|
||||
|
||||
vars_section = root.find('variables')
|
||||
if vars_section is None:
|
||||
return
|
||||
|
||||
for var_elem in vars_section.findall('var'):
|
||||
name = var_elem.attrib.get('name')
|
||||
if name in selected_names:
|
||||
def set_text(tag, value):
|
||||
el = var_elem.find(tag)
|
||||
if el is None:
|
||||
el = ET.SubElement(var_elem, tag)
|
||||
el.text = value
|
||||
set_text('show_var', 'false')
|
||||
set_text('enable', 'false')
|
||||
|
||||
myXML.fwrite(root, self.xml_path)
|
||||
|
||||
self.table.populate(self.all_vars, self.structs, None)
|
||||
|
||||
# Проверка пути к XML
|
||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.")
|
||||
return
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||
if root is None:
|
||||
return
|
||||
|
||||
vars_section = root.find('variables')
|
||||
if vars_section is None:
|
||||
return
|
||||
|
||||
removed_any = False
|
||||
for var_elem in list(vars_section.findall('var')):
|
||||
name = var_elem.attrib.get('name')
|
||||
if name in selected_names:
|
||||
vars_section.remove(var_elem)
|
||||
removed_any = True
|
||||
self.var_map.pop(name, None)
|
||||
|
||||
# Удаляем из all_vars (глобально)
|
||||
self.all_vars[:] = [v for v in self.all_vars if v['name'] not in selected_names]
|
||||
|
||||
# Удаляем из expanded_vars (тоже глобально)
|
||||
def filter_out_selected(vars_list):
|
||||
filtered = []
|
||||
for v in vars_list:
|
||||
if v['name'] not in selected_names:
|
||||
# Рекурсивно фильтруем детей, если есть
|
||||
if 'children' in v:
|
||||
v = v.copy()
|
||||
v['children'] = filter_out_selected(v['children'])
|
||||
filtered.append(v)
|
||||
return filtered
|
||||
|
||||
self.expanded_vars[:] = filter_out_selected(self.expanded_vars)
|
||||
if removed_any:
|
||||
myXML.fwrite(root, self.xml_path)
|
||||
|
||||
self.filter_tree()
|
||||
|
||||
def _get_selected_var_names(self):
|
||||
self.tree.setFocus()
|
||||
return [item.text(0) for item in self.tree.selectedItems() if item.text(0)]
|
||||
|
||||
|
||||
def save_checkbox_state(self):
|
||||
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
|
||||
|
||||
|
||||
|
||||
def split_path(self, path):
|
||||
"""
|
||||
Разбивает путь на компоненты:
|
||||
- 'foo[2].bar[1]->baz' → ['foo', [2]', 'bar', '[1]' 'baz']
|
||||
"""
|
||||
tokens = []
|
||||
token = ''
|
||||
i = 0
|
||||
while i < len(path):
|
||||
c = path[i]
|
||||
# Разделители: '->' и '.'
|
||||
if c == '-' and path[i:i+2] == '->':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
i += 2
|
||||
continue
|
||||
elif c == '.':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
i += 1
|
||||
continue
|
||||
elif c == '[':
|
||||
# Заканчиваем текущий токен, если есть
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
# Собираем индекс [N]
|
||||
idx = ''
|
||||
while i < len(path) and path[i] != ']':
|
||||
idx += path[i]
|
||||
i += 1
|
||||
if i < len(path) and path[i] == ']':
|
||||
idx += ']'
|
||||
i += 1
|
||||
tokens.append(idx)
|
||||
continue
|
||||
else:
|
||||
token += c
|
||||
i += 1
|
||||
if token:
|
||||
tokens.append(token)
|
||||
return tokens
|
||||
|
||||
|
||||
def filter_vars(vars_list, path_parts):
|
||||
"""Рекурсивно фильтруем vars_list по path_parts по вхождению на любом уровне."""
|
||||
filtered = []
|
||||
|
||||
def matches_path(name, search_parts):
|
||||
name_parts = name.lower().split('.')
|
||||
if len(name_parts) < len(search_parts):
|
||||
return False
|
||||
|
||||
for sp, np in zip(search_parts, name_parts):
|
||||
if sp not in np:
|
||||
return False
|
||||
return True
|
||||
|
||||
for var in vars_list:
|
||||
fullname = var.get('fullname', var['name']) # желательно иметь полное имя
|
||||
if not path_parts or matches_path(fullname, path_parts):
|
||||
new_var = var.copy()
|
||||
if 'children' in var:
|
||||
new_var['children'] = filter_vars(var['children'], path_parts)
|
||||
filtered.append(new_var)
|
||||
else:
|
||||
if 'children' in var:
|
||||
child_filtered = filter_vars(var['children'], path_parts)
|
||||
if child_filtered:
|
||||
new_var = var.copy()
|
||||
new_var['children'] = child_filtered
|
||||
filtered.append(new_var)
|
||||
|
||||
return filtered
|
||||
|
||||
|
||||
@@ -1,271 +0,0 @@
|
||||
from PySide2.QtWidgets import (
|
||||
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
|
||||
QAbstractItemView, QHeaderView, QLabel
|
||||
)
|
||||
from PySide2.QtGui import QColor, QBrush, QPalette
|
||||
from PySide2.QtCore import Qt
|
||||
from enum import IntEnum
|
||||
from generateVars import type_map
|
||||
|
||||
class rows(IntEnum):
|
||||
No = 0
|
||||
include = 1
|
||||
name = 2
|
||||
type = 3
|
||||
pt_type = 4
|
||||
iq_type = 5
|
||||
ret_type = 6
|
||||
short_name = 7
|
||||
|
||||
|
||||
class VariableTableWidget(QTableWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(0, 8, parent)
|
||||
# Таблица переменных
|
||||
self.setHorizontalHeaderLabels([
|
||||
'№', # новый столбец
|
||||
'En',
|
||||
'Name',
|
||||
'Origin Type',
|
||||
'Pointer Type',
|
||||
'IQ Type',
|
||||
'Return Type',
|
||||
'Short Name'
|
||||
])
|
||||
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||||
self.var_list = []
|
||||
|
||||
self.type_options = list(dict.fromkeys(type_map.values()))
|
||||
self.display_type_options = [t.replace('pt_', '') for t in self.type_options]
|
||||
self.iq_types = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
|
||||
|
||||
header = self.horizontalHeader()
|
||||
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
|
||||
|
||||
for col in range(self.columnCount()):
|
||||
if col == self.columnCount() - 1:
|
||||
header.setSectionResizeMode(col, QHeaderView.Stretch)
|
||||
else:
|
||||
header.setSectionResizeMode(col, QHeaderView.Interactive)
|
||||
|
||||
parent_widget = self.parentWidget()
|
||||
# Сделаем колонки с номерами фиксированной ширины
|
||||
self.setColumnWidth(rows.No, 30)
|
||||
self.setColumnWidth(rows.include, 30)
|
||||
self.setColumnWidth(rows.pt_type, 85)
|
||||
self.setColumnWidth(rows.iq_type, 85)
|
||||
self.setColumnWidth(rows.ret_type, 85)
|
||||
|
||||
self.setColumnWidth(rows.name, 300)
|
||||
self.setColumnWidth(rows.type, 100)
|
||||
self._resizing = False
|
||||
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
|
||||
|
||||
|
||||
def populate(self, vars_list, structs, on_change_callback):
|
||||
self.var_list = vars_list
|
||||
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)]
|
||||
|
||||
# --- ДО: удаляем отображение структур и union-переменных
|
||||
for var in vars_list:
|
||||
pt_type = var.get('pt_type', '')
|
||||
if 'struct' in pt_type or 'union' in pt_type:
|
||||
var['show_var'] = 'false'
|
||||
var['enable'] = 'false'
|
||||
|
||||
|
||||
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
|
||||
self.setRowCount(len(filtered_vars))
|
||||
self.verticalHeader().setVisible(False)
|
||||
style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt; font-family: 'Segoe UI';"
|
||||
|
||||
|
||||
|
||||
|
||||
for row, var in enumerate(filtered_vars):
|
||||
# №
|
||||
no_item = QTableWidgetItem(str(row))
|
||||
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.setItem(row, rows.No, no_item)
|
||||
|
||||
# Enable
|
||||
cb = QCheckBox()
|
||||
cb.setChecked(var.get('enable', 'false') == 'true')
|
||||
cb.stateChanged.connect(on_change_callback)
|
||||
cb.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.include, cb)
|
||||
|
||||
# Name
|
||||
name_edit = QLineEdit(var['name'])
|
||||
if var['type'] in structs:
|
||||
completer = QCompleter(structs[var['type']].keys())
|
||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
name_edit.setCompleter(completer)
|
||||
name_edit.textChanged.connect(on_change_callback)
|
||||
name_edit.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.name, name_edit)
|
||||
|
||||
# Origin Type (readonly)
|
||||
origin_item = QTableWidgetItem(var.get('type', ''))
|
||||
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
origin_item.setToolTip(var.get('type', '')) # Всплывающая подсказка
|
||||
origin_item.setForeground(QBrush(Qt.black))
|
||||
self.setItem(row, rows.type, origin_item)
|
||||
|
||||
# pt_type
|
||||
pt_combo = QComboBox()
|
||||
pt_combo.addItems(self.display_type_options)
|
||||
value = var['pt_type'].replace('pt_', '')
|
||||
if value not in self.display_type_options:
|
||||
pt_combo.addItem(value)
|
||||
pt_combo.setCurrentText(value)
|
||||
pt_combo.currentTextChanged.connect(on_change_callback)
|
||||
pt_combo.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.pt_type, pt_combo)
|
||||
|
||||
# iq_type
|
||||
iq_combo = QComboBox()
|
||||
iq_combo.addItems(self.iq_types)
|
||||
value = var['iq_type'].replace('t_', '')
|
||||
if value not in self.iq_types:
|
||||
iq_combo.addItem(value)
|
||||
iq_combo.setCurrentText(value)
|
||||
iq_combo.currentTextChanged.connect(on_change_callback)
|
||||
iq_combo.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.iq_type, iq_combo)
|
||||
|
||||
# return_type
|
||||
ret_combo = QComboBox()
|
||||
ret_combo.addItems(self.iq_types)
|
||||
ret_combo.setCurrentText(var.get('return_type', ''))
|
||||
ret_combo.currentTextChanged.connect(on_change_callback)
|
||||
ret_combo.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.ret_type, ret_combo)
|
||||
|
||||
# short_name
|
||||
short_name_val = var.get('shortname', var['name'])
|
||||
short_name_edit = QLineEdit(short_name_val)
|
||||
short_name_edit.textChanged.connect(on_change_callback)
|
||||
short_name_edit.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.short_name, short_name_edit)
|
||||
|
||||
self.check()
|
||||
|
||||
def check(self):
|
||||
warning_color = (QColor("#FFFACD")) # Жёлтый для длинных shortname
|
||||
error_color = (QColor("#FFB6C1")) # Светло-красный для отсутствующих переменных
|
||||
tooltip_shortname = "Short Name длиннее 10 символов — будет обрезано при генерации"
|
||||
tooltip_missing = f'Имя переменной не найдено среди переменных. Добавьте её через кнопку "Добавить переменные"'
|
||||
|
||||
for row in range(self.rowCount()):
|
||||
# Получаем имя переменной (столбец `name`)
|
||||
name_widget = self.cellWidget(row, rows.name)
|
||||
name = name_widget.text() if name_widget else ""
|
||||
|
||||
# Получаем shortname
|
||||
short_name_edit = self.cellWidget(row, rows.short_name)
|
||||
shortname = short_name_edit.text() if short_name_edit else ""
|
||||
|
||||
# Флаги ошибок
|
||||
long_shortname = len(shortname) > 10
|
||||
found = any(v.get('name') == name for v in self.var_list)
|
||||
|
||||
# Выбираем цвет и подсказку
|
||||
color = None
|
||||
tooltip = ""
|
||||
if not found:
|
||||
color = error_color
|
||||
tooltip = tooltip_missing
|
||||
elif long_shortname:
|
||||
color = warning_color
|
||||
tooltip = tooltip_shortname
|
||||
|
||||
self.highlight_row(row, color, tooltip)
|
||||
|
||||
|
||||
def read_data(self):
|
||||
result = []
|
||||
for row in range(self.rowCount()):
|
||||
cb = self.cellWidget(row, rows.include)
|
||||
name = self.cellWidget(row, rows.name).text()
|
||||
pt = self.cellWidget(row, rows.pt_type).currentText()
|
||||
iq = self.cellWidget(row, rows.iq_type).currentText()
|
||||
ret = self.cellWidget(row, rows.ret_type).currentText()
|
||||
shortname = self.cellWidget(row, rows.short_name).text()
|
||||
origin_type = self.item(row, rows.type).text()
|
||||
|
||||
result.append({
|
||||
'show_var': True,
|
||||
'enable': cb.isChecked(),
|
||||
'name': name,
|
||||
'pt_type': f'pt_{pt}',
|
||||
'iq_type': f't_{iq}',
|
||||
'return_type': f't_{ret}',
|
||||
'shortname': shortname,
|
||||
'type': origin_type,
|
||||
})
|
||||
return result
|
||||
|
||||
def on_section_resized(self, logicalIndex, oldSize, newSize):
|
||||
if self._resizing:
|
||||
return # предотвращаем рекурсию
|
||||
|
||||
min_width = 50
|
||||
delta = newSize - oldSize
|
||||
right_index = logicalIndex + 1
|
||||
|
||||
if right_index >= self.columnCount():
|
||||
# Если правая колока - нет соседа, ограничиваем минимальную ширину
|
||||
if newSize < min_width:
|
||||
self._resizing = True
|
||||
self.setColumnWidth(logicalIndex, min_width)
|
||||
self._resizing = False
|
||||
return
|
||||
|
||||
self._resizing = True
|
||||
try:
|
||||
right_width = self.columnWidth(right_index)
|
||||
new_right_width = right_width - delta
|
||||
|
||||
# Если соседняя колонка станет уже минимальной - подкорректируем левую
|
||||
if new_right_width < min_width:
|
||||
new_right_width = min_width
|
||||
newSize = oldSize + (right_width - min_width)
|
||||
self.setColumnWidth(logicalIndex, newSize)
|
||||
|
||||
self.setColumnWidth(right_index, new_right_width)
|
||||
finally:
|
||||
self._resizing = False
|
||||
|
||||
|
||||
|
||||
|
||||
def highlight_row(self, row: int, color: QColor = None, tooltip: str = ""):
|
||||
"""
|
||||
Подсвечивает строку таблицы цветом `color`, не меняя шрифт.
|
||||
Работает с QLineEdit, QComboBox, QCheckBox (включая обёртки).
|
||||
Если `color=None`, сбрасывает подсветку.
|
||||
"""
|
||||
css_reset = "background-color: none; font: inherit;"
|
||||
css_color = f"background-color: {color.name()};" if color else css_reset
|
||||
|
||||
for col in range(self.columnCount()):
|
||||
item = self.item(row, col)
|
||||
widget = self.cellWidget(row, col)
|
||||
|
||||
# Подсветка обычной item-ячейки (например, тип переменной)
|
||||
if item is not None:
|
||||
if color:
|
||||
item.setBackground(QBrush(color))
|
||||
item.setToolTip(tooltip)
|
||||
else:
|
||||
item.setBackground(QBrush(Qt.NoBrush))
|
||||
item.setToolTip("")
|
||||
|
||||
# Подсветка виджетов — здесь главная доработка
|
||||
elif widget is not None:
|
||||
# Надёжная подсветка: через styleSheet
|
||||
widget.setStyleSheet(css_color)
|
||||
widget.setToolTip(tooltip if color else "")
|
||||
@@ -2,13 +2,11 @@ import subprocess
|
||||
import shutil
|
||||
import os
|
||||
from pathlib import Path
|
||||
from PIL import Image # нужна библиотека Pillow для конвертации PNG в ICO
|
||||
import PySide2
|
||||
from PyInstaller.utils.hooks import collect_data_files
|
||||
# install:
|
||||
# install: pip install PySide2 lxml nuitka pyinstaller
|
||||
# - PyInstaller
|
||||
# - nuitka
|
||||
# - Pillow
|
||||
# - PySide2
|
||||
# - clang
|
||||
|
||||
@@ -25,6 +23,13 @@ SPEC_PATH = WORK_PATH
|
||||
ICON_PATH = SRC_PATH / "icon.png"
|
||||
ICON_ICO_PATH = SRC_PATH / "icon.ico"
|
||||
|
||||
TEMP_FOLDERS = [
|
||||
"build_temp",
|
||||
"__pycache__",
|
||||
"DebugVarEdit_GUI.build",
|
||||
"DebugVarEdit_GUI.onefile-build",
|
||||
"DebugVarEdit_GUI.dist"
|
||||
]
|
||||
# === Пути к DLL и прочим зависимостям ===
|
||||
LIBS = {
|
||||
"libclang.dll": SRC_PATH / "libclang.dll"
|
||||
@@ -48,7 +53,7 @@ for name, path in LIBS.items():
|
||||
print(f"WARNING: {path.name} не найден — он не будет включён в сборку")
|
||||
|
||||
def clean_temp():
|
||||
for folder in ["build_temp", "__pycache__", "DebugVarEdit_GUI.build", "DebugVarEdit_GUI.dist", "DebugVarEdit_GUI.onefile-build"]:
|
||||
for folder in TEMP_FOLDERS:
|
||||
path = Path(folder)
|
||||
if path.exists():
|
||||
shutil.rmtree(path, ignore_errors=True)
|
||||
@@ -1,20 +1,21 @@
|
||||
# build command
|
||||
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generateVars.py
|
||||
# pyinstaller --onefile --distpath . --workpath ./build --specpath ./build generate_debug_vars.py
|
||||
# start script
|
||||
# generateVars.exe F:\Work\Projects\TMS\TMS_new_bus\ Src/DebugTools/vars.xml Src/DebugTools
|
||||
# generate_debug_vars.exe F:\Work\Projects\TMS\TMS_new_bus\ Src/DebugTools/vars.xml Src/DebugTools
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
import lxml.etree as ET
|
||||
from pathlib import Path
|
||||
from xml.dom import minidom
|
||||
import myXML
|
||||
import argparse
|
||||
|
||||
shortnameSize = 10
|
||||
|
||||
# === Словарь соответствия типов XML → DebugVarType_t ===
|
||||
type_map = dict([
|
||||
type_map_tms = dict([
|
||||
*[(k, 'pt_int8') for k in ('signed char', 'char')],
|
||||
*[(k, 'pt_int16') for k in ('int', 'int16', 'short')],
|
||||
*[(k, 'pt_int32') for k in ('long', 'int32', '_iqx')],
|
||||
@@ -52,6 +53,150 @@ type_map = dict([
|
||||
('struct[]', 'pt_arr_struct'),
|
||||
('union[]', 'pt_arr_union'),
|
||||
])
|
||||
# === Словарь соответствия типов XML → DebugVarType_t ===
|
||||
type_map_stm32 = dict([
|
||||
|
||||
*[(k, 'pt_int8') for k in (
|
||||
'int8_t', 'signed char', 'char'
|
||||
)],
|
||||
|
||||
# --- 8-bit unsigned ---
|
||||
*[(k, 'pt_uint8') for k in (
|
||||
'uint8_t', 'unsigned char'
|
||||
)],
|
||||
|
||||
# --- 16-bit signed ---
|
||||
*[(k, 'pt_int16') for k in (
|
||||
'int16_t', 'short', 'short int', 'signed short', 'signed short int'
|
||||
)],
|
||||
|
||||
# --- 16-bit unsigned ---
|
||||
*[(k, 'pt_uint16') for k in (
|
||||
'uint16_t', 'unsigned short', 'unsigned short int'
|
||||
)],
|
||||
|
||||
# --- 32-bit signed ---
|
||||
*[(k, 'pt_int32') for k in (
|
||||
'int32_t', 'int', 'signed', 'signed int'
|
||||
)],
|
||||
|
||||
# --- 32-bit unsigned ---
|
||||
*[(k, 'pt_uint32') for k in (
|
||||
'uint32_t', 'unsigned', 'unsigned int'
|
||||
)],
|
||||
|
||||
# --- 64-bit signed ---
|
||||
*[(k, 'pt_int64') for k in (
|
||||
'int64_t', 'long long', 'signed long long', 'signed long long int'
|
||||
)],
|
||||
|
||||
# --- 64-bit unsigned ---
|
||||
*[(k, 'pt_uint64') for k in (
|
||||
'uint64_t', 'unsigned long long', 'unsigned long long int'
|
||||
)],
|
||||
|
||||
# --- Float ---
|
||||
*[(k, 'pt_float') for k in (
|
||||
'float', 'float32_t'
|
||||
)],
|
||||
|
||||
# --- Struct and Union ---
|
||||
('struct', 'pt_struct'),
|
||||
('union', 'pt_union'),
|
||||
('struct*', 'pt_ptr_struct'),
|
||||
('union*', 'pt_ptr_union'),
|
||||
('struct[]', 'pt_arr_struct'),
|
||||
('union[]', 'pt_arr_union'),
|
||||
|
||||
# === POINTERS ===
|
||||
|
||||
# 8-bit
|
||||
*[(k, 'pt_ptr_int8') for k in (
|
||||
'int8_t*', 'signed char*', 'char*'
|
||||
)],
|
||||
*[(k, 'pt_ptr_uint8') for k in (
|
||||
'uint8_t*', 'unsigned char*'
|
||||
)],
|
||||
|
||||
# 16-bit
|
||||
*[(k, 'pt_ptr_int16') for k in (
|
||||
'int16_t*', 'short*', 'short int*', 'signed short*', 'signed short int*'
|
||||
)],
|
||||
*[(k, 'pt_ptr_uint16') for k in (
|
||||
'uint16_t*', 'unsigned short*', 'unsigned short int*'
|
||||
)],
|
||||
|
||||
# 32-bit
|
||||
*[(k, 'pt_ptr_int32') for k in (
|
||||
'int32_t*', 'int*', 'signed*', 'signed int*'
|
||||
)],
|
||||
*[(k, 'pt_ptr_uint32') for k in (
|
||||
'uint32_t*', 'unsigned*', 'unsigned int*'
|
||||
)],
|
||||
|
||||
# 64-bit
|
||||
*[(k, 'pt_ptr_int64') for k in (
|
||||
'int64_t*', 'long long*', 'signed long long*', 'signed long long int*'
|
||||
)],
|
||||
*[(k, 'pt_ptr_uint64') for k in (
|
||||
'uint64_t*', 'unsigned long long*', 'unsigned long long int*'
|
||||
)],
|
||||
|
||||
# float*
|
||||
*[(k, 'pt_ptr_float') for k in (
|
||||
'float*', 'float32_t*'
|
||||
)],
|
||||
|
||||
# === ARRAYS ===
|
||||
|
||||
# 8-bit
|
||||
*[(k, 'pt_arr_int8') for k in (
|
||||
'int8_t[]', 'signed char[]', 'char[]'
|
||||
)],
|
||||
*[(k, 'pt_arr_uint8') for k in (
|
||||
'uint8_t[]', 'unsigned char[]'
|
||||
)],
|
||||
|
||||
# 16-bit
|
||||
*[(k, 'pt_arr_int16') for k in (
|
||||
'int16_t[]', 'short[]', 'short int[]', 'signed short[]', 'signed short int[]'
|
||||
)],
|
||||
*[(k, 'pt_arr_uint16') for k in (
|
||||
'uint16_t[]', 'unsigned short[]', 'unsigned short int[]'
|
||||
)],
|
||||
|
||||
# 32-bit
|
||||
*[(k, 'pt_arr_int32') for k in (
|
||||
'int32_t[]', 'int[]', 'signed[]', 'signed int[]'
|
||||
)],
|
||||
*[(k, 'pt_arr_uint32') for k in (
|
||||
'uint32_t[]', 'unsigned[]', 'unsigned int[]'
|
||||
)],
|
||||
|
||||
# 64-bit
|
||||
*[(k, 'pt_arr_int64') for k in (
|
||||
'int64_t[]', 'long long[]', 'signed long long[]', 'signed long long int[]'
|
||||
)],
|
||||
*[(k, 'pt_arr_uint64') for k in (
|
||||
'uint64_t[]', 'unsigned long long[]', 'unsigned long long int[]'
|
||||
)],
|
||||
|
||||
# float[]
|
||||
*[(k, 'pt_arr_float') for k in (
|
||||
'float[]', 'float32_t[]'
|
||||
)],
|
||||
])
|
||||
type_map = type_map_tms
|
||||
stm_flag_global = 0
|
||||
def choose_type_map(stm_flag):
|
||||
global type_map # объявляем, что будем менять глобальную переменную
|
||||
global stm_flag_global # объявляем, что будем менять глобальную переменную
|
||||
if stm_flag:
|
||||
type_map = type_map_stm32
|
||||
stm_flag_global = 1
|
||||
else:
|
||||
type_map = type_map_tms
|
||||
stm_flag_global = 0
|
||||
|
||||
def map_type_to_pt(typename, varname=None, typedef_map=None):
|
||||
typename_orig = typename.strip()
|
||||
@@ -140,15 +285,20 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
|
||||
Возвращает True если что-то добавлено и XML перезаписан, иначе False.
|
||||
"""
|
||||
|
||||
pattern = re.compile(
|
||||
r'{\s*\(uint8_t\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?(?:(?:\.|->)[a-zA-Z_][a-zA-Z0-9_]*(?:\[.*?\])?)*)\s*,\s*'
|
||||
r'(pt_\w+)\s*,\s*'
|
||||
r'(t?_?iq\w+)\s*,\s*'
|
||||
r'(t?_?iq\w+)\s*,\s*'
|
||||
r'"([^"]+)"'
|
||||
)
|
||||
# Считываем существующие переменные
|
||||
parsed_vars = {}
|
||||
if os.path.isfile(output_path):
|
||||
with open(output_path, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
for line in f:
|
||||
# {(char *)&some.deep.var.name , pt_uint16 , t_iq15 , "ShortName"},
|
||||
m = re.match(
|
||||
r'{\s*\(char\s*\*\)\s*&([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)*)\s*,\s*(pt_\w+)\s*,\s*(t?iq_\w+)\s*,\s*"([^"]+)"',
|
||||
line)
|
||||
# {(uint8_t *)&some.deep.var.name , pt_uint16 , t_iq15 , t_iq10, "ShortName"},
|
||||
m = pattern.search(line)
|
||||
if m:
|
||||
full_varname = m.group(1) # e.g., some.deep.var.name
|
||||
pt_type = m.group(2)
|
||||
@@ -285,12 +435,27 @@ def read_vars_from_xml(proj_path, xml_rel_path):
|
||||
return unique_vars, include_files, vars_need_extern
|
||||
|
||||
|
||||
def read_file_try_encodings(filepath):
|
||||
if not os.path.isfile(filepath):
|
||||
# Файл не существует — просто вернуть пустую строку или None
|
||||
return "", None
|
||||
for enc in ['utf-8', 'cp1251']:
|
||||
try:
|
||||
with open(filepath, 'r', encoding=enc) as f:
|
||||
content = f.read()
|
||||
return content, enc
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
|
||||
|
||||
|
||||
|
||||
def generate_vars_file(proj_path, xml_path, output_dir):
|
||||
output_dir = os.path.join(proj_path, output_dir)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
output_path = os.path.join(output_dir, 'debug_vars.c')
|
||||
LIBC_path = os.path.join(output_dir, 'debug_tools.c')
|
||||
LIBH_path = os.path.join(output_dir, 'debug_tools.h')
|
||||
|
||||
|
||||
# Запись новых переменных для в XML
|
||||
@@ -338,7 +503,7 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
||||
# Дополнительные поля, например комментарий
|
||||
comment = info.get("comment", "")
|
||||
short_name = info.get("shortname", f'"{vname}"')
|
||||
short_trimmed = short_name[:10] # ограничиваем длину до 10
|
||||
short_trimmed = short_name[:shortnameSize] # ограничиваем длину до 10
|
||||
|
||||
if pt_type not in ('pt_struct', 'pt_union'):
|
||||
f_name = f'{vname},'
|
||||
@@ -348,7 +513,7 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
||||
f_short_name = f'"{short_trimmed}"' # оборачиваем в кавычки
|
||||
# Добавим комментарий после записи, если он есть
|
||||
comment_str = f' // {comment}' if comment else ''
|
||||
line = f'{{(char *)&{f_name:<45} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}'
|
||||
line = f'{{(uint8_t *)&{f_name:<58} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}'
|
||||
new_debug_vars[vname] = line
|
||||
|
||||
else:
|
||||
@@ -394,14 +559,19 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
||||
|
||||
out_lines.append(f'\n\n// Определение массива с указателями на переменные для отладки')
|
||||
out_lines.append(f'int DebugVar_Qnt = {len(all_debug_lines)};')
|
||||
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")')
|
||||
if stm_flag_global == 0:
|
||||
out_lines.append('#pragma DATA_SECTION(dbg_vars,".dbgvar_info")')
|
||||
out_lines.append('// pointer_type iq_type return_iq_type short_name')
|
||||
out_lines.append('DebugVar_t dbg_vars[] = {\\')
|
||||
out_lines.extend(all_debug_lines)
|
||||
out_lines.append('};')
|
||||
out_lines.append('')
|
||||
# Выберем кодировку для записи файла
|
||||
# Если встречается несколько, возьмем первую из set
|
||||
enc_to_write = 'cp1251'
|
||||
if stm_flag_global == 0:
|
||||
enc_to_write = 'cp1251'
|
||||
else:
|
||||
enc_to_write = 'utf-8'
|
||||
|
||||
#print("== GLOBAL VARS FOUND ==")
|
||||
#for vname, (vtype, path) in vars_in_c.items():
|
||||
@@ -411,6 +581,16 @@ def generate_vars_file(proj_path, xml_path, output_dir):
|
||||
with open(output_path, 'w', encoding=enc_to_write) as f:
|
||||
f.write('\n'.join(out_lines))
|
||||
|
||||
if os.path.isfile(LIBC_path):
|
||||
libc_code, _ = read_file_try_encodings(LIBC_path)
|
||||
with open(LIBC_path, 'w', encoding=enc_to_write) as f:
|
||||
f.write(libc_code)
|
||||
|
||||
if os.path.isfile(LIBH_path):
|
||||
libh_code, _ = read_file_try_encodings(LIBH_path)
|
||||
with open(LIBH_path, 'w', encoding=enc_to_write) as f:
|
||||
f.write(libh_code)
|
||||
|
||||
print(f'Файл debug_vars.c сгенерирован в кодировке, переменных: {len(all_debug_lines)}')
|
||||
|
||||
|
||||
@@ -472,16 +652,17 @@ Usage example:
|
||||
print(f"Error: Project path '{proj_path}' не является директорией или не существует.")
|
||||
sys.exit(1)
|
||||
|
||||
generate_vars_file(proj_path, xml_path_rel, output_dir_rel)
|
||||
generate_vars_file(proj_path, xml_path_rel, output_dir_rel, 0)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
def run_generate(proj_path, xml_path, output_dir):
|
||||
def run_generate(proj_path, xml_path, output_dir, shortname_size):
|
||||
import os
|
||||
|
||||
global shortnameSize
|
||||
shortnameSize = shortname_size
|
||||
# Normalize absolute paths
|
||||
proj_path = os.path.abspath(proj_path)
|
||||
xml_path_abs = os.path.abspath(xml_path)
|
||||
242
Src/makefile_parser.py
Normal file
242
Src/makefile_parser.py
Normal file
@@ -0,0 +1,242 @@
|
||||
import os
|
||||
import re
|
||||
from lxml import etree as ET
|
||||
|
||||
|
||||
def strip_single_line_comments(code):
|
||||
# Удалим // ... до конца строки
|
||||
return re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
|
||||
|
||||
def read_file_try_encodings(filepath):
|
||||
if not os.path.isfile(filepath):
|
||||
# Файл не существует — просто вернуть пустую строку или None
|
||||
return "", None
|
||||
for enc in ['utf-8', 'cp1251']:
|
||||
try:
|
||||
with open(filepath, 'r', encoding=enc) as f:
|
||||
content = f.read()
|
||||
content = strip_single_line_comments(content)
|
||||
return content, enc
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
|
||||
|
||||
def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
|
||||
"""
|
||||
Рекурсивно ищет все include-файлы начиная с заданных c_files.
|
||||
Возвращает множество ПОЛНЫХ ПУТЕЙ к найденным include-файлам.
|
||||
|
||||
include_dirs — список директорий, в которых ищем include-файлы.
|
||||
processed_files — множество уже обработанных файлов (для избежания циклов).
|
||||
"""
|
||||
if processed_files is None:
|
||||
processed_files = set()
|
||||
|
||||
include_files = set()
|
||||
include_pattern = re.compile(r'#include\s+"([^"]+)"')
|
||||
|
||||
for cfile in c_files:
|
||||
norm_path = os.path.normpath(cfile)
|
||||
if norm_path in processed_files:
|
||||
continue
|
||||
processed_files.add(norm_path)
|
||||
|
||||
content, _ = read_file_try_encodings(cfile)
|
||||
if content is None:
|
||||
continue
|
||||
includes = include_pattern.findall(content)
|
||||
for inc in includes:
|
||||
# Ищем полный путь к include-файлу в include_dirs
|
||||
inc_full_path = None
|
||||
for dir_ in include_dirs:
|
||||
candidate = os.path.normpath(os.path.join(dir_, inc))
|
||||
if os.path.isfile(candidate):
|
||||
inc_full_path = os.path.abspath(candidate)
|
||||
break
|
||||
|
||||
if inc_full_path:
|
||||
include_files.add(inc_full_path)
|
||||
|
||||
# Рекурсивный обход вложенных includes
|
||||
if inc_full_path not in processed_files:
|
||||
nested_includes = find_all_includes_recursive(
|
||||
[inc_full_path], include_dirs, processed_files
|
||||
)
|
||||
include_files.update(nested_includes)
|
||||
|
||||
return include_files
|
||||
|
||||
|
||||
def parse_objects_list(objects_list_path, project_root):
|
||||
c_files = []
|
||||
include_dirs = set()
|
||||
|
||||
if not os.path.isfile(objects_list_path):
|
||||
return c_files, include_dirs
|
||||
|
||||
with open(objects_list_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
for line in lines:
|
||||
line = line.strip().strip('"').replace("\\", "/")
|
||||
if line.endswith(".o"):
|
||||
c_file = re.sub(r"\.o$", ".c", line)
|
||||
abs_path = os.path.normpath(os.path.join(project_root, c_file))
|
||||
if os.path.isfile(abs_path):
|
||||
if not any(x in abs_path for x in ["DebugTools", "v120", "v100"]):
|
||||
c_files.append(abs_path)
|
||||
include_dirs.add(os.path.dirname(abs_path))
|
||||
|
||||
return c_files, include_dirs
|
||||
|
||||
|
||||
def parse_uvprojx(uvprojx_path):
|
||||
import xml.etree.ElementTree as ET
|
||||
import os
|
||||
|
||||
tree = ET.parse(uvprojx_path)
|
||||
root = tree.getroot()
|
||||
|
||||
project_dir = os.path.dirname(os.path.abspath(uvprojx_path))
|
||||
|
||||
c_files = []
|
||||
include_dirs = set()
|
||||
defines = set()
|
||||
|
||||
# Найдём C-файлы и директории
|
||||
for file_elem in root.findall(".//FilePath"):
|
||||
file_path = file_elem.text
|
||||
if file_path:
|
||||
abs_path = os.path.normpath(os.path.join(project_dir, file_path))
|
||||
if os.path.isfile(abs_path):
|
||||
if abs_path.endswith(".c"):
|
||||
c_files.append(abs_path)
|
||||
include_dirs.add(os.path.dirname(abs_path))
|
||||
|
||||
# Включаем IncludePath
|
||||
for inc_path_elem in root.findall(".//IncludePath"):
|
||||
path_text = inc_path_elem.text
|
||||
if path_text:
|
||||
paths = path_text.split(';')
|
||||
for p in paths:
|
||||
p = p.strip()
|
||||
if p:
|
||||
abs_inc_path = os.path.normpath(os.path.join(project_dir, p))
|
||||
if os.path.isdir(abs_inc_path):
|
||||
include_dirs.add(abs_inc_path)
|
||||
|
||||
# Добавим <Define>
|
||||
for define_elem in root.findall(".//Define"):
|
||||
def_text = define_elem.text
|
||||
if def_text:
|
||||
for d in def_text.split(','):
|
||||
d = d.strip()
|
||||
if d:
|
||||
defines.add(d)
|
||||
|
||||
h_files = find_all_includes_recursive(c_files, include_dirs)
|
||||
|
||||
return sorted(c_files), sorted(h_files), sorted(include_dirs), sorted(defines)
|
||||
|
||||
|
||||
|
||||
def parse_makefile(makefile_path, proj_path):
|
||||
import os
|
||||
import re
|
||||
|
||||
project_root = os.path.abspath(proj_path)
|
||||
c_files = []
|
||||
include_dirs = set()
|
||||
defines = [] # Заглушка: нет define-параметров из Makefile
|
||||
|
||||
with open(makefile_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
raw_entries = []
|
||||
collecting = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
|
||||
if (("ORDERED_OBJS" in stripped or "C_SOURCES" in stripped) and ("+=" in stripped or "=" in stripped)):
|
||||
collecting = True
|
||||
|
||||
if collecting:
|
||||
line_clean = stripped.rstrip("\\").strip()
|
||||
if line_clean:
|
||||
line_clean = re.sub(r"\$\([^)]+\)", "", line_clean)
|
||||
line_clean = re.sub(r"\$\{[^}]+\}", "", line_clean)
|
||||
raw_entries.append(line_clean)
|
||||
|
||||
if not stripped.endswith("\\"):
|
||||
collecting = False
|
||||
|
||||
for entry in raw_entries:
|
||||
for token in entry.split():
|
||||
token = token.strip('"')
|
||||
if not token:
|
||||
continue
|
||||
|
||||
token = token.replace("\\", "/")
|
||||
|
||||
if token.endswith(".obj"):
|
||||
token = re.sub(r"\.obj$", ".c", token)
|
||||
elif token.endswith(".o"):
|
||||
token = re.sub(r"\.o$", ".c", token)
|
||||
|
||||
if token.endswith(".c"):
|
||||
abs_path = os.path.normpath(os.path.join(project_root, token))
|
||||
if os.path.isfile(abs_path):
|
||||
if not any(x in abs_path for x in ["DebugTools", "v120", "v100"]):
|
||||
c_files.append(abs_path)
|
||||
include_dirs.add(os.path.dirname(abs_path))
|
||||
|
||||
if not c_files:
|
||||
makefile_dir = os.path.dirname(os.path.abspath(makefile_path))
|
||||
objects_list_path = os.path.join(makefile_dir, "objects.list")
|
||||
c_from_objects, inc_from_objects = parse_objects_list(objects_list_path, project_root)
|
||||
c_files.extend(c_from_objects)
|
||||
include_dirs.update(inc_from_objects)
|
||||
|
||||
for line in lines:
|
||||
if "-I" in line or "C_INCLUDES" in line:
|
||||
matches = re.findall(r"-I\s*([^\s\\]+)", line)
|
||||
for match in matches:
|
||||
match = match.strip('"').replace("\\", "/")
|
||||
abs_include = os.path.normpath(os.path.join(project_root, match))
|
||||
if os.path.isdir(abs_include):
|
||||
include_dirs.add(abs_include)
|
||||
|
||||
# Добавляем пути с заменой 'Src' на 'Inc', если путь заканчивается на 'Src'
|
||||
additional_includes = set()
|
||||
for inc in include_dirs:
|
||||
if inc.endswith(os.sep + "Src") or inc.endswith("/Src"):
|
||||
inc_inc = inc[:-3] + "Inc" # заменяем 'Src' на 'Inc'
|
||||
if os.path.isdir(inc_inc):
|
||||
additional_includes.add(inc_inc)
|
||||
|
||||
include_dirs.update(additional_includes)
|
||||
|
||||
h_files = find_all_includes_recursive(c_files, include_dirs)
|
||||
|
||||
return sorted(c_files), sorted(h_files), sorted(include_dirs), sorted(defines)
|
||||
|
||||
|
||||
def parse_project(project_file_path, project_root=None):
|
||||
"""
|
||||
Выбирает парсер в зависимости от расширения project_file_path:
|
||||
- для *.uvprojx и *.uvproj вызывается парсер Keil
|
||||
- для остальных - parse_makefile
|
||||
|
||||
project_root нужен для parse_makefile, если не передан - берется из project_file_path
|
||||
"""
|
||||
ext = os.path.splitext(project_file_path)[1].lower()
|
||||
|
||||
if ext in ['.uvprojx', '.uvproj']:
|
||||
# Парсим Keil проект
|
||||
return parse_uvprojx(project_file_path)
|
||||
else:
|
||||
# Парсим makefile
|
||||
if project_root is None:
|
||||
project_root = os.path.dirname(os.path.abspath(project_file_path))
|
||||
return parse_makefile(project_file_path, project_root)
|
||||
36
Src/myXML.py
36
Src/myXML.py
@@ -1,5 +1,5 @@
|
||||
import os
|
||||
import xml.etree.ElementTree as ET
|
||||
from lxml import etree
|
||||
|
||||
def make_absolute_path(path, base_path):
|
||||
if not os.path.isabs(path) and os.path.isdir(base_path):
|
||||
@@ -33,44 +33,38 @@ def make_relative_path(abs_path, base_path):
|
||||
return abs_path.replace("\\", "/")
|
||||
|
||||
def indent_xml(elem, level=0):
|
||||
indent = " "
|
||||
i = "\n" + level * indent
|
||||
# Убираем strip() — они медленные, и текст мы всё равно перезаписываем
|
||||
i = "\n" + level * " "
|
||||
if len(elem):
|
||||
if not elem.text or not elem.text.strip():
|
||||
elem.text = i + indent
|
||||
elem.text = elem.text or i + " "
|
||||
for child in elem:
|
||||
indent_xml(child, level + 1)
|
||||
if not elem.tail or not elem.tail.strip():
|
||||
elem.tail = i
|
||||
elem[-1].tail = elem[-1].tail or i
|
||||
else:
|
||||
if level and (not elem.tail or not elem.tail.strip()):
|
||||
elem.tail = i
|
||||
elem.tail = elem.tail or i
|
||||
|
||||
|
||||
def fwrite(root, xml_full_path):
|
||||
indent_xml(root)
|
||||
ET.ElementTree(root).write(xml_full_path, encoding="utf-8", xml_declaration=True)
|
||||
#indent_xml(root)
|
||||
#ET.ElementTree(root).write(xml_full_path, encoding="utf-8", xml_declaration=True)
|
||||
rough_string = etree.tostring(root, encoding="utf-8")
|
||||
parsed = etree.fromstring(rough_string)
|
||||
with open(xml_full_path, "wb") as f:
|
||||
f.write(etree.tostring(parsed, pretty_print=True, encoding="utf-8", xml_declaration=True))
|
||||
|
||||
|
||||
def safe_parse_xml(xml_path):
|
||||
"""
|
||||
Безопасно парсит XML-файл.
|
||||
|
||||
Возвращает кортеж (root, tree) или (None, None) при ошибках.
|
||||
"""
|
||||
def safe_parse_xml(xml_path):
|
||||
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)
|
||||
tree = etree.parse(xml_path)
|
||||
root = tree.getroot()
|
||||
return root, tree
|
||||
|
||||
except ET.ParseError as e:
|
||||
except etree .XMLSyntaxError as e:
|
||||
print(f"Ошибка парсинга XML файла '{xml_path}': {e}")
|
||||
return None, None
|
||||
except Exception as e:
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
def strip_single_line_comments(code):
|
||||
# Удалим // ... до конца строки
|
||||
return re.sub(r'//.*?$', '', code, flags=re.MULTILINE)
|
||||
|
||||
def read_file_try_encodings(filepath):
|
||||
if not os.path.isfile(filepath):
|
||||
# Файл не существует — просто вернуть пустую строку или None
|
||||
return "", None
|
||||
for enc in ['utf-8', 'cp1251']:
|
||||
try:
|
||||
with open(filepath, 'r', encoding=enc) as f:
|
||||
content = f.read()
|
||||
content = strip_single_line_comments(content)
|
||||
return content, enc
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
raise UnicodeDecodeError(f"Не удалось прочитать файл {filepath} с кодировками utf-8 и cp1251")
|
||||
|
||||
def find_all_includes_recursive(c_files, include_dirs, processed_files=None):
|
||||
"""
|
||||
Рекурсивно ищет все include-файлы начиная с заданных c_files.
|
||||
Возвращает множество ПОЛНЫХ ПУТЕЙ к найденным include-файлам.
|
||||
|
||||
include_dirs — список директорий, в которых ищем include-файлы.
|
||||
processed_files — множество уже обработанных файлов (для избежания циклов).
|
||||
"""
|
||||
if processed_files is None:
|
||||
processed_files = set()
|
||||
|
||||
include_files = set()
|
||||
include_pattern = re.compile(r'#include\s+"([^"]+)"')
|
||||
|
||||
for cfile in c_files:
|
||||
norm_path = os.path.normpath(cfile)
|
||||
if norm_path in processed_files:
|
||||
continue
|
||||
processed_files.add(norm_path)
|
||||
|
||||
content, _ = read_file_try_encodings(cfile)
|
||||
if content is None:
|
||||
continue
|
||||
includes = include_pattern.findall(content)
|
||||
for inc in includes:
|
||||
# Ищем полный путь к include-файлу в include_dirs
|
||||
inc_full_path = None
|
||||
for dir_ in include_dirs:
|
||||
candidate = os.path.normpath(os.path.join(dir_, inc))
|
||||
if os.path.isfile(candidate):
|
||||
inc_full_path = os.path.abspath(candidate)
|
||||
break
|
||||
|
||||
if inc_full_path:
|
||||
include_files.add(inc_full_path)
|
||||
|
||||
# Рекурсивный обход вложенных includes
|
||||
if inc_full_path not in processed_files:
|
||||
nested_includes = find_all_includes_recursive(
|
||||
[inc_full_path], include_dirs, processed_files
|
||||
)
|
||||
include_files.update(nested_includes)
|
||||
|
||||
return include_files
|
||||
|
||||
|
||||
def parse_makefile(makefile_path, proj_path):
|
||||
makefile_dir = os.path.dirname(makefile_path)
|
||||
project_root = proj_path
|
||||
|
||||
with open(makefile_path, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
objs_lines = []
|
||||
collecting = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
if stripped.startswith("ORDERED_OBJS") and "+=" in stripped:
|
||||
parts = stripped.split("\\")
|
||||
first_part = parts[0]
|
||||
idx = first_part.find("+=")
|
||||
tail = first_part[idx+2:].strip()
|
||||
if tail:
|
||||
objs_lines.append(tail)
|
||||
collecting = True
|
||||
if len(parts) > 1:
|
||||
for p in parts[1:]:
|
||||
p = p.strip()
|
||||
if p:
|
||||
objs_lines.append(p)
|
||||
continue
|
||||
|
||||
if collecting:
|
||||
if stripped.endswith("\\"):
|
||||
objs_lines.append(stripped[:-1].strip())
|
||||
else:
|
||||
objs_lines.append(stripped)
|
||||
collecting = False
|
||||
|
||||
objs_str = ' '.join(objs_lines)
|
||||
|
||||
objs_str = re.sub(r"\$\([^)]+\)", "", objs_str)
|
||||
|
||||
objs = []
|
||||
for part in objs_str.split():
|
||||
part = part.strip()
|
||||
if part.startswith('"') and part.endswith('"'):
|
||||
part = part[1:-1]
|
||||
if part:
|
||||
objs.append(part)
|
||||
|
||||
c_files = []
|
||||
include_dirs = set()
|
||||
|
||||
for obj_path in objs:
|
||||
if "DebugTools" in obj_path:
|
||||
continue
|
||||
if "v120" in obj_path:
|
||||
continue
|
||||
if "v100" in obj_path:
|
||||
continue
|
||||
|
||||
if obj_path.startswith("Debug\\") or obj_path.startswith("Debug/"):
|
||||
rel_path = obj_path.replace("Debug\\", "Src\\").replace("Debug/", "Src/")
|
||||
else:
|
||||
rel_path = obj_path
|
||||
|
||||
abs_path = os.path.normpath(os.path.join(project_root, rel_path))
|
||||
|
||||
root, ext = os.path.splitext(abs_path)
|
||||
if ext.lower() == ".obj":
|
||||
c_path = root + ".c"
|
||||
else:
|
||||
c_path = abs_path
|
||||
|
||||
# Проверяем существование файла, если нет — пропускаем
|
||||
if not os.path.isfile(c_path):
|
||||
continue
|
||||
|
||||
# Сохраняем только .c файлы
|
||||
if c_path.lower().endswith(".c"):
|
||||
c_files.append(c_path)
|
||||
dir_path = os.path.dirname(c_path)
|
||||
if dir_path and "DebugTools" not in dir_path:
|
||||
include_dirs.add(dir_path)
|
||||
|
||||
|
||||
h_files = find_all_includes_recursive(c_files, include_dirs)
|
||||
|
||||
|
||||
return sorted(c_files), sorted(h_files), sorted(include_dirs)
|
||||
Binary file not shown.
@@ -6,8 +6,8 @@ import sys
|
||||
import contextlib
|
||||
import io
|
||||
import json
|
||||
from scanVars import run_scan
|
||||
from VariableTable import VariableTableWidget, rows
|
||||
from scan_vars import run_scan
|
||||
from var_table import VariableTableWidget, rows
|
||||
|
||||
from PySide2.QtWidgets import (
|
||||
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||||
@@ -73,13 +73,13 @@ class EmittingStream(QObject):
|
||||
self._buffer = ""
|
||||
|
||||
|
||||
|
||||
class ProcessOutputWindow(QDialog):
|
||||
def __init__(self, proj_path, makefile_path, xml_path, on_done_callback=None):
|
||||
super().__init__()
|
||||
def __init__(self, proj_path, makefile_path, xml_path, on_done_callback=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Поиск переменных...")
|
||||
self.resize(600, 480)
|
||||
self.setModal(True)
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
|
||||
self.proj_path = proj_path
|
||||
self.makefile_path = makefile_path
|
||||
@@ -1,7 +1,7 @@
|
||||
# build command
|
||||
# pyinstaller --onefile scanVars.py --add-binary "F:\Work\Projects\TMS\TMS_new_bus\Src\DebugTools/build/libclang.dll;." --distpath . --workpath ./build --specpath ./build
|
||||
# pyinstaller --onefile scan_vars.py --add-binary "F:\Work\Projects\TMS\TMS_new_bus\Src\DebugTools/build/libclang.dll;." --distpath . --workpath ./build --specpath ./build
|
||||
# start script
|
||||
# scanVars.exe F:\Work\Projects\TMS\TMS_new_bus\ F:\Work\Projects\TMS\TMS_new_bus\Debug\makefile
|
||||
# scan_vars.exe F:\Work\Projects\TMS\TMS_new_bus\ F:\Work\Projects\TMS\TMS_new_bus\Debug\makefile
|
||||
|
||||
import os
|
||||
import sys
|
||||
@@ -9,9 +9,9 @@ import re
|
||||
import clang.cindex
|
||||
from clang import cindex
|
||||
from clang.cindex import Config
|
||||
import xml.etree.ElementTree as ET
|
||||
import lxml.etree as ET
|
||||
from xml.dom import minidom
|
||||
from parseMakefile import parse_makefile
|
||||
from makefile_parser import parse_project
|
||||
from collections import deque
|
||||
import argparse
|
||||
import myXML
|
||||
@@ -118,11 +118,11 @@ def get_canonical_typedef_file(var_type, include_dirs):
|
||||
break
|
||||
return None
|
||||
|
||||
def analyze_variables_across_files(c_files, h_files, include_dirs):
|
||||
def analyze_variables_across_files(c_files, h_files, include_dirs, global_defs):
|
||||
optional_printf(PRINT_STATUS, "Starting analysis of variables across files...")
|
||||
index = clang.cindex.Index.create()
|
||||
args = [f"-I{inc}" for inc in include_dirs]
|
||||
|
||||
define_args = [f"-D{d}" for d in global_defs]
|
||||
args = [f"-I{inc}" for inc in include_dirs] + define_args
|
||||
unique_vars = {} # имя переменной → словарь с инфой
|
||||
h_files_needed = set()
|
||||
vars_need_extern = {} # имя переменной → словарь без поля 'extern'
|
||||
@@ -154,6 +154,7 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
|
||||
# Проверяем, начинается ли имя с "_" и содержит заглавные буквы или служебные символы
|
||||
return bool(re.match(r"^_[_A-Z]", var_name))
|
||||
|
||||
|
||||
if node.kind == clang.cindex.CursorKind.VAR_DECL:
|
||||
if node.semantic_parent.kind == clang.cindex.CursorKind.TRANSLATION_UNIT:
|
||||
is_extern = (node.storage_class == clang.cindex.StorageClass.EXTERN)
|
||||
@@ -170,7 +171,12 @@ def analyze_variables_across_files(c_files, h_files, include_dirs):
|
||||
return # игнорируем только явно известные служебные переменные
|
||||
if node.spelling == 'HUGE': # еще одна служеюная, которую хз как выделять
|
||||
return
|
||||
|
||||
if 'Drivers' in node.location.file.name:
|
||||
return
|
||||
|
||||
if 'uint' in node.spelling:
|
||||
a = 1
|
||||
# Проверяем, является ли тип указателем на функцию
|
||||
# Признак: в типе есть '(' и ')' и '*', например: "void (*)(int)"
|
||||
if "(" in var_type and "*" in var_type and ")" in var_type:
|
||||
@@ -295,6 +301,8 @@ def strip_ptr_and_array(typename):
|
||||
|
||||
return typename
|
||||
|
||||
|
||||
|
||||
def analyze_typedefs_and_struct(typedefs, structs):
|
||||
optional_printf(PRINT_STATUS, "Resolving typedefs and expanding struct field types...")
|
||||
|
||||
@@ -422,10 +430,28 @@ def contains_anywhere_in_node(node, target: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
|
||||
|
||||
def try_guess_std_include():
|
||||
# Популярные места, где может лежать stdint.h
|
||||
guesses = [
|
||||
r"C:\Keil_v5\ARM\ARMCLANG\include",
|
||||
r"C:\Program Files (x86)\GNU Arm Embedded Toolchain",
|
||||
r"C:\Program Files (x86)\Arm GNU Toolchain"
|
||||
]
|
||||
|
||||
found = []
|
||||
for base in guesses:
|
||||
for root, dirs, files in os.walk(base):
|
||||
if "stdint.h" in files:
|
||||
found.append(root)
|
||||
return found
|
||||
|
||||
def analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs):
|
||||
optional_printf(PRINT_STATUS, "Starting analysis of typedefs and structs across files...")
|
||||
index = clang.cindex.Index.create()
|
||||
args = [f"-I{inc}" for inc in include_dirs]
|
||||
define_args = [f"-D{d}" for d in global_defs]
|
||||
extra_std_include_dirs = try_guess_std_include()
|
||||
args = [f"-I{inc}" for inc in include_dirs] + extra_std_include_dirs + define_args
|
||||
|
||||
unique_typedefs_raw = {}
|
||||
unique_structs_raw = {}
|
||||
@@ -452,7 +478,6 @@ def analyze_typedefs_and_structs_across_files(c_files, include_dirs):
|
||||
|
||||
raw_name = node.spelling
|
||||
normalized_name = normalize_type_name(raw_name)
|
||||
|
||||
# struct_name всегда с префиксом
|
||||
if node.spelling and "unnamed" not in normalized_name:
|
||||
struct_name = f"{prefix}{normalized_name}"
|
||||
@@ -545,7 +570,7 @@ def read_vars_from_xml(xml_path):
|
||||
'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'),
|
||||
'return_type': var_elem.findtext('return_type', 't_iq_none'),
|
||||
'type': var_elem.findtext('type', 'unknown'),
|
||||
'file': var_elem.findtext('file', ''),
|
||||
'extern': get_bool('extern'),
|
||||
@@ -608,7 +633,7 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
|
||||
'shortname': info.get('shortname', name),
|
||||
'pt_type': info.get('pt_type', ''),
|
||||
'iq_type': info.get('iq_type', ''),
|
||||
'return_type': info.get('return_type', 'int'),
|
||||
'return_type': info.get('return_type', 't_iq_none'),
|
||||
'type': info.get('type', 'unknown'),
|
||||
'file': info.get('file', ''),
|
||||
'extern': info.get('extern', False),
|
||||
@@ -627,7 +652,7 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
|
||||
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, "iq_type").text = info.get('iq_type', '')
|
||||
ET.SubElement(var_elem, "return_type").text = info.get('return_type', 'int')
|
||||
ET.SubElement(var_elem, "return_type").text = info.get('return_type', 't_iq_none')
|
||||
|
||||
ET.SubElement(var_elem, "type").text = info.get('type', 'unknown')
|
||||
rel_file = make_relative_if_possible(info.get('file', ''), proj_path).replace("\\", "/")
|
||||
@@ -855,10 +880,10 @@ Usage example:
|
||||
print(f"Error: Makefile path '{makefile_path}' does not exist.")
|
||||
sys.exit(1)
|
||||
|
||||
c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path)
|
||||
c_files, h_files, include_dirs, global_defs = parse_project(makefile_path, proj_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, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs, global_defs)
|
||||
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs)
|
||||
|
||||
vars = dict(sorted(vars.items()))
|
||||
includes = get_sorted_headers(c_files, includes, include_dirs)
|
||||
@@ -898,10 +923,10 @@ def run_scan(proj_path, makefile_path, output_xml, verbose=2):
|
||||
if not os.path.isfile(makefile_path):
|
||||
raise FileNotFoundError(f"Makefile path '{makefile_path}' does not exist.")
|
||||
|
||||
c_files, h_files, include_dirs = parse_makefile(makefile_path, proj_path)
|
||||
c_files, h_files, include_dirs, global_defs = parse_project(makefile_path, proj_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, includes, externs = analyze_variables_across_files(c_files, h_files, include_dirs, global_defs)
|
||||
typedefs, structs = analyze_typedefs_and_structs_across_files(c_files, include_dirs, global_defs)
|
||||
|
||||
vars = dict(sorted(vars.items()))
|
||||
includes = get_sorted_headers(c_files, includes, include_dirs)
|
||||
606
Src/var_selector_table.py
Normal file
606
Src/var_selector_table.py
Normal file
@@ -0,0 +1,606 @@
|
||||
import re
|
||||
from PySide2.QtWidgets import (
|
||||
QWidget, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QLineEdit,
|
||||
QHeaderView, QCompleter
|
||||
)
|
||||
from PySide2.QtGui import QKeyEvent
|
||||
from PySide2.QtCore import Qt, QStringListModel
|
||||
import pickle
|
||||
import time
|
||||
import hashlib
|
||||
|
||||
def compute_vars_hash(vars_list):
|
||||
return hashlib.sha1(pickle.dumps(vars_list)).hexdigest()
|
||||
|
||||
# Вспомогательные функции, которые теперь будут использоваться виджетом
|
||||
def split_path(path):
|
||||
"""
|
||||
Разбивает путь на компоненты:
|
||||
- 'foo[2].bar[1]->baz' → ['foo', '[2]', 'bar', '[1]', 'baz']
|
||||
Если видит '-' в конце строки (без '>' после) — обрезает этот '-'
|
||||
"""
|
||||
tokens = []
|
||||
token = ''
|
||||
i = 0
|
||||
length = len(path)
|
||||
while i < length:
|
||||
c = path[i]
|
||||
# Разделители: '->' и '.'
|
||||
if c == '-' and i + 1 < length and path[i:i+2] == '->':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
i += 2
|
||||
continue
|
||||
elif c == '-' and i == length - 1:
|
||||
# '-' на конце строки без '>' после — просто пропускаем его
|
||||
i += 1
|
||||
continue
|
||||
elif c == '.':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
i += 1
|
||||
continue
|
||||
elif c == '[':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
idx = ''
|
||||
while i < length and path[i] != ']':
|
||||
idx += path[i]
|
||||
i += 1
|
||||
if i < length and path[i] == ']':
|
||||
idx += ']'
|
||||
i += 1
|
||||
tokens.append(idx)
|
||||
continue
|
||||
else:
|
||||
token += c
|
||||
i += 1
|
||||
if token:
|
||||
tokens.append(token)
|
||||
return tokens
|
||||
|
||||
|
||||
def is_lazy_item(item):
|
||||
return item.childCount() == 1 and item.child(0).text(0) == 'lazy_marker'
|
||||
|
||||
|
||||
class VariableSelectWidget(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.expanded_vars = []
|
||||
self.node_index = {}
|
||||
self.is_autocomplete_on = True # <--- ДОБАВИТЬ ЭТУ СТРОКУ
|
||||
self._bckspc_pressed = False
|
||||
self.manual_completion_active = False
|
||||
self._vars_hash = None
|
||||
|
||||
# --- UI Элементы ---
|
||||
self.search_input = QLineEdit(self)
|
||||
self.search_input.setPlaceholderText("Поиск...")
|
||||
|
||||
self.tree = QTreeWidget(self)
|
||||
self.tree.setHeaderLabels(["Имя переменной", "Тип"])
|
||||
self.tree.setSelectionMode(QTreeWidget.ExtendedSelection)
|
||||
self.tree.setRootIsDecorated(True)
|
||||
self.tree.setUniformRowHeights(True)
|
||||
self.tree.setStyleSheet("""
|
||||
QTreeWidget::item:selected { background-color: #87CEFA; color: black; }
|
||||
QTreeWidget::item:hover { background-color: #D3D3D3; }
|
||||
""")
|
||||
self.tree.itemExpanded.connect(self.on_item_expanded)
|
||||
|
||||
self.completer = QCompleter(self)
|
||||
self.completer.setCompletionMode(QCompleter.PopupCompletion)
|
||||
self.completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
self.completer.setFilterMode(Qt.MatchContains)
|
||||
self.completer.setWidget(self.search_input)
|
||||
|
||||
# --- Layout ---
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.addWidget(self.search_input)
|
||||
layout.addWidget(self.tree)
|
||||
|
||||
# --- Соединения ---
|
||||
#self.search_input.textChanged.connect(self.on_search_text_changed)
|
||||
self.search_input.textChanged.connect(lambda text: self.on_search_text_changed(text))
|
||||
self.search_input.installEventFilter(self)
|
||||
self.completer.activated[str].connect(lambda text: self.insert_completion(text))
|
||||
|
||||
# --- Публичные методы для управления виджетом снаружи ---
|
||||
|
||||
def set_autocomplete(self, enabled: bool):
|
||||
"""Включает или выключает режим автодополнения."""
|
||||
self.is_autocomplete_on = enabled
|
||||
|
||||
def set_data(self, vars_list):
|
||||
"""Основной метод для загрузки данных в виджет."""
|
||||
self.expanded_vars = pickle.loads(pickle.dumps(vars_list, protocol=pickle.HIGHEST_PROTOCOL))
|
||||
# self.build_completion_list() # Если нужна полная перестройка списка
|
||||
self.populate_tree()
|
||||
|
||||
|
||||
def populate_tree(self, vars_list=None):
|
||||
if vars_list is None:
|
||||
vars_list = self.expanded_vars
|
||||
|
||||
new_hash = compute_vars_hash(vars_list)
|
||||
if self._vars_hash == new_hash:
|
||||
return
|
||||
|
||||
self._vars_hash = new_hash
|
||||
self.tree.setUpdatesEnabled(False)
|
||||
self.tree.blockSignals(True)
|
||||
self.tree.clear()
|
||||
self.node_index.clear()
|
||||
|
||||
for var in vars_list:
|
||||
self.add_tree_item_lazy(None, var)
|
||||
|
||||
self.tree.setUpdatesEnabled(True)
|
||||
self.tree.blockSignals(False)
|
||||
header = self.tree.header()
|
||||
header.setSectionResizeMode(QHeaderView.Interactive)
|
||||
header.setSectionResizeMode(1, QHeaderView.Stretch)
|
||||
self.tree.setColumnWidth(0, 400)
|
||||
|
||||
def on_item_expanded(self, item):
|
||||
if is_lazy_item(item):
|
||||
item.removeChild(item.child(0))
|
||||
var = item.data(0, Qt.UserRole + 100)
|
||||
if var:
|
||||
for child_var in var.get('children', []):
|
||||
self.add_tree_item_lazy(item, child_var)
|
||||
|
||||
|
||||
def get_full_item_name(self, item):
|
||||
fullname = item.text(0)
|
||||
# Заменяем '->' на '.'
|
||||
fullname = fullname.replace('->', '.')
|
||||
fullname = fullname.replace('[', '.[')
|
||||
return fullname
|
||||
|
||||
def add_tree_item_lazy(self, parent, var):
|
||||
name = var['name']
|
||||
type_str = var.get('type', '')
|
||||
item = QTreeWidgetItem([name, type_str])
|
||||
item.setData(0, Qt.UserRole, name)
|
||||
full_name = self.get_full_item_name(item)
|
||||
self.node_index[full_name.lower()] = item
|
||||
|
||||
if "(bitfield:" in type_str:
|
||||
item.setDisabled(True)
|
||||
self.set_tool(item, "Битовые поля недоступны для выбора")
|
||||
|
||||
for i, attr in enumerate(['file', 'extern', 'static']):
|
||||
item.setData(0, Qt.UserRole + 1 + i, var.get(attr))
|
||||
|
||||
if parent is None:
|
||||
self.tree.addTopLevelItem(item)
|
||||
else:
|
||||
parent.addChild(item)
|
||||
|
||||
# Если есть дети — добавляем заглушку (чтобы можно было раскрыть)
|
||||
if var.get('children'):
|
||||
dummy = QTreeWidgetItem(["lazy_marker"])
|
||||
item.addChild(dummy)
|
||||
|
||||
# Кэшируем детей для подгрузки по событию
|
||||
item.setData(0, Qt.UserRole + 100, var) # Сохраняем var целиком
|
||||
|
||||
|
||||
def show_matching_path(self, item, path_parts, level=0):
|
||||
node_name = item.text(0).lower()
|
||||
node_parts = split_path(node_name)
|
||||
|
||||
if 'project' in node_name:
|
||||
a = 1
|
||||
|
||||
if level >= len(path_parts):
|
||||
# Путь полностью пройден — показываем только этот узел (без раскрытия всех детей)
|
||||
item.setHidden(False)
|
||||
item.setExpanded(False)
|
||||
return True
|
||||
|
||||
if level >= len(node_parts):
|
||||
# Уровень поиска больше длины пути узла — скрываем
|
||||
item.setHidden(False)
|
||||
|
||||
search_part = path_parts[level]
|
||||
node_part = node_parts[level]
|
||||
|
||||
if search_part == node_part:
|
||||
# Точное совпадение — показываем узел, идём вглубь только по совпадениям
|
||||
item.setHidden(False)
|
||||
matched_any = False
|
||||
self.on_item_expanded(item)
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
if self.show_matching_path(child, path_parts, level + 1):
|
||||
matched_any = True
|
||||
item.setExpanded(matched_any)
|
||||
return matched_any or item.childCount() == 0
|
||||
|
||||
elif node_part.startswith(search_part):
|
||||
# Неполное совпадение — показываем только этот узел, детей скрываем, не раскрываем
|
||||
item.setHidden(False)
|
||||
item.setExpanded(False)
|
||||
return True
|
||||
|
||||
elif search_part in node_part and (level == len(path_parts)-1):
|
||||
# Неполное совпадение — показываем только этот узел, детей скрываем, не раскрываем
|
||||
item.setHidden(False)
|
||||
item.setExpanded(False)
|
||||
return True
|
||||
|
||||
else:
|
||||
# Несовпадение — скрываем
|
||||
item.setHidden(True)
|
||||
return False
|
||||
|
||||
|
||||
def filter_tree(self):
|
||||
text = self.search_input.text().strip().lower()
|
||||
path_parts = split_path(text) if text else []
|
||||
|
||||
if '.' not in text and '->' not in text and '[' not in text and text != '':
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
name = item.text(0).lower()
|
||||
if text in name:
|
||||
item.setHidden(False)
|
||||
# Не сбрасываем expanded, чтобы можно было раскрывать вручную
|
||||
else:
|
||||
item.setHidden(True)
|
||||
else:
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
self.show_matching_path(item, path_parts, 0)
|
||||
|
||||
|
||||
|
||||
def find_node_by_path(self, root_vars, path_list):
|
||||
current_level = root_vars
|
||||
node = None
|
||||
for part in path_list:
|
||||
node = None
|
||||
for var in current_level:
|
||||
if var['name'] == part:
|
||||
node = var
|
||||
break
|
||||
if node is None:
|
||||
return None
|
||||
current_level = node.get('children', [])
|
||||
return node
|
||||
|
||||
|
||||
def update_completions(self, text=None):
|
||||
if text is None:
|
||||
text = self.search_input.text().strip()
|
||||
else:
|
||||
text = text.strip()
|
||||
|
||||
normalized_text = text.replace('->', '.')
|
||||
parts = split_path(text)
|
||||
path_parts = parts[:-1] if parts else []
|
||||
prefix = parts[-1].lower() if parts else ''
|
||||
ends_with_sep = text.endswith('.') or text.endswith('->') or text.endswith('[')
|
||||
is_index_suggestion = text.endswith('[')
|
||||
|
||||
completions = []
|
||||
|
||||
def find_exact_node(parts):
|
||||
if not parts:
|
||||
return None
|
||||
fullname = parts[0]
|
||||
for p in parts[1:]:
|
||||
fullname += '.' + p
|
||||
return self.node_index.get(fullname.lower())
|
||||
|
||||
if is_index_suggestion:
|
||||
base_text = text[:-1] # убираем '['
|
||||
parent_node = self.find_node_by_fullname(base_text)
|
||||
if not parent_node:
|
||||
base_text_clean = re.sub(r'\[\d+\]$', '', base_text)
|
||||
parent_node = self.find_node_by_fullname(base_text_clean)
|
||||
if parent_node:
|
||||
seen = set()
|
||||
for i in range(parent_node.childCount()):
|
||||
child = parent_node.child(i)
|
||||
if child.isHidden():
|
||||
continue
|
||||
cname = child.text(0)
|
||||
m = re.match(rf'^{re.escape(base_text)}\[(\d+)\]$', cname)
|
||||
if m and cname not in seen:
|
||||
completions.append(cname)
|
||||
seen.add(cname)
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
return completions
|
||||
|
||||
if ends_with_sep:
|
||||
node = self.find_node_by_fullname(text[:-1])
|
||||
if node:
|
||||
for i in range(node.childCount()):
|
||||
child = node.child(i)
|
||||
if child.isHidden():
|
||||
continue
|
||||
completions.append(child.text(0))
|
||||
elif not path_parts:
|
||||
# Первый уровень — только если имя начинается с prefix
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
item = self.tree.topLevelItem(i)
|
||||
if item.isHidden():
|
||||
continue
|
||||
name = item.text(0)
|
||||
if name.lower().startswith(prefix):
|
||||
completions.append(name)
|
||||
else:
|
||||
node = find_exact_node(path_parts)
|
||||
if node:
|
||||
for i in range(node.childCount()):
|
||||
child = node.child(i)
|
||||
if child.isHidden():
|
||||
continue
|
||||
name = child.text(0)
|
||||
name_parts = child.data(0, Qt.UserRole + 10)
|
||||
if name_parts is None:
|
||||
name_parts = split_path(name)
|
||||
child.setData(0, Qt.UserRole + 10, name_parts)
|
||||
if not name_parts:
|
||||
continue
|
||||
last_part = name_parts[-1].lower()
|
||||
if prefix == '' or prefix in last_part: # ← строго startswith
|
||||
completions.append(name)
|
||||
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
return completions
|
||||
|
||||
|
||||
# Функция для поиска узла с полным именем
|
||||
def find_node_by_fullname(self, name):
|
||||
if name is None:
|
||||
return None
|
||||
normalized_name = name.replace('->', '.').lower()
|
||||
normalized_name = normalized_name.replace('[', '.[').lower()
|
||||
return self.node_index.get(normalized_name)
|
||||
|
||||
def insert_completion(self, text):
|
||||
node = self.find_node_by_fullname(text)
|
||||
if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->') or text.endswith('[')):
|
||||
# Определяем разделитель по имени первого ребёнка
|
||||
child_name = node.child(0).text(0)
|
||||
if child_name.startswith(text + '->'):
|
||||
text += '->'
|
||||
elif child_name.startswith(text + '.'):
|
||||
text += '.'
|
||||
elif '[' in child_name:
|
||||
text += '[' # для массивов
|
||||
else:
|
||||
text += '.' # fallback
|
||||
|
||||
if not self._bckspc_pressed:
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
|
||||
self.run_completions(text)
|
||||
else:
|
||||
self.search_input.setText(text)
|
||||
self.search_input.setCursorPosition(len(text))
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
if obj == self.search_input and isinstance(event, QKeyEvent):
|
||||
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
|
||||
self.manual_completion_active = True
|
||||
text = self.search_input.text().strip()
|
||||
self.run_completions(text)
|
||||
elif event.key() == Qt.Key_Escape:
|
||||
# Esc — выключаем ручной режим и скрываем подсказки, если autocomplete выключен
|
||||
if not self.is_autocomplete_on:
|
||||
self.manual_completion_active = False
|
||||
self.completer.popup().hide()
|
||||
return True
|
||||
|
||||
if event.key() == Qt.Key_Backspace:
|
||||
self._bckspc_pressed = True
|
||||
else:
|
||||
self._bckspc_pressed = False
|
||||
|
||||
return super().eventFilter(obj, event)
|
||||
|
||||
def run_completions(self, text):
|
||||
completions = self.update_completions(text)
|
||||
|
||||
if not self.is_autocomplete_on and self._bckspc_pressed:
|
||||
text = text[:-1]
|
||||
|
||||
if len(completions) == 1 and completions[0].lower() == text.lower():
|
||||
# Найдем узел с таким именем
|
||||
def find_exact_item(name):
|
||||
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
|
||||
while stack:
|
||||
node = stack.pop()
|
||||
if node.text(0).lower() == name.lower():
|
||||
return node
|
||||
for i in range(node.childCount()):
|
||||
stack.append(node.child(i))
|
||||
return None
|
||||
|
||||
node = find_exact_item(completions[0])
|
||||
if node and node.childCount() > 0:
|
||||
# Используем первую подсказку, чтобы определить нужный разделитель
|
||||
completions = self.update_completions(text + '.')
|
||||
if not completions:
|
||||
return
|
||||
suggestion = completions[0]
|
||||
|
||||
# Ищем, какой символ идёт после текущего текста
|
||||
separator = '.'
|
||||
if suggestion.startswith(text):
|
||||
rest = suggestion[len(text):]
|
||||
if rest.startswith(text + '->'):
|
||||
separator += '->'
|
||||
elif rest.startswith(text + '.'):
|
||||
separator += '.'
|
||||
elif '[' in rest:
|
||||
separator += '[' # для массивов
|
||||
else:
|
||||
separator += '.' # fallback
|
||||
|
||||
if not self._bckspc_pressed:
|
||||
self.search_input.setText(text + separator)
|
||||
completions = self.update_completions(text)
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
# Иначе просто показываем подсказки
|
||||
self.completer.setModel(QStringListModel(completions))
|
||||
if completions:
|
||||
self.completer.complete()
|
||||
return True
|
||||
|
||||
def on_search_text_changed(self, text):
|
||||
sender_widget = self.sender()
|
||||
sender_name = sender_widget.objectName() if sender_widget else "Unknown Sender"
|
||||
|
||||
self.completer.setWidget(self.search_input)
|
||||
self.filter_tree()
|
||||
if text == None:
|
||||
text = self.search_input.text().strip()
|
||||
if self.is_autocomplete_on:
|
||||
self.run_completions(text)
|
||||
else:
|
||||
# Если выключено, показываем подсказки только если флаг ручного вызова True
|
||||
if self.manual_completion_active:
|
||||
self.run_completions(text)
|
||||
else:
|
||||
self.completer.popup().hide()
|
||||
|
||||
def focusInEvent(self, event):
|
||||
if self.completer.widget() != self.search_input:
|
||||
self.completer.setWidget(self.search_input)
|
||||
super().focusInEvent(event)
|
||||
|
||||
def _custom_focus_in_event(self, event):
|
||||
# Принудительно установить виджет для completer при получении фокуса
|
||||
if self.completer.widget() != self.search_input:
|
||||
self.completer.setWidget(self.search_input)
|
||||
super(QLineEdit, self.search_input).focusInEvent(event) # Вызвать оригинальный обработчик
|
||||
|
||||
|
||||
def build_completion_list(self):
|
||||
completions = []
|
||||
|
||||
def recurse(var, prefix=''):
|
||||
fullname = f"{prefix}.{var['name']}" if prefix else var['name']
|
||||
completions.append(fullname)
|
||||
for child in var.get('children', []):
|
||||
recurse(child, fullname)
|
||||
|
||||
for v in self.expanded_vars:
|
||||
recurse(v)
|
||||
self.all_completions = completions
|
||||
|
||||
def set_tool(self, item, text):
|
||||
item.setToolTip(0, text)
|
||||
item.setToolTip(1, text)
|
||||
|
||||
def get_all_items(self):
|
||||
"""Возвращает все конечные (leaf) элементы, исключая битовые поля и элементы с детьми (реальными)."""
|
||||
def collect_leaf_items(parent):
|
||||
leaf_items = []
|
||||
for i in range(parent.childCount()):
|
||||
child = parent.child(i)
|
||||
if child.isHidden():
|
||||
continue
|
||||
|
||||
# Если есть заглушка — раскрываем
|
||||
self.on_item_expanded(child)
|
||||
|
||||
if child.childCount() == 0:
|
||||
item_type = child.text(1)
|
||||
if item_type and 'bitfield' in str(item_type).lower():
|
||||
continue
|
||||
leaf_items.append(child)
|
||||
else:
|
||||
leaf_items.extend(collect_leaf_items(child))
|
||||
return leaf_items
|
||||
|
||||
all_leaf_items = []
|
||||
for i in range(self.tree.topLevelItemCount()):
|
||||
top = self.tree.topLevelItem(i)
|
||||
|
||||
# Раскрываем lazy, если надо
|
||||
self.on_item_expanded(top)
|
||||
|
||||
if top.childCount() == 0:
|
||||
item_type = top.text(1)
|
||||
if item_type and 'bitfield' in str(item_type).lower():
|
||||
continue
|
||||
all_leaf_items.append(top)
|
||||
else:
|
||||
all_leaf_items.extend(collect_leaf_items(top))
|
||||
return all_leaf_items
|
||||
|
||||
|
||||
|
||||
def _get_internal_selected_items(self):
|
||||
"""Возвращает выделенные элементы и всех их потомков, включая lazy."""
|
||||
selected = self.tree.selectedItems()
|
||||
all_items = []
|
||||
|
||||
def collect_children(item):
|
||||
# Раскрываем при необходимости
|
||||
# Раскрываем lazy, если надо
|
||||
self.on_item_expanded(item)
|
||||
|
||||
items = [item]
|
||||
for i in range(item.childCount()):
|
||||
child = item.child(i)
|
||||
items.extend(collect_children(child))
|
||||
return items
|
||||
|
||||
for item in selected:
|
||||
all_items.extend(collect_children(item))
|
||||
|
||||
return all_items
|
||||
|
||||
def get_selected_items(self):
|
||||
"""Возвращает только конечные (leaf) выделенные элементы, исключая bitfield."""
|
||||
selected = self.tree.selectedItems()
|
||||
leaf_items = []
|
||||
for item in selected:
|
||||
# Раскрываем lazy, если надо
|
||||
self.on_item_expanded(item)
|
||||
|
||||
# Если у узла нет видимых/выделенных детей — он лист
|
||||
if all(item.child(i).isHidden() or not item.child(i).isSelected() for i in range(item.childCount())):
|
||||
item_type = item.data(0, Qt.UserRole)
|
||||
if item_type and 'bitfield' in str(item_type).lower():
|
||||
continue
|
||||
leaf_items.append(item)
|
||||
return leaf_items
|
||||
|
||||
|
||||
def get_all_var_names(self):
|
||||
"""Возвращает имена всех конечных (leaf) переменных, исключая битовые поля и группы."""
|
||||
return [item.text(0) for item in self.get_all_items() if item.text(0)]
|
||||
|
||||
|
||||
def _get_internal_selected_var_names(self):
|
||||
"""Возвращает имена выделенных переменных."""
|
||||
return [item.text(0) for item in self._get_internal_selected_items() if item.text(0)]
|
||||
|
||||
|
||||
def get_selected_var_names(self):
|
||||
"""Возвращает имена только конечных (leaf) переменных из выделенных."""
|
||||
return [item.text(0) for item in self.get_selected_items() if item.text(0)]
|
||||
|
||||
def closeEvent(self, event):
|
||||
self.completer.setWidget(None)
|
||||
self.completer.deleteLater()
|
||||
super().closeEvent(event)
|
||||
392
Src/var_selector_window.py
Normal file
392
Src/var_selector_window.py
Normal file
@@ -0,0 +1,392 @@
|
||||
import re
|
||||
import lxml.etree as ET
|
||||
from PySide2.QtWidgets import (
|
||||
QDialog, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPushButton,
|
||||
QLineEdit, QLabel, QHeaderView, QCompleter, QCheckBox, QHBoxLayout, QSizePolicy
|
||||
)
|
||||
from PySide2.QtGui import QKeySequence, QKeyEvent
|
||||
from PySide2.QtCore import Qt, QStringListModel, QSettings
|
||||
import var_table
|
||||
import var_setup
|
||||
import myXML
|
||||
import time
|
||||
import var_selector_table
|
||||
|
||||
|
||||
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
|
||||
|
||||
class VariableSelectorDialog(QDialog):
|
||||
def __init__(self, table, all_vars, structs, typedefs, xml_path=None, parent=None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Выбор переменных")
|
||||
self.setAttribute(Qt.WA_DeleteOnClose)
|
||||
self.resize(1200, 500)
|
||||
self.selected_names = []
|
||||
self._bckspc_pressed = False # флаг подавления добавления разделителя
|
||||
self.table = table
|
||||
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.node_index = {}
|
||||
self.xml_path = xml_path # сохраняем путь к xml
|
||||
self.manual_completion_active = False
|
||||
|
||||
# --- Добавляем чекбокс для автодополнения ---
|
||||
self.autocomplete_checkbox = QCheckBox("Включить автодополнение")
|
||||
self.autocomplete_checkbox.setChecked(True)
|
||||
|
||||
# Инициализируем QSettings с именем организации и приложения
|
||||
self.settings = QSettings("SET", "DebugVarEdit_VarsSelector")
|
||||
# Восстанавливаем сохранённое состояние чекбокса, если есть
|
||||
checked = self.settings.value("autocomplete_enabled", True, type=bool)
|
||||
self.autocomplete_checkbox.setChecked(checked)
|
||||
# При изменении состояния чекбокса сохраняем его
|
||||
self.autocomplete_checkbox.stateChanged.connect(self.save_checkbox_state)
|
||||
|
||||
# Кнопки между таблицами
|
||||
self.btn_right = QPushButton(">")
|
||||
self.btn_right.clicked.connect(self.on_move_right)
|
||||
self.btn_left = QPushButton("<")
|
||||
self.btn_left.clicked.connect(self.on_move_left)
|
||||
|
||||
# Создаем кнопки, они остаются в диалоге
|
||||
self.btn_accept = QPushButton("Применить")
|
||||
|
||||
# Создаем экземпляр вашего готового виджета
|
||||
self.vars_widget = var_selector_table.VariableSelectWidget(self)
|
||||
self.vars_widget.tree.itemDoubleClicked.connect(self.on_left_tree_double_click)
|
||||
self.vars_widget.setObjectName("LeftTable")
|
||||
self.selected_vars_widget = var_selector_table.VariableSelectWidget(self)
|
||||
self.selected_vars_widget.tree.itemDoubleClicked.connect(self.on_rigth_tree_double_click)
|
||||
self.selected_vars_widget.setObjectName("RightTable")
|
||||
|
||||
# Подписи над таблицами
|
||||
label_all = QLabel("Все переменные")
|
||||
label_all.setStyleSheet("font-weight: bold; font-size: 14px;")
|
||||
|
||||
label_selected = QLabel("Выбранные переменные")
|
||||
label_selected.setStyleSheet("font-weight: bold; font-size: 14px;")
|
||||
|
||||
|
||||
# --- Лэйауты ---
|
||||
main_layout = QVBoxLayout(self) # главный вертикальный layout окна
|
||||
|
||||
# Чекбокс автодополнения — первый в главном layout
|
||||
main_layout.addWidget(self.autocomplete_checkbox)
|
||||
|
||||
# Подписи над таблицами
|
||||
labels_layout = QHBoxLayout()
|
||||
labels_layout.addWidget(label_all)
|
||||
labels_layout.addStretch()
|
||||
labels_layout.addWidget(label_selected)
|
||||
main_layout.addLayout(labels_layout)
|
||||
|
||||
# Горизонтальный layout с таблицами и кнопками
|
||||
tables_layout = QHBoxLayout()
|
||||
|
||||
# Левая таблица
|
||||
self.vars_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
tables_layout.addWidget(self.vars_widget)
|
||||
|
||||
# Кнопки ">" и "<" между таблицами
|
||||
middle_buttons_layout = QVBoxLayout()
|
||||
middle_buttons_layout.addStretch()
|
||||
middle_buttons_layout.addWidget(self.btn_right)
|
||||
middle_buttons_layout.addWidget(self.btn_left)
|
||||
middle_buttons_layout.addStretch()
|
||||
tables_layout.addLayout(middle_buttons_layout)
|
||||
|
||||
# Правая таблица
|
||||
self.selected_vars_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
tables_layout.addWidget(self.selected_vars_widget)
|
||||
|
||||
# Добавляем горизонтальный layout с таблицами в главный вертикальный
|
||||
main_layout.addLayout(tables_layout)
|
||||
|
||||
# Кнопки "Добавить выбранные" и "Удалить выбранные" под таблицами
|
||||
buttons_layout = QVBoxLayout()
|
||||
buttons_layout.addWidget(self.btn_accept)
|
||||
main_layout.addLayout(buttons_layout)
|
||||
|
||||
# Важно, если окно — QDialog или QWidget, установи layout
|
||||
self.setLayout(main_layout)
|
||||
|
||||
|
||||
|
||||
# Соединяем сигналы кнопок с методами диалога
|
||||
self.btn_accept.clicked.connect(self.on_apply_clicked)
|
||||
|
||||
# Соединяем чекбокс с методом виджета
|
||||
self.autocomplete_checkbox.stateChanged.connect(self.set_autocomplete_tables)
|
||||
# Устанавливаем начальное состояние автодополнения в виджете
|
||||
self.vars_widget.set_autocomplete(self.autocomplete_checkbox.isChecked())
|
||||
self.selected_vars_widget.set_autocomplete(self.autocomplete_checkbox.isChecked())
|
||||
|
||||
# --- Код в конце __init__ ---
|
||||
self.expanded_vars = var_setup.expand_vars(self.all_vars, self.structs, self.typedefs)
|
||||
self.update_vars_widget()
|
||||
|
||||
def on_move_right(self):
|
||||
# Устанавливаем show_var=True для всех выбранных переменных из ЛЕВОЙ таблицы
|
||||
selected = self.vars_widget._get_internal_selected_var_names()
|
||||
if not selected:
|
||||
return
|
||||
|
||||
def mark_selected_show_var(data):
|
||||
for var in data:
|
||||
if var['name'] in selected:
|
||||
var['show_var'] = 'true'
|
||||
var['enable'] = 'true'
|
||||
if 'children' in var:
|
||||
mark_selected_show_var(var['children'])
|
||||
mark_selected_show_var(self.expanded_vars)
|
||||
|
||||
self.update_vars_widget()
|
||||
|
||||
def on_move_left(self):
|
||||
# Сбрасываем show_var=False для всех выбранных переменных из ПРАВОЙ таблицы
|
||||
selected = self.selected_vars_widget._get_internal_selected_var_names()
|
||||
if not selected:
|
||||
return
|
||||
|
||||
def mark_selected_hide_var(data):
|
||||
for var in data:
|
||||
if var['name'] in selected:
|
||||
var['show_var'] = 'false'
|
||||
if 'children' in var:
|
||||
mark_selected_hide_var(var['children'])
|
||||
mark_selected_hide_var(self.expanded_vars)
|
||||
|
||||
self.update_vars_widget()
|
||||
|
||||
def update_vars_widget(self):
|
||||
t_start = time.perf_counter()
|
||||
|
||||
t1 = time.perf_counter()
|
||||
self.selected_vars, self.unselected_vars = var_setup.split_vars_by_show_flag(self.expanded_vars)
|
||||
|
||||
t2 = time.perf_counter()
|
||||
self.vars_widget.set_data(self.unselected_vars)
|
||||
|
||||
t3 = time.perf_counter()
|
||||
self.vars_widget.filter_tree()
|
||||
|
||||
t4 = time.perf_counter()
|
||||
self.selected_vars_widget.set_data(self.selected_vars)
|
||||
|
||||
t5 = time.perf_counter()
|
||||
self.selected_vars_widget.filter_tree()
|
||||
|
||||
def on_apply_clicked(self):
|
||||
# Получаем имена всех переменных из правой таблицы (selected_vars_widget)
|
||||
right_var_names = set(self.selected_vars_widget.get_all_var_names())
|
||||
all_items = self.selected_vars_widget.get_all_items()
|
||||
if not all_items:
|
||||
return
|
||||
|
||||
# Устанавливаем show_var=true и enable=true для переменных из правой таблицы
|
||||
def add_or_update_var(item):
|
||||
name = item.text(0)
|
||||
type_str = item.text(1)
|
||||
|
||||
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': 't_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
|
||||
|
||||
for item in all_items:
|
||||
add_or_update_var(item)
|
||||
|
||||
# Сбрасываем show_var и enable у всех переменных, которых нет в правой таблице
|
||||
for var in self.all_vars:
|
||||
if var['name'] not in right_var_names:
|
||||
var['show_var'] = 'false'
|
||||
var['enable'] = 'false'
|
||||
|
||||
# Обновляем expanded_vars чтобы отразить новые show_var и enable
|
||||
def update_expanded_vars(data):
|
||||
for v in data:
|
||||
name = v['name']
|
||||
if name in self.var_map:
|
||||
v['show_var'] = self.var_map[name]['show_var']
|
||||
v['enable'] = self.var_map[name]['enable']
|
||||
if 'children' in v:
|
||||
update_expanded_vars(v['children'])
|
||||
update_expanded_vars(self.expanded_vars)
|
||||
|
||||
# Обновляем отображение в виджетах
|
||||
self.update_vars_widget()
|
||||
|
||||
# Закрываем диалог
|
||||
self.accept()
|
||||
|
||||
|
||||
# Обнови on_left_tree_double_click:
|
||||
def on_left_tree_double_click(self, item, column):
|
||||
selected_names = [item.text(0)]
|
||||
if not selected_names:
|
||||
return
|
||||
|
||||
def mark_selected_show_var(data):
|
||||
for var in data:
|
||||
if var['name'] in selected_names:
|
||||
var['show_var'] = 'true'
|
||||
var['enable'] = 'true'
|
||||
if 'children' in var:
|
||||
mark_selected_show_var(var['children'])
|
||||
mark_selected_show_var(self.expanded_vars)
|
||||
|
||||
self.update_vars_widget()
|
||||
|
||||
# Добавь обработчик двойного клика справа (если нужно):
|
||||
def on_rigth_tree_double_click(self, item, column):
|
||||
selected_names = [item.text(0)]
|
||||
if not selected_names:
|
||||
return
|
||||
|
||||
def mark_selected_hide_var(data):
|
||||
for var in data:
|
||||
if var['name'] in selected_names:
|
||||
var['show_var'] = 'false'
|
||||
if 'children' in var:
|
||||
mark_selected_hide_var(var['children'])
|
||||
mark_selected_hide_var(self.expanded_vars)
|
||||
|
||||
self.update_vars_widget()
|
||||
|
||||
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == Qt.Key_Delete:
|
||||
self.delete_selected_vars()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
def delete_selected_vars(self):
|
||||
selected_names = self._get_selected_var_names()
|
||||
if not selected_names:
|
||||
print("nothing selected")
|
||||
return
|
||||
|
||||
# Обновляем var_map и all_vars
|
||||
for name in selected_names:
|
||||
if name in self.var_map:
|
||||
self.var_map[name]['show_var'] = 'false'
|
||||
self.var_map[name]['enable'] = 'false'
|
||||
|
||||
for v in self.all_vars:
|
||||
if v['name'] == name:
|
||||
v['show_var'] = 'false'
|
||||
v['enable'] = 'false'
|
||||
break
|
||||
|
||||
# Проверка пути к XML
|
||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно обновить переменные.")
|
||||
return
|
||||
|
||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||
if root is None:
|
||||
return
|
||||
|
||||
vars_section = root.find('variables')
|
||||
if vars_section is None:
|
||||
return
|
||||
|
||||
for var_elem in vars_section.findall('var'):
|
||||
name = var_elem.attrib.get('name')
|
||||
if name in selected_names:
|
||||
def set_text(tag, value):
|
||||
el = var_elem.find(tag)
|
||||
if el is None:
|
||||
el = ET.SubElement(var_elem, tag)
|
||||
el.text = value
|
||||
set_text('show_var', 'false')
|
||||
set_text('enable', 'false')
|
||||
|
||||
myXML.fwrite(root, self.xml_path)
|
||||
|
||||
self.table.populate(self.all_vars, self.structs, None)
|
||||
|
||||
# Проверка пути к XML
|
||||
if not hasattr(self, 'xml_path') or not self.xml_path:
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
QMessageBox.warning(self, "Ошибка", "Путь к XML не задан, невозможно удалить переменные.")
|
||||
return
|
||||
|
||||
root, tree = myXML.safe_parse_xml(self.xml_path)
|
||||
if root is None:
|
||||
return
|
||||
|
||||
vars_section = root.find('variables')
|
||||
if vars_section is None:
|
||||
return
|
||||
|
||||
removed_any = False
|
||||
for var_elem in list(vars_section.findall('var')):
|
||||
name = var_elem.attrib.get('name')
|
||||
if name in selected_names:
|
||||
vars_section.remove(var_elem)
|
||||
removed_any = True
|
||||
self.var_map.pop(name, None)
|
||||
|
||||
# Удаляем из all_vars (глобально)
|
||||
self.all_vars[:] = [v for v in self.all_vars if v['name'] not in selected_names]
|
||||
|
||||
# Удаляем из expanded_vars (тоже глобально)
|
||||
def filter_out_selected(vars_list):
|
||||
filtered = []
|
||||
for v in vars_list:
|
||||
if v['name'] not in selected_names:
|
||||
# Рекурсивно фильтруем детей, если есть
|
||||
if 'children' in v:
|
||||
v = v.copy()
|
||||
v['children'] = filter_out_selected(v['children'])
|
||||
filtered.append(v)
|
||||
return filtered
|
||||
|
||||
self.expanded_vars[:] = filter_out_selected(self.expanded_vars)
|
||||
if removed_any:
|
||||
myXML.fwrite(root, self.xml_path)
|
||||
|
||||
self.update_vars_widget()
|
||||
|
||||
def _get_selected_var_names(self):
|
||||
focused = self.focusWidget()
|
||||
if focused and focused is self.vars_widget.tree:
|
||||
return self.vars_widget.get_selected_var_names()
|
||||
elif focused and focused is self.selected_vars_widget.tree:
|
||||
return self.selected_vars_widget.get_selected_var_names()
|
||||
else:
|
||||
return []
|
||||
|
||||
def save_checkbox_state(self):
|
||||
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
|
||||
|
||||
|
||||
|
||||
def set_autocomplete_tables(self, state):
|
||||
self.vars_widget.set_autocomplete(state)
|
||||
self.selected_vars_widget.set_autocomplete(state)
|
||||
@@ -1,11 +1,63 @@
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from generateVars import map_type_to_pt, get_iq_define, type_map
|
||||
import lxml.etree as ET
|
||||
from generate_debug_vars import map_type_to_pt, get_iq_define, type_map
|
||||
from enum import IntEnum
|
||||
import scanVars
|
||||
import scan_vars
|
||||
import myXML
|
||||
import pickle
|
||||
|
||||
|
||||
# Вспомогательные функции, которые теперь будут использоваться виджетом
|
||||
def split_path(path):
|
||||
"""
|
||||
Разбивает путь на компоненты:
|
||||
- 'foo[2].bar[1]->baz' → ['foo', '[2]', 'bar', '[1]', 'baz']
|
||||
Если видит '-' в конце строки (без '>' после) — обрезает этот '-'
|
||||
"""
|
||||
tokens = []
|
||||
token = ''
|
||||
i = 0
|
||||
length = len(path)
|
||||
while i < length:
|
||||
c = path[i]
|
||||
# Разделители: '->' и '.'
|
||||
if c == '-' and i + 1 < length and path[i:i+2] == '->':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
i += 2
|
||||
continue
|
||||
elif c == '-' and i == length - 1:
|
||||
# '-' на конце строки без '>' после — просто пропускаем его
|
||||
i += 1
|
||||
continue
|
||||
elif c == '.':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
i += 1
|
||||
continue
|
||||
elif c == '[':
|
||||
if token:
|
||||
tokens.append(token)
|
||||
token = ''
|
||||
idx = ''
|
||||
while i < length and path[i] != ']':
|
||||
idx += path[i]
|
||||
i += 1
|
||||
if i < length and path[i] == ']':
|
||||
idx += ']'
|
||||
i += 1
|
||||
tokens.append(idx)
|
||||
continue
|
||||
else:
|
||||
token += c
|
||||
i += 1
|
||||
if token:
|
||||
tokens.append(token)
|
||||
return tokens
|
||||
|
||||
|
||||
def make_absolute_path(path, base_path):
|
||||
@@ -86,7 +138,7 @@ def parse_vars(filename, typedef_map=None):
|
||||
'shortname': var.findtext('shortname', name),
|
||||
'pt_type': pt_type,
|
||||
'iq_type': iq_type,
|
||||
'return_type': var.findtext('return_type', ''),
|
||||
'return_type': var.findtext('return_type', 't_iq_none'),
|
||||
'type': var_type,
|
||||
'file': var.findtext('file', ''),
|
||||
'extern': var.findtext('extern', 'false') == 'true',
|
||||
@@ -239,7 +291,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
return process_array(prefix, type_str, structs, typedefs, var_attrs, depth)
|
||||
|
||||
# Ищем структуру по имени типа
|
||||
base_type = scanVars.strip_ptr_and_array(type_str)
|
||||
base_type = scan_vars.strip_ptr_and_array(type_str)
|
||||
fields = structs.get(base_type)
|
||||
if not isinstance(fields, dict):
|
||||
# Не структура и не массив — просто возвращаем пустой список
|
||||
@@ -266,6 +318,12 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
else:
|
||||
field_type_str = None
|
||||
|
||||
|
||||
if '*' in field_type_str:
|
||||
full_name_prefix = full_name + '*'
|
||||
else:
|
||||
full_name_prefix = full_name
|
||||
|
||||
# Обработка, если поле — строка (тип или массив)
|
||||
if field_type_str:
|
||||
base_subtype, sub_dims = parse_array_dims(field_type_str)
|
||||
@@ -311,7 +369,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
|
||||
if isinstance(field_value, dict):
|
||||
# Это одиночная структура — раскрываем рекурсивно
|
||||
sub_items = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1)
|
||||
sub_items = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1)
|
||||
child = {
|
||||
'name': full_name,
|
||||
'type': field_type_str,
|
||||
@@ -353,7 +411,7 @@ def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, de
|
||||
'extern': var_attrs.get('extern'),
|
||||
'static': var_attrs.get('static'),
|
||||
}
|
||||
subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1)
|
||||
subchildren = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1)
|
||||
if subchildren:
|
||||
child['children'] = subchildren
|
||||
children.append(child)
|
||||
@@ -399,3 +457,163 @@ def expand_vars(vars_list, structs, typedefs):
|
||||
|
||||
return expanded
|
||||
|
||||
|
||||
def build_full_names(parts, full_name):
|
||||
"""
|
||||
Восстанавливает вложенные полные имена из списка частей,
|
||||
ориентируясь на оригинальное полное имя (с '.', '->' и индексами).
|
||||
|
||||
Пример:
|
||||
parts = ['arr', '[0]', '[1]', 'ptr', 'val']
|
||||
full_name = 'arr[0][1].ptr->val'
|
||||
|
||||
→ [
|
||||
'arr',
|
||||
'arr[0]',
|
||||
'arr[0][1]',
|
||||
'arr[0][1].ptr',
|
||||
'arr[0][1].ptr->val'
|
||||
]
|
||||
"""
|
||||
names = []
|
||||
acc = ''
|
||||
idx = 0
|
||||
for part in parts:
|
||||
pos = full_name.find(part, idx)
|
||||
if pos == -1:
|
||||
acc += part
|
||||
else:
|
||||
acc = full_name[:pos + len(part)]
|
||||
idx = pos + len(part)
|
||||
names.append(acc)
|
||||
return names
|
||||
|
||||
def find_var_by_name(tree, name):
|
||||
for var in tree:
|
||||
if var.get('name') == name:
|
||||
return var
|
||||
if 'children' in var:
|
||||
found = find_var_by_name(var['children'], name)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
|
||||
def add_to_nested_tree(tree, var, path_parts, full_names=None, depth=0, source_tree=None):
|
||||
if not path_parts:
|
||||
return
|
||||
|
||||
if full_names is None:
|
||||
full_names = build_full_names(path_parts, var['name'])
|
||||
|
||||
current_name = full_names[depth]
|
||||
|
||||
for child in tree:
|
||||
if child.get('name') == current_name:
|
||||
if depth == len(path_parts) - 1:
|
||||
child.update(var)
|
||||
return
|
||||
if 'children' not in child:
|
||||
child['children'] = []
|
||||
add_to_nested_tree(child['children'], var, path_parts, full_names, depth + 1, source_tree)
|
||||
return
|
||||
|
||||
# Ищем в source_tree (expanded_vars) родительский узел по current_name
|
||||
parent_data = {}
|
||||
if source_tree:
|
||||
parent_var = find_var_by_name(source_tree, current_name)
|
||||
if parent_var:
|
||||
# Копируем все поля кроме детей (children)
|
||||
parent_data = {k: v for k, v in parent_var.items() if k != 'children'}
|
||||
|
||||
new_node = {
|
||||
'name': current_name,
|
||||
'children': []
|
||||
}
|
||||
|
||||
# Обновляем new_node данными родителя
|
||||
new_node.update(parent_data)
|
||||
|
||||
if depth == len(path_parts) - 1:
|
||||
new_node.update(var)
|
||||
else:
|
||||
add_to_nested_tree(new_node['children'], var, path_parts, full_names, depth + 1, source_tree)
|
||||
|
||||
tree.append(new_node)
|
||||
|
||||
|
||||
|
||||
|
||||
def split_vars_by_show_flag(expanded_vars):
|
||||
unselected_vars = pickle.loads(pickle.dumps(expanded_vars, protocol=pickle.HIGHEST_PROTOCOL))
|
||||
selected_vars = []
|
||||
|
||||
def find_and_remove(var_list, target_name):
|
||||
"""Удаляет элемент по полному имени и возвращает его"""
|
||||
for i, var in enumerate(var_list):
|
||||
if var.get("name") == target_name:
|
||||
return var_list.pop(i)
|
||||
if 'children' in var:
|
||||
found = find_and_remove(var['children'], target_name)
|
||||
if found:
|
||||
return found
|
||||
return None
|
||||
|
||||
def collect_selected_nodes(var):
|
||||
"""Рекурсивно возвращает все show_var=true узлы (включая поддерево)"""
|
||||
nodes = []
|
||||
if var.get('show_var', 'false').lower() == 'true':
|
||||
nodes.append(var)
|
||||
for child in var.get('children', []):
|
||||
nodes.extend(collect_selected_nodes(child))
|
||||
return nodes
|
||||
|
||||
def exists_by_path(tree, full_name):
|
||||
"""
|
||||
Проверяет, существует ли переменная в дереве, следуя по частям пути (например: project → adc → status).
|
||||
Каждая часть ('project', 'project.adc', ...) должна иметь точное совпадение с 'name' в узле.
|
||||
"""
|
||||
path_parts = split_path(full_name)
|
||||
full_names = build_full_names(path_parts, full_name)
|
||||
|
||||
current_level = tree
|
||||
for name in full_names:
|
||||
found = False
|
||||
for var in current_level:
|
||||
if var.get('name') == name:
|
||||
current_level = var.get('children', [])
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
return False
|
||||
return True
|
||||
|
||||
selected_nodes = []
|
||||
for var in expanded_vars:
|
||||
full_name = var['name']
|
||||
# Проверка: если имя содержит вложенность, но целиком есть в корне — пропускаем
|
||||
if ('.' in full_name or '[' in full_name or '->' in full_name):
|
||||
path_parts = split_path(full_name)
|
||||
if exists_by_path(expanded_vars, full_name):
|
||||
# Удалим лишнюю копию из корня unselected_vars
|
||||
find_and_remove(unselected_vars, full_name)
|
||||
else:
|
||||
add_to_nested_tree(unselected_vars, var, path_parts, source_tree=expanded_vars)
|
||||
find_and_remove(unselected_vars, full_name)
|
||||
selected_nodes.extend(collect_selected_nodes(var))
|
||||
|
||||
for node in selected_nodes:
|
||||
full_name = node['name']
|
||||
|
||||
|
||||
path_parts = split_path(full_name)
|
||||
|
||||
# Вырезать из unselected_vars
|
||||
removed = find_and_remove(unselected_vars, full_name)
|
||||
if removed:
|
||||
add_to_nested_tree(selected_vars, removed, path_parts, source_tree=expanded_vars)
|
||||
else:
|
||||
# вдруг удалённый родитель — создаём вручную
|
||||
add_to_nested_tree(selected_vars, node, path_parts, source_tree=expanded_vars)
|
||||
|
||||
return selected_vars, unselected_vars
|
||||
461
Src/var_table.py
Normal file
461
Src/var_table.py
Normal file
@@ -0,0 +1,461 @@
|
||||
from PySide2.QtWidgets import (
|
||||
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
|
||||
QAbstractItemView, QHeaderView, QLabel, QSpacerItem, QSizePolicy, QSpinBox,
|
||||
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QScrollArea, QWidget
|
||||
)
|
||||
from PySide2.QtGui import QColor, QBrush, QPalette
|
||||
from PySide2.QtCore import Qt, QSettings
|
||||
from enum import IntEnum
|
||||
from generate_debug_vars import type_map
|
||||
import time
|
||||
from typing import Dict, List
|
||||
|
||||
class rows(IntEnum):
|
||||
No = 0
|
||||
include = 1
|
||||
name = 2
|
||||
type = 3
|
||||
pt_type = 4
|
||||
iq_type = 5
|
||||
ret_type = 6
|
||||
short_name = 7
|
||||
|
||||
class FilterDialog(QDialog):
|
||||
def __init__(self, parent, options, selected, title="Выберите значения"):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle(title)
|
||||
self.resize(250, 300)
|
||||
self.selected = set(selected)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
|
||||
scroll = QScrollArea(self)
|
||||
scroll.setWidgetResizable(True)
|
||||
container = QWidget()
|
||||
scroll.setWidget(container)
|
||||
|
||||
self.checkboxes = []
|
||||
vbox = QVBoxLayout(container)
|
||||
|
||||
for opt in options:
|
||||
cb = QCheckBox(opt)
|
||||
cb.setChecked(opt in self.selected)
|
||||
vbox.addWidget(cb)
|
||||
self.checkboxes.append(cb)
|
||||
|
||||
layout.addWidget(scroll)
|
||||
|
||||
btn_layout = QHBoxLayout()
|
||||
btn_ok = QPushButton("OK")
|
||||
btn_cancel = QPushButton("Отмена")
|
||||
btn_layout.addWidget(btn_ok)
|
||||
btn_layout.addWidget(btn_cancel)
|
||||
|
||||
layout.addLayout(btn_layout)
|
||||
|
||||
btn_ok.clicked.connect(self.accept)
|
||||
btn_cancel.clicked.connect(self.reject)
|
||||
|
||||
def get_selected(self):
|
||||
return [cb.text() for cb in self.checkboxes if cb.isChecked()]
|
||||
|
||||
|
||||
class SetSizeDialog(QDialog):
|
||||
"""
|
||||
Диалоговое окно для выбора числового значения (размера).
|
||||
"""
|
||||
def __init__(self, parent=None, initial_value=10, min_value=1, max_value=50, title="Укажите размер короткого имени"):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle(title)
|
||||
self.setFixedSize(320, 120) # Задаем фиксированный размер для аккуратного вида
|
||||
|
||||
# Основной вертикальный макет
|
||||
main_layout = QVBoxLayout(self)
|
||||
|
||||
# Макет для ввода значения
|
||||
input_layout = QHBoxLayout()
|
||||
label = QLabel("Количество символов:", self)
|
||||
|
||||
self.spin_box = QSpinBox(self)
|
||||
self.spin_box.setRange(min_value, max_value) # Устанавливаем диапазон допустимых значений
|
||||
initial_value = parent._shortname_size
|
||||
self.spin_box.setValue(initial_value) # Устанавливаем начальное значение
|
||||
self.spin_box.setFocus() # Устанавливаем фокус на поле ввода
|
||||
|
||||
input_layout.addWidget(label)
|
||||
input_layout.addWidget(self.spin_box)
|
||||
main_layout.addLayout(input_layout)
|
||||
|
||||
# Добавляем пустое пространство для лучшего разделения
|
||||
main_layout.addSpacerItem(QSpacerItem(20, 20, QSizePolicy.Minimum, QSizePolicy.Expanding))
|
||||
|
||||
# Макет для кнопок
|
||||
btn_layout = QHBoxLayout()
|
||||
btn_layout.addStretch() # Добавляем растягивающийся элемент, чтобы кнопки были справа
|
||||
|
||||
btn_ok = QPushButton("OK")
|
||||
btn_cancel = QPushButton("Отмена")
|
||||
|
||||
btn_layout.addWidget(btn_ok)
|
||||
btn_layout.addWidget(btn_cancel)
|
||||
|
||||
main_layout.addLayout(btn_layout)
|
||||
|
||||
# Подключение сигналов к слотам
|
||||
btn_ok.clicked.connect(self.accept) # При нажатии "OK" диалог закроется со статусом "Accepted"
|
||||
btn_cancel.clicked.connect(self.reject) # При нажатии "Отмена" - со статусом "Rejected"
|
||||
|
||||
def get_selected_size(self):
|
||||
"""
|
||||
Возвращает значение, выбранное в QSpinBox.
|
||||
"""
|
||||
return self.spin_box.value()
|
||||
|
||||
class CtrlScrollComboBox(QComboBox):
|
||||
def wheelEvent(self, event):
|
||||
if event.modifiers() & Qt.ControlModifier:
|
||||
super().wheelEvent(event)
|
||||
else:
|
||||
event.ignore()
|
||||
|
||||
class VariableTableWidget(QTableWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(0, 8, parent)
|
||||
# Таблица переменных
|
||||
self.setHorizontalHeaderLabels([
|
||||
'№', # новый столбец
|
||||
'En',
|
||||
'Name',
|
||||
'Origin Type',
|
||||
'Base Type',
|
||||
'IQ Type',
|
||||
'Return Type',
|
||||
'Short Name'
|
||||
])
|
||||
self.setEditTriggers(QAbstractItemView.AllEditTriggers)
|
||||
self.var_list = []
|
||||
# Инициализируем QSettings с именем организации и приложения
|
||||
self.settings = QSettings("SET", "DebugVarEdit_VarTable")
|
||||
# Восстанавливаем сохранённое состояние, если есть
|
||||
shortsize = self.settings.value("shortname_size", True, type=int)
|
||||
self._shortname_size = shortsize
|
||||
|
||||
self.type_options = list(dict.fromkeys(type_map.values()))
|
||||
self.pt_types_all = [t.replace('pt_', '') for t in self.type_options]
|
||||
self.iq_types_all = ['iq_none', 'iq'] + [f'iq{i}' for i in range(1, 31)]
|
||||
# Задаём базовые iq-типы (без префикса 'iq_')
|
||||
self.iq_types = ['iq_none', 'iq', 'iq10', 'iq15', 'iq19', 'iq24']
|
||||
# Фильтруем типы из type_map.values() исключая те, что содержат 'arr' или 'ptr'
|
||||
type_options = [t for t in dict.fromkeys(type_map.values()) if 'arr' not in t and 'ptr' not in t and
|
||||
'struct' not in t and 'union' not in t and '64' not in t]
|
||||
# Формируем display_type_options без префикса 'pt_'
|
||||
self.pt_types = [t.replace('pt_', '') for t in type_options]
|
||||
|
||||
self._iq_type_filter = list(self.iq_types) # Текущий фильтр iq типов (по умолчанию все)
|
||||
self._pt_type_filter = list(self.pt_types)
|
||||
self._ret_type_filter = list(self.iq_types)
|
||||
header = self.horizontalHeader()
|
||||
# Для остальных колонок — растяжение (Stretch), чтобы они заняли всю оставшуюся ширину
|
||||
|
||||
for col in range(self.columnCount()):
|
||||
if col == self.columnCount() - 1:
|
||||
header.setSectionResizeMode(col, QHeaderView.Stretch)
|
||||
else:
|
||||
header.setSectionResizeMode(col, QHeaderView.Interactive)
|
||||
|
||||
parent_widget = self.parentWidget()
|
||||
# Сделаем колонки с номерами фиксированной ширины
|
||||
self.setColumnWidth(rows.No, 30)
|
||||
self.setColumnWidth(rows.include, 30)
|
||||
self.setColumnWidth(rows.pt_type, 85)
|
||||
self.setColumnWidth(rows.iq_type, 85)
|
||||
self.setColumnWidth(rows.ret_type, 85)
|
||||
|
||||
self.setColumnWidth(rows.name, 300)
|
||||
self.setColumnWidth(rows.type, 100)
|
||||
self._resizing = False
|
||||
self.horizontalHeader().sectionResized.connect(self.on_section_resized)
|
||||
self.horizontalHeader().sectionClicked.connect(self.on_header_clicked)
|
||||
|
||||
|
||||
def populate(self, vars_list, structs, on_change_callback):
|
||||
self.var_list = vars_list
|
||||
|
||||
# --- ДО: удаляем отображение структур и union-переменных
|
||||
for var in vars_list:
|
||||
pt_type = var.get('pt_type', '')
|
||||
if 'struct' in pt_type or 'union' in pt_type:
|
||||
var['show_var'] = 'false'
|
||||
var['enable'] = 'false'
|
||||
|
||||
|
||||
filtered_vars = [v for v in vars_list if v.get('show_var', 'false') == 'true']
|
||||
self.setRowCount(len(filtered_vars))
|
||||
self.verticalHeader().setVisible(False)
|
||||
style_with_padding = "padding-left: 5px; padding-right: 5px; font-size: 14pt; font-family: 'Segoe UI';"
|
||||
|
||||
|
||||
|
||||
|
||||
for row, var in enumerate(filtered_vars):
|
||||
# №
|
||||
no_item = QTableWidgetItem(str(row))
|
||||
no_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
self.setItem(row, rows.No, no_item)
|
||||
|
||||
# Enable
|
||||
cb = QCheckBox()
|
||||
cb.setChecked(var.get('enable', 'false') == 'true')
|
||||
cb.stateChanged.connect(on_change_callback)
|
||||
cb.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.include, cb)
|
||||
|
||||
# Name
|
||||
name_edit = QLineEdit(var['name'])
|
||||
if var['type'] in structs:
|
||||
completer = QCompleter(structs[var['type']].keys())
|
||||
completer.setCaseSensitivity(Qt.CaseInsensitive)
|
||||
name_edit.setCompleter(completer)
|
||||
name_edit.textChanged.connect(on_change_callback)
|
||||
name_edit.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.name, name_edit)
|
||||
|
||||
# Origin Type (readonly)
|
||||
origin_item = QTableWidgetItem(var.get('type', ''))
|
||||
origin_item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled)
|
||||
origin_item.setToolTip(var.get('type', '')) # Всплывающая подсказка
|
||||
origin_item.setForeground(QBrush(Qt.black))
|
||||
self.setItem(row, rows.type, origin_item)
|
||||
|
||||
# pt_type
|
||||
pt_combo = CtrlScrollComboBox()
|
||||
pt_combo.addItems(self.pt_types)
|
||||
value = var['pt_type'].replace('pt_', '')
|
||||
if value not in self.pt_types:
|
||||
pt_combo.addItem(value)
|
||||
pt_combo.setCurrentText(value)
|
||||
pt_combo.currentTextChanged.connect(on_change_callback)
|
||||
pt_combo.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.pt_type, pt_combo)
|
||||
|
||||
# iq_type
|
||||
iq_combo = CtrlScrollComboBox()
|
||||
iq_combo.addItems(self.iq_types)
|
||||
value = var['iq_type'].replace('t_', '')
|
||||
if value not in self.iq_types:
|
||||
iq_combo.addItem(value)
|
||||
iq_combo.setCurrentText(value)
|
||||
iq_combo.currentTextChanged.connect(on_change_callback)
|
||||
iq_combo.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.iq_type, iq_combo)
|
||||
|
||||
# return_type
|
||||
ret_combo = CtrlScrollComboBox()
|
||||
ret_combo.addItems(self.iq_types)
|
||||
value = var['return_type'].replace('t_', '')
|
||||
ret_combo.setCurrentText(value)
|
||||
ret_combo.currentTextChanged.connect(on_change_callback)
|
||||
ret_combo.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.ret_type, ret_combo)
|
||||
|
||||
# short_name
|
||||
short_name_val = var.get('shortname', var['name'])
|
||||
short_name_edit = QLineEdit(short_name_val)
|
||||
short_name_edit.textChanged.connect(on_change_callback)
|
||||
short_name_edit.setStyleSheet(style_with_padding)
|
||||
self.setCellWidget(row, rows.short_name, short_name_edit)
|
||||
|
||||
self.check()
|
||||
|
||||
def check(self):
|
||||
warning_color = QColor("#FFFACD")
|
||||
error_color = QColor("#FFB6C1")
|
||||
tooltip_shortname = "Short Name длиннее 10 символов — будет обрезано при генерации"
|
||||
tooltip_missing = 'Имя переменной не найдено среди переменных. Добавьте её через кнопку "Добавить переменные"'
|
||||
|
||||
var_names_set = {v.get('name') for v in self.var_list if v.get('name')}
|
||||
|
||||
t0 = time.time()
|
||||
self.setUpdatesEnabled(False)
|
||||
for row in range(self.rowCount()):
|
||||
t1 = time.time()
|
||||
name_widget = self.cellWidget(row, rows.name)
|
||||
t2 = time.time()
|
||||
name = name_widget.text() if name_widget else ""
|
||||
|
||||
short_name_edit = self.cellWidget(row, rows.short_name)
|
||||
t3 = time.time()
|
||||
shortname = short_name_edit.text() if short_name_edit else ""
|
||||
|
||||
long_shortname = len(shortname) > self._shortname_size
|
||||
found = name in var_names_set
|
||||
|
||||
color = None
|
||||
tooltip = ""
|
||||
if not found:
|
||||
color = error_color
|
||||
tooltip = tooltip_missing
|
||||
elif long_shortname:
|
||||
color = warning_color
|
||||
tooltip = tooltip_shortname
|
||||
|
||||
self.highlight_row(row, color, tooltip)
|
||||
t4 = time.time()
|
||||
|
||||
self.setUpdatesEnabled(True)
|
||||
#print(f"Row {row}: cellWidget(name) {t2-t1:.4f}s, cellWidget(shortname) {t3-t2:.4f}s, highlight_row {t4-t3:.4f}s")
|
||||
|
||||
|
||||
def read_data(self):
|
||||
result = []
|
||||
for row in range(self.rowCount()):
|
||||
cb = self.cellWidget(row, rows.include)
|
||||
name = self.cellWidget(row, rows.name).text()
|
||||
pt = self.cellWidget(row, rows.pt_type).currentText()
|
||||
iq = self.cellWidget(row, rows.iq_type).currentText()
|
||||
ret = self.cellWidget(row, rows.ret_type).currentText()
|
||||
shortname = self.cellWidget(row, rows.short_name).text()
|
||||
origin_type = self.item(row, rows.type).text()
|
||||
|
||||
result.append({
|
||||
'show_var': True,
|
||||
'enable': cb.isChecked(),
|
||||
'name': name,
|
||||
'pt_type': f'pt_{pt}',
|
||||
'iq_type': f't_{iq}',
|
||||
'return_type': f't_{ret}',
|
||||
'shortname': shortname,
|
||||
'type': origin_type,
|
||||
})
|
||||
return result
|
||||
|
||||
|
||||
|
||||
def on_header_clicked(self, logicalIndex):
|
||||
if logicalIndex == rows.pt_type:
|
||||
dlg = FilterDialog(self, self.pt_types_all, self._pt_type_filter, "Выберите базовые типы")
|
||||
if dlg.exec_():
|
||||
self._pt_type_filter = dlg.get_selected()
|
||||
self.update_comboboxes({rows.pt_type: self._pt_type_filter})
|
||||
|
||||
elif logicalIndex == rows.iq_type:
|
||||
dlg = FilterDialog(self, self.iq_types_all, self._iq_type_filter, "Выберите IQ типы")
|
||||
if dlg.exec_():
|
||||
self._iq_type_filter = dlg.get_selected()
|
||||
self.update_comboboxes({rows.iq_type: self._iq_type_filter})
|
||||
|
||||
elif logicalIndex == rows.ret_type:
|
||||
dlg = FilterDialog(self, self.iq_types_all, self._ret_type_filter, "Выберите IQ типы")
|
||||
if dlg.exec_():
|
||||
self._ret_type_filter = dlg.get_selected()
|
||||
self.update_comboboxes({rows.ret_type: self._ret_type_filter})
|
||||
|
||||
elif logicalIndex == rows.short_name:
|
||||
dlg = SetSizeDialog(self)
|
||||
if dlg.exec_():
|
||||
self._shortname_size = dlg.get_selected_size()
|
||||
self.settings.setValue("shortname_size", self._shortname_size)
|
||||
self.check()
|
||||
|
||||
|
||||
|
||||
def update_comboboxes(self, columns_filters: Dict[int, List[str]]):
|
||||
"""
|
||||
Обновляет combobox-ячейки в указанных столбцах таблицы.
|
||||
|
||||
:param columns_filters: dict, где ключ — индекс столбца,
|
||||
значение — список допустимых вариантов для combobox.
|
||||
"""
|
||||
for row in range(self.rowCount()):
|
||||
for col, allowed_items in columns_filters.items():
|
||||
combo = self.cellWidget(row, col)
|
||||
if combo:
|
||||
current = combo.currentText()
|
||||
combo.blockSignals(True)
|
||||
combo.clear()
|
||||
|
||||
combo.addItems(allowed_items)
|
||||
if current in allowed_items:
|
||||
combo.setCurrentText(current)
|
||||
else:
|
||||
combo.setCurrentIndex(0)
|
||||
combo.blockSignals(False)
|
||||
|
||||
|
||||
|
||||
def on_section_resized(self, logicalIndex, oldSize, newSize):
|
||||
if self._resizing:
|
||||
return # предотвращаем рекурсию
|
||||
|
||||
min_width = 50
|
||||
delta = newSize - oldSize
|
||||
right_index = logicalIndex + 1
|
||||
|
||||
if right_index >= self.columnCount():
|
||||
# Если правая колока - нет соседа, ограничиваем минимальную ширину
|
||||
if newSize < min_width:
|
||||
self._resizing = True
|
||||
self.setColumnWidth(logicalIndex, min_width)
|
||||
self._resizing = False
|
||||
return
|
||||
|
||||
self._resizing = True
|
||||
try:
|
||||
right_width = self.columnWidth(right_index)
|
||||
new_right_width = right_width - delta
|
||||
|
||||
# Если соседняя колонка станет уже минимальной - подкорректируем левую
|
||||
if new_right_width < min_width:
|
||||
new_right_width = min_width
|
||||
newSize = oldSize + (right_width - min_width)
|
||||
self.setColumnWidth(logicalIndex, newSize)
|
||||
|
||||
self.setColumnWidth(right_index, new_right_width)
|
||||
finally:
|
||||
self._resizing = False
|
||||
|
||||
|
||||
|
||||
|
||||
def highlight_row(self, row: int, color: QColor = None, tooltip: str = ""):
|
||||
"""
|
||||
Подсвечивает строку таблицы цветом `color`, не меняя шрифт.
|
||||
Работает с QLineEdit, QComboBox, QCheckBox (включая обёртки).
|
||||
Если `color=None`, сбрасывает подсветку.
|
||||
"""
|
||||
css_reset = "background-color: none; font: inherit;"
|
||||
css_color = f"background-color: {color.name()};" if color else css_reset
|
||||
|
||||
for col in range(self.columnCount()):
|
||||
item = self.item(row, col)
|
||||
widget = self.cellWidget(row, col)
|
||||
|
||||
if item is not None:
|
||||
current_bg = item.background().color() if item.background() else None
|
||||
if color and current_bg != color:
|
||||
item.setBackground(QBrush(color))
|
||||
item.setToolTip(tooltip)
|
||||
elif not color and current_bg is not None:
|
||||
item.setBackground(QBrush(Qt.NoBrush))
|
||||
item.setToolTip("")
|
||||
elif widget is not None:
|
||||
if widget.styleSheet() != css_color:
|
||||
widget.setStyleSheet(css_color)
|
||||
current_tip = widget.toolTip()
|
||||
if color and current_tip != tooltip:
|
||||
widget.setToolTip(tooltip)
|
||||
elif not color and current_tip:
|
||||
widget.setToolTip("")
|
||||
|
||||
def get_selected_var_names(self):
|
||||
selected_indexes = self.selectedIndexes()
|
||||
selected_rows = set(index.row() for index in selected_indexes)
|
||||
names = []
|
||||
|
||||
for row in selected_rows:
|
||||
name_widget = self.cellWidget(row, rows.name)
|
||||
if name_widget:
|
||||
name = name_widget.text()
|
||||
if name:
|
||||
names.append(name)
|
||||
return names
|
||||
499
debug_tools.c
499
debug_tools.c
@@ -1,117 +1,148 @@
|
||||
#include "debug_tools.h"
|
||||
#include "IQmathLib.h"
|
||||
|
||||
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var);
|
||||
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var);
|
||||
#if !defined(GLOBAL_Q)
|
||||
#define GLOBAL_Q 16
|
||||
#endif
|
||||
|
||||
|
||||
DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT; ///< Ñòðóêòóðà îòëàäêè íèæíåãî óðîâíÿ (èíèöèàëèçàöèÿ)
|
||||
|
||||
long var_numb = 1;
|
||||
long return_var;
|
||||
long return_ll_var;
|
||||
DebugVar_t dbg_var_ll;
|
||||
int result;
|
||||
char ext_date[] = {7, 233, 11, 07, 16, 50};
|
||||
int Debug_Test_Example(void)
|
||||
static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var);
|
||||
static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var);
|
||||
|
||||
///////////////////////////----EXAPLE-----//////////////////////////////
|
||||
int var_numb = 1; ///< Ïðèìåð ïåðåìåííîé äëÿ îòëàäêè
|
||||
DebugVarName_t var_name; ///< Èìÿ ïåðåìåííîé
|
||||
int32_t return_var; ///< Ïåðåìåííàÿ äëÿ âîçâðàòà ðåçóëüòàòà
|
||||
int32_t return_ll_var; ///< Âîçâðàùàåìîå çíà÷åíèå ñ íèæíåãî óðîâíÿ
|
||||
int result; ///< Ïåðåìåííàÿ ðåçóëüòàòà
|
||||
DateTime_t ext_date = {2025, 11, 07, 16, 50}; ///< Ïðèìåð âíåøíåé äàòû ñáîðêè
|
||||
|
||||
/**
|
||||
* @brief Ïðèìåð èñïîëüçîâàíèÿ ôóíêöèé îòëàäêè.
|
||||
* @details Äåìîíñòðàöèîííàÿ ôóíêöèÿ äëÿ ðàáîòû ñ ïåðåìåííûìè îòëàäêè.
|
||||
*/
|
||||
void Debug_Test_Example(void)
|
||||
{
|
||||
result = Debug_ReadVar(&dbg_vars[var_numb], &return_var);
|
||||
result = Debug_ReadVar(var_numb, &return_var);
|
||||
result = Debug_ReadVarName(var_numb, var_name);
|
||||
|
||||
|
||||
if(Debug_LowLevel_Initialize(ext_date) == 0)
|
||||
result = Debug_LowLevel_ReadVar(&dbg_var_ll, &return_ll_var);
|
||||
if(Debug_LowLevel_Initialize(&ext_date) == 0)
|
||||
result = Debug_LowLevel_ReadVar(&return_ll_var);
|
||||
}
|
||||
|
||||
///////////////////////////----PUBLIC-----//////////////////////////////
|
||||
|
||||
int Debug_LowLevel_ReadVar(DebugVar_t *var_ll, long *return_long)
|
||||
/**
|
||||
* @brief ×èòàåò ïåðåìåííóþ ïî èíäåêñó.
|
||||
* @param var_ind – èíäåêñ ïåðåìåííîé.
|
||||
* @param return_32b – óêàçàòåëü äëÿ âîçâðàòà ðåçóëüòàòà.
|
||||
* @return int – 0: óñïåõ, 1: îøèáêà.
|
||||
* @details Èñïîëüçóåòñÿ äëÿ ÷òåíèÿ çíà÷åíèé ïåðåìåííûõ ïî èõ èíäåêñó.
|
||||
*/
|
||||
int Debug_ReadVar(int var_ind, int32_t *return_32b)
|
||||
{
|
||||
if (var_ll == NULL)
|
||||
if(return_32b == NULL)
|
||||
return 1;
|
||||
int32_t tmp_var;
|
||||
|
||||
if (var_ind >= DebugVar_Qnt)
|
||||
return 1;
|
||||
if((dbg_vars[var_ind].ptr_type == pt_struct) || (dbg_vars[var_ind].ptr_type == pt_union) ||
|
||||
(dbg_vars[var_ind].ptr_type == pt_unknown))
|
||||
return 1;
|
||||
|
||||
char *addr = var_ll->Ptr;
|
||||
unsigned long addr_val = (unsigned long)addr;
|
||||
// Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè íà TMS320F2812
|
||||
if (!(
|
||||
(addr_val <= 0x0007FF) || // RAMM0 + RAMM1
|
||||
(addr_val >= 0x008000 && addr_val <= 0x009FFF) || // L0 + L1 SARAM
|
||||
(addr_val >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0
|
||||
(addr_val >= 0x3D8000 && addr_val <= 0x3EFFFF) || // Flash A-F
|
||||
(addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) // Boot ROM / Reset
|
||||
)) {
|
||||
return 2; // àäðåñ âíå äîïóñòèìîãî äèàïàçîíà, èãíîðèðóåì
|
||||
}
|
||||
|
||||
convertDebugVarToIQx(var_ll, return_long);
|
||||
|
||||
return 0;
|
||||
return convertDebugVarToIQx(&dbg_vars[var_ind], return_32b);
|
||||
}
|
||||
|
||||
|
||||
int Debug_ReadVar(DebugVar_t *var, long *return_long)
|
||||
/**
|
||||
* @brief ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó.
|
||||
* @param var_ind – èíäåêñ ïåðåìåííîé.
|
||||
* @param name_ptr – óêàçàòåëü íà áóôåð èìåíè (DebugVarName_t).
|
||||
* @return int – 0: óñïåõ, 1: îøèáêà.
|
||||
* @details Êîïèðóåò èìÿ ïåðåìåííîé â ïðåäîñòàâëåííûé áóôåð.
|
||||
*/
|
||||
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr)
|
||||
{
|
||||
long tmp_var;
|
||||
if (var == NULL)
|
||||
return 1;
|
||||
if((var->ptr_type == pt_struct) || (var->ptr_type == pt_union) ||
|
||||
(var->ptr_type == pt_unknown))
|
||||
if(name_ptr == NULL)
|
||||
return 1;
|
||||
|
||||
convertDebugVarToIQx(var, return_long);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Debug_ReadVarName(DebugVar_t *var, char *name_ptr)
|
||||
{
|
||||
if((var == NULL)||(name_ptr == NULL))
|
||||
if (var_ind >= DebugVar_Qnt)
|
||||
return 1;
|
||||
|
||||
int i;
|
||||
// Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
|
||||
for (i = 0; i < sizeof(var->name); i++)
|
||||
for (i = 0; i < sizeof(dbg_vars[var_ind].name); i++)
|
||||
{
|
||||
name_ptr[i] = var->name[i];
|
||||
if (var->name[i] == '\0')
|
||||
name_ptr[i] = dbg_vars[var_ind].name[i];
|
||||
if (dbg_vars[var_ind].name[i] == '\0')
|
||||
break;
|
||||
}
|
||||
// Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
|
||||
name_ptr[sizeof(var->name) - 1] = '\0';
|
||||
name_ptr[sizeof(dbg_vars[var_ind].name) - 1] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief ×èòàåò çíà÷åíèå ïåðåìåííîé îòëàäêè ñ íèæíåãî óðîâíÿ.
|
||||
* @param return_32b – óêàçàòåëü íà ïåðåìåííóþ, êóäà çàïèñûâàåòñÿ ðåçóëüòàò.
|
||||
* @return int – 0: óñïåõ, 1: îøèáêà, 2: íåäîïóñòèìûé àäðåñ.
|
||||
* @details Èñïîëüçóåò àäðåññ, ïåðåäàâàåìûé ñ òåðìèíàëêè äëÿ ïîëó÷åíèÿ çíà÷åíèÿ.
|
||||
*/
|
||||
int Debug_LowLevel_ReadVar(int32_t *return_32b)
|
||||
{
|
||||
if (return_32b == NULL)
|
||||
return 1;
|
||||
if (debug_ll.isVerified == 0)
|
||||
return 1;
|
||||
|
||||
int Debug_LowLevel_Initialize(const char* external_date)
|
||||
uint8_t *addr = debug_ll.dbg_var.Ptr;
|
||||
uint32_t addr_val = (uint32_t)addr;
|
||||
|
||||
// Ðàçðåø¸ííûå äèàïàçîíû ïàìÿòè (èç .cmd ôàéëà)
|
||||
if (!(
|
||||
(addr_val <= 0x0007FF) || // RAMM0 + RAMM1
|
||||
(addr_val >= 0x008120 && addr_val <= 0x009FFC) || // L0 + L1 SARAM
|
||||
(addr_val >= 0x3F8000 && addr_val <= 0x3F9FFF) || // PRAMH0 + DRAMH0
|
||||
(addr_val >= 0x3FF000 && addr_val <= 0x3FFFFF) || // BOOTROM + RESET
|
||||
(addr_val >= 0x080002 && addr_val <= 0x09FFFF) || // RAMEX1
|
||||
(addr_val >= 0x0F0000 && addr_val <= 0x0FFEFF) || // RAMEX4
|
||||
(addr_val >= 0x100002 && addr_val <= 0x103FFF) || // RAMEX0 + RAMEX2 + RAMEX01
|
||||
(addr_val >= 0x102000 && addr_val <= 0x103FFF) // RAMEX2
|
||||
)) {
|
||||
return 2; // Çàïðåù¸ííûé àäðåñ — íåëüçÿ ÷èòàòü
|
||||
}
|
||||
|
||||
return convertDebugVarToIQx(&debug_ll.dbg_var, return_32b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Èíèöèàëèçàöèÿ îòëàäêè íà íèæíåì óðîâíå ïî äàòå ñáîðêè.
|
||||
* @param external_date – ñòðóêòóðà ñ äàòîé DateTime_t
|
||||
* @return int – 0: ñîâïàäàåò, 1: íå ñîâïàäàåò, -1: îøèáêà.
|
||||
* @details Ñðàâíèâàåò äàòó êîìïèëÿöèè ñ çàïðàøèâàåìîé è èíèöèàëèçèðóåò îòëàäî÷íóþ ïåðåìåííóþ.
|
||||
*/
|
||||
int Debug_LowLevel_Initialize(DateTime_t* external_date)
|
||||
{
|
||||
if (external_date == NULL) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Ïðåîáðàçóåì external_date â ñòðóêòóðó
|
||||
DateTimeHex ext;
|
||||
ext.year = (external_date[0] << 8) | external_date[1];
|
||||
ext.day = external_date[2];
|
||||
ext.month = external_date[3];
|
||||
ext.hour = external_date[4];
|
||||
ext.minute = external_date[5];
|
||||
|
||||
// Çàïîëíèì ñòðóêòóðó build èç ìàêðîñîâ (ïðåäïîëàãàåì, ÷òî îíè ÷èñëîâûå)
|
||||
DateTimeHex build;
|
||||
|
||||
build.year = BUILD_YEAR; // íàïðèìåð 2025
|
||||
build.month = BUILD_MONTH; // íàïðèìåð 7
|
||||
build.day = BUILD_DATA; // íàïðèìåð 11
|
||||
build.hour = BUILD_HOURS; // íàïðèìåð 16
|
||||
build.minute = BUILD_MINUTES;// íàïðèìåð 50
|
||||
|
||||
// Ñðàâíåíèå âñåõ ïîëåé
|
||||
if (ext.year == build.year &&
|
||||
ext.month == build.month &&
|
||||
ext.day == build.day &&
|
||||
ext.hour == build.hour &&
|
||||
ext.minute == build.minute)
|
||||
if (external_date->year == debug_ll.build_date.year &&
|
||||
external_date->month == debug_ll.build_date.month &&
|
||||
external_date->day == debug_ll.build_date.day &&
|
||||
external_date->hour == debug_ll.build_date.hour &&
|
||||
external_date->minute == debug_ll.build_date.minute)
|
||||
{
|
||||
debug_ll.isVerified = 1;
|
||||
return 0; // Ñîâïàëî
|
||||
}
|
||||
debug_ll.isVerified = 0;
|
||||
|
||||
return 1; // Íå ñîâïàëî
|
||||
}
|
||||
@@ -121,263 +152,136 @@ int Debug_LowLevel_Initialize(const char* external_date)
|
||||
|
||||
|
||||
/////////////////////----INTERNAL FUNCTIONS-----////////////////////////
|
||||
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var)
|
||||
/**
|
||||
* @brief Ïðåîáðàçóåò òèï IQ ïåðåìåííîé â ÷èñëî áèòîâ äëÿ ñäâèãà(Q-ôàêòîð).
|
||||
* @param t – òèï IQ (ïåðå÷èñëåíèå DebugVarIQType_t).
|
||||
* @return int – Q-ôàêòîð (íàïðèìåð, 24), 0: åñëè t_iq_none, -1: îøèáêà.
|
||||
* @details Ñîïîñòàâëÿåò òèï IQ ïåðåìåííîé ñ ñîîòâåòñòâóþùèì Q-çíà÷åíèåì.
|
||||
*/
|
||||
static int iqTypeToQ(DebugVarIQType_t t)
|
||||
{
|
||||
long iq_numb, iq_united, iq_final;
|
||||
if (t == t_iq_none)
|
||||
return 0; // áåç IQ, float, int
|
||||
else if (t == t_iq)
|
||||
return GLOBAL_Q; // îáùèé IQ, íàïðèìåð 24
|
||||
else if (t >= t_iq1 && t <= t_iq30)
|
||||
return (int)t - (int)t_iq1 + 1; // íàïðèìåð t_iq1 -> 1, t_iq2 -> 2 è ò.ä.
|
||||
else
|
||||
return -1; // îøèáêà
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Ïðåîáðàçóåò ïåðåìåííóþ îòëàäêè â IQ ôîðìàò.
|
||||
* @param var – óêàçàòåëü íà ïåðåìåííóþ îòëàäêè.
|
||||
* @param ret_var – óêàçàòåëü äëÿ âîçâðàòà çíà÷åíèÿ â ôîðìàòå 32 áèòà.
|
||||
* @return int – 0: óñïåõ, 1: îøèáêà ÷òåíèÿ, 2: íåïðàâèëüíûé ôîðìàò, 3: ïåðåïîëíåíèå.
|
||||
* @details Îïðåäåëÿåò ôîðìàò IQ ïåðåìåííîé, êîíâåðòèðóåò å¸ â 32b ñ ó÷¸òîì ìàñøòàáà.
|
||||
*/
|
||||
static int convertDebugVarToIQx(DebugVar_t *var, int32_t *ret_var)
|
||||
{
|
||||
int32_t iq_numb, iq_united, iq_final;
|
||||
float float_numb;
|
||||
|
||||
if(getDebugVar(var, &iq_numb, &float_numb) != 0)
|
||||
return 1;
|
||||
|
||||
// ïðèâåäåíèå ê îäíîìó IQ
|
||||
switch(var->iq_type)
|
||||
{
|
||||
case t_iq_none:
|
||||
if(var->ptr_type == pt_float)
|
||||
{
|
||||
iq_united = _IQ(float_numb);
|
||||
}
|
||||
else
|
||||
{
|
||||
iq_united = _IQ(iq_numb);
|
||||
}
|
||||
break;
|
||||
case t_iq1:
|
||||
iq_united = _IQ1toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq2:
|
||||
iq_united = _IQ2toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq3:
|
||||
iq_united = _IQ3toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq4:
|
||||
iq_united = _IQ4toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq5:
|
||||
iq_united = _IQ5toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq6:
|
||||
iq_united = _IQ6toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq7:
|
||||
iq_united = _IQ7toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq8:
|
||||
iq_united = _IQ8toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq9:
|
||||
iq_united = _IQ9toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq10:
|
||||
iq_united = _IQ10toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq11:
|
||||
iq_united = _IQ11toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq12:
|
||||
iq_united = _IQ12toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq13:
|
||||
iq_united = _IQ13toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq14:
|
||||
iq_united = _IQ14toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq15:
|
||||
iq_united = _IQ15toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq16:
|
||||
iq_united = _IQ16toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq17:
|
||||
iq_united = _IQ17toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq18:
|
||||
iq_united = _IQ18toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq19:
|
||||
iq_united = _IQ19toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq20:
|
||||
iq_united = _IQ20toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq21:
|
||||
iq_united = _IQ21toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq22:
|
||||
iq_united = _IQ22toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq23:
|
||||
iq_united = _IQ23toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq24:
|
||||
iq_united = _IQ24toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq25:
|
||||
iq_united = _IQ25toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq26:
|
||||
iq_united = _IQ26toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq27:
|
||||
iq_united = _IQ27toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq28:
|
||||
iq_united = _IQ28toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq29:
|
||||
iq_united = _IQ29toIQ(iq_numb);
|
||||
break;
|
||||
case t_iq30:
|
||||
iq_united = _IQ30toIQ(iq_numb);
|
||||
break;
|
||||
int src_q = iqTypeToQ(var->iq_type);
|
||||
int dst_q = iqTypeToQ(var->return_type);
|
||||
|
||||
if (src_q < 0 || dst_q < 0)
|
||||
return 2; // íåïðàâèëüíûé ôîðìàò
|
||||
|
||||
int64_t iq_united64 = 0;
|
||||
int64_t iq_final64 = 0;
|
||||
|
||||
// Êîíâåðòàöèÿ ê GLOBAL_Q (64-áèò)
|
||||
if (var->iq_type == t_iq_none) {
|
||||
if (var->ptr_type == pt_float) {
|
||||
// float_numb óìíîæàåì íà 2^GLOBAL_Q
|
||||
// Ðåçóëüòàò ïðèâîäèì ê 64 áèòà
|
||||
iq_united64 = (int64_t)(float_numb * (1 << GLOBAL_Q));
|
||||
} else {
|
||||
iq_united64 = ((int64_t)iq_numb) << GLOBAL_Q;
|
||||
}
|
||||
} else {
|
||||
int shift = GLOBAL_Q - src_q;
|
||||
if (shift >= 0)
|
||||
iq_united64 = ((int64_t)iq_numb) << shift;
|
||||
else
|
||||
iq_united64 = ((int64_t)iq_numb) >> (-shift);
|
||||
}
|
||||
|
||||
// ïðèâåäåíèå îáùåãî IQ ê çàïðàøèâàåìîìó
|
||||
switch(var->return_type)
|
||||
{
|
||||
case t_iq_none:
|
||||
iq_final = (long)_IQtoF(iq_united);
|
||||
break;
|
||||
case t_iq1:
|
||||
iq_final = _IQtoIQ1(iq_united);
|
||||
break;
|
||||
case t_iq2:
|
||||
iq_final = _IQtoIQ2(iq_united);
|
||||
break;
|
||||
case t_iq3:
|
||||
iq_final = _IQtoIQ3(iq_united);
|
||||
break;
|
||||
case t_iq4:
|
||||
iq_final = _IQtoIQ4(iq_united);
|
||||
break;
|
||||
case t_iq5:
|
||||
iq_final = _IQtoIQ5(iq_united);
|
||||
break;
|
||||
case t_iq6:
|
||||
iq_final = _IQtoIQ6(iq_united);
|
||||
break;
|
||||
case t_iq7:
|
||||
iq_final = _IQtoIQ7(iq_united);
|
||||
break;
|
||||
case t_iq8:
|
||||
iq_final = _IQtoIQ8(iq_united);
|
||||
break;
|
||||
case t_iq9:
|
||||
iq_final = _IQtoIQ9(iq_united);
|
||||
break;
|
||||
case t_iq10:
|
||||
iq_final = _IQtoIQ10(iq_united);
|
||||
break;
|
||||
case t_iq11:
|
||||
iq_final = _IQtoIQ11(iq_united);
|
||||
break;
|
||||
case t_iq12:
|
||||
iq_final = _IQtoIQ12(iq_united);
|
||||
break;
|
||||
case t_iq13:
|
||||
iq_final = _IQtoIQ13(iq_united);
|
||||
break;
|
||||
case t_iq14:
|
||||
iq_final = _IQtoIQ14(iq_united);
|
||||
break;
|
||||
case t_iq15:
|
||||
iq_final = _IQtoIQ15(iq_united);
|
||||
break;
|
||||
case t_iq16:
|
||||
iq_final = _IQtoIQ16(iq_united);
|
||||
break;
|
||||
case t_iq17:
|
||||
iq_final = _IQtoIQ17(iq_united);
|
||||
break;
|
||||
case t_iq18:
|
||||
iq_final = _IQtoIQ18(iq_united);
|
||||
break;
|
||||
case t_iq19:
|
||||
iq_final = _IQtoIQ19(iq_united);
|
||||
break;
|
||||
case t_iq20:
|
||||
iq_final = _IQtoIQ20(iq_united);
|
||||
break;
|
||||
case t_iq21:
|
||||
iq_final = _IQtoIQ21(iq_united);
|
||||
break;
|
||||
case t_iq22:
|
||||
iq_final = _IQtoIQ22(iq_united);
|
||||
break;
|
||||
case t_iq23:
|
||||
iq_final = _IQtoIQ23(iq_united);
|
||||
break;
|
||||
case t_iq24:
|
||||
iq_final = _IQtoIQ24(iq_united);
|
||||
break;
|
||||
case t_iq25:
|
||||
iq_final = _IQtoIQ25(iq_united);
|
||||
break;
|
||||
case t_iq26:
|
||||
iq_final = _IQtoIQ26(iq_united);
|
||||
break;
|
||||
case t_iq27:
|
||||
iq_final = _IQtoIQ27(iq_united);
|
||||
break;
|
||||
case t_iq28:
|
||||
iq_final = _IQtoIQ28(iq_united);
|
||||
break;
|
||||
case t_iq29:
|
||||
iq_final = _IQtoIQ29(iq_united);
|
||||
break;
|
||||
case t_iq30:
|
||||
iq_final = _IQtoIQ30(iq_united);
|
||||
break;
|
||||
// Êîíâåðòàöèÿ èç GLOBAL_Q â öåëåâîé IQ (64-áèò)
|
||||
if (var->return_type == t_iq_none) {
|
||||
// Âîçâðàùàåì öåëîå, îòáðîñèâ äðîáíóþ ÷àñòü
|
||||
*ret_var = (uint32_t)(iq_united64 >> GLOBAL_Q);
|
||||
} else {
|
||||
int shift = dst_q - GLOBAL_Q;
|
||||
if (shift >= 0)
|
||||
iq_final64 = iq_united64 << shift;
|
||||
else
|
||||
iq_final64 = iq_united64 >> (-shift);
|
||||
|
||||
// Ïðîâåðÿåì ïåðåïîëíåíèå int32_t
|
||||
if (iq_final64 > 2147483647 || iq_final64 < -2147483648)
|
||||
return 3; // ïåðåïîëíåíèå
|
||||
|
||||
*ret_var = (uint32_t)iq_final64;
|
||||
}
|
||||
|
||||
*ret_var = iq_final;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
|
||||
/**
|
||||
* @brief Ïðî÷èòàòü çíà÷åíèå ïåðåìåííîé îòëàäêè.
|
||||
* @param var – óêàçàòåëü íà ñòðóêòóðó DebugVar.
|
||||
* @param int_var – óêàçàòåëü íà ïåðåìåííóþ òèïà 32 áèòà äëÿ âîçâðàòà öåëî÷èñëåííîãî çíà÷åíèÿ.
|
||||
* @param float_var – óêàçàòåëü íà ïåðåìåííóþ òèïà float äëÿ âîçâðàòà çíà÷åíèÿ ñ ïëàâàþùåé òî÷êîé.
|
||||
* @return int – 0: óñïåõ, 1: îøèáêà óêàçàòåëåé èëè íåïîääåðæèâàåìûé òèï, 3/4: îøèáêà âûðàâíèâàíèÿ.
|
||||
* @details  çàâèñèìîñòè îò òèïà ïåðåìåííîé ñ÷èòûâàåò å¸ çíà÷åíèå è ñîõðàíÿåò â ñîîòâåòñòâóþùåì óêàçàòåëå.
|
||||
*/
|
||||
static int getDebugVar(DebugVar_t *var, int32_t *int_var, float *float_var)
|
||||
{
|
||||
if (!var || !int_var || !float_var || !var->Ptr)
|
||||
return 1; // îøèáêà: null óêàçàòåëü
|
||||
|
||||
char *addr = var->Ptr;
|
||||
unsigned long addr_val = (unsigned long)addr;
|
||||
uint8_t *addr = var->Ptr;
|
||||
uint32_t addr_val = (uint32_t)addr;
|
||||
|
||||
switch (var->ptr_type)
|
||||
{
|
||||
case pt_int8: // 8 áèò
|
||||
if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||
return 1; // îøèáêà âûðàâíèâàíèÿ
|
||||
*int_var = *((volatile int8_t *)addr);
|
||||
case pt_uint8:
|
||||
// âûðàâíèâàíèå íå íóæíî äëÿ 8 áèò
|
||||
*int_var = *((volatile char *)addr);
|
||||
if ((addr_val & ALIGN_8BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||
return 1; // îøèáêà âûðàâíèâàíèÿ
|
||||
*int_var = *((volatile uint8_t *)addr);
|
||||
break;
|
||||
|
||||
case pt_int16: // 16 áèò (int)
|
||||
case pt_uint16:
|
||||
if (addr_val & 0x1) // ïðîâåðêà âûðàâíèâàíèÿ ïî 2 áàéòàì
|
||||
if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||
return 2; // îøèáêà âûðàâíèâàíèÿ
|
||||
*int_var = *((volatile int *)addr);
|
||||
*int_var = *((volatile int16_t *)addr);
|
||||
case pt_uint16:
|
||||
if ((addr_val & ALIGN_16BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||
return 2; // îøèáêà âûðàâíèâàíèÿ
|
||||
*int_var = *((volatile uint16_t *)addr);
|
||||
break;
|
||||
|
||||
case pt_int32: // 32 áèò (long)
|
||||
case pt_uint32:
|
||||
if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 áàéòàì
|
||||
case pt_int32: // 32 áèò
|
||||
if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||
return 3; // îøèáêà âûðàâíèâàíèÿ
|
||||
*int_var = *((volatile long *)addr);
|
||||
*int_var = *((volatile int32_t *)addr);
|
||||
case pt_uint32:
|
||||
if ((addr_val & ALIGN_32BIT) != 0) // ïðîâåðÿåì âûðàâíèâàíèå
|
||||
return 3; // îøèáêà âûðàâíèâàíèÿ
|
||||
*int_var = *((volatile uint32_t *)addr);
|
||||
break;
|
||||
|
||||
// case pt_int64: // 64 áèò (long long)
|
||||
// case pt_uint64:
|
||||
// if (addr_val & 0x7) // ïðîâåðêà âûðàâíèâàíèÿ ïî 8 áàéòàì
|
||||
// return 2; // îøèáêà âûðàâíèâàíèÿ
|
||||
// // Òóò ïðîñòî ÷èòàåì, íî long long ìîæåò íå ïîìåñòèòüñÿ â *int_var
|
||||
// // Ìîæíî çàìåíèòü ëîãèêó ïîä 64-áèòíîå ÷òåíèå ïðè íåîáõîäèìîñòè
|
||||
// *int_var = *((volatile long long *)addr);
|
||||
// break;
|
||||
|
||||
case pt_float: // float (4 áàéòà)
|
||||
if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 áàéòàì
|
||||
if ((addr_val & ALIGN_FLOAT) != 0) // ïðîâåðêà âûðàâíèâàíèÿ
|
||||
return 4; // îøèáêà âûðàâíèâàíèÿ
|
||||
*float_var = *((volatile float *)addr);
|
||||
break;
|
||||
@@ -401,3 +305,4 @@ static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
|
||||
|
||||
return 0; // óñïåõ
|
||||
}
|
||||
|
||||
|
||||
112
debug_tools.h
112
debug_tools.h
@@ -1,8 +1,40 @@
|
||||
#ifndef DEBUG_TOOLS
|
||||
#define DEBUG_TOOLS
|
||||
#include "IQmathLib.h"
|
||||
#include "DSP281x_Device.h"
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
|
||||
|
||||
|
||||
#if UINT8_MAX // Åñëè åñòü òèï 8 áèò - çí÷à÷èò àäðåñàöèÿ ïî 8 áèò
|
||||
|
||||
#define ALIGN_8BIT 0x0 ///< Âûðàâíèâàíèå áåç îãðàíè÷åíèé (ëþáîé àäðåñ)
|
||||
#define ALIGN_16BIT 0x1 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 2 (addr & 0x1 == 0)
|
||||
#define ALIGN_32BIT 0x3 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 4 (addr & 0x3 == 0)
|
||||
#define ALIGN_64BIT 0x7 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 8 (addr & 0x7 == 0)
|
||||
#define ALIGN_FLOAT ALIGN_32BIT
|
||||
|
||||
#else // Åñëè íåò òèïà 8 áèò - çíà÷èò àäðåñàöèÿ ïî 16 áèò
|
||||
|
||||
#define ALIGN_8BIT 0x0 ///< Âûðàâíèâàíèå áåç îãðàíè÷åíèé (ëþáîé àäðåñ)
|
||||
#define ALIGN_16BIT 0x0 ///< Âûðàâíèâàíèå áåç îãðàíè÷åíèé (ëþáîé àäðåñ)
|
||||
#define ALIGN_32BIT 0x1 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 4 (addr & 0x1 == 0)
|
||||
#define ALIGN_64BIT 0x3 ///< Âûðàâíèâàíèå: àäðåñ äîëæåí áûòü êðàòåí 8 (addr & 0x3 == 0)
|
||||
#define ALIGN_FLOAT ALIGN_32BIT
|
||||
|
||||
#endif //STM32/TMS32
|
||||
|
||||
#if !UINT8_MAX
|
||||
typedef unsigned char uint8_t;
|
||||
typedef signed char int8_t;
|
||||
#endif
|
||||
#if !defined(NULL)
|
||||
#define NULL 0
|
||||
#endif
|
||||
|
||||
|
||||
/**
|
||||
* @brief Òèï äàííûõ, íà êîòîðûé óêàçûâàåò óêàçàòåëü ïåðåìåííîé îòëàäêè.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
pt_unknown, // unknown
|
||||
@@ -14,7 +46,7 @@ typedef enum
|
||||
pt_uint16, // unsigned int
|
||||
pt_uint32, // unsigned long
|
||||
pt_uint64, // unsigned long
|
||||
pt_float, // float
|
||||
pt_float, // floatf
|
||||
pt_struct, // struct
|
||||
pt_union, // struct
|
||||
// pt_ptr_int8, // signed char*
|
||||
@@ -31,6 +63,9 @@ typedef enum
|
||||
// pt_arr_uint32, // unsigned long[]
|
||||
}DebugVarPtrType_t;
|
||||
|
||||
/**
|
||||
* @brief Òèïû IQ-ïðåäñòàâëåíèÿ ïåðåìåííîé îòëàäêè.
|
||||
*/
|
||||
typedef enum
|
||||
{
|
||||
t_iq_none,
|
||||
@@ -67,34 +102,67 @@ typedef enum
|
||||
t_iq30
|
||||
}DebugVarIQType_t;
|
||||
|
||||
typedef char DebugVarName_t[11]; ///< Èìÿ ïåðåìåííîé îòëàäêè (äî 10 ñèìâîëîâ + \0)
|
||||
|
||||
/**
|
||||
* @brief Îïèñàíèå ïåðåìåííîé îòëàäêè.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
char* Ptr;
|
||||
DebugVarPtrType_t ptr_type;
|
||||
DebugVarIQType_t iq_type;
|
||||
DebugVarIQType_t return_type;
|
||||
char name[11]; // 10 ñèìâîëîâ + '\0'
|
||||
}DebugVar_t;
|
||||
uint8_t* Ptr; ///< Óêàçàòåëü íà çíà÷åíèå ïåðåìåííîé
|
||||
DebugVarPtrType_t ptr_type; ///< Òèï çíà÷åíèÿ
|
||||
DebugVarIQType_t iq_type; ///< Òèï IQ ïåðåìåííîé (åñëè åñòü)
|
||||
DebugVarIQType_t return_type;///< Òèï IQ âîçâðàùàåìîãî çíà÷åíèÿ
|
||||
DebugVarName_t name; ///< Èìÿ ïåðåìåííîé
|
||||
} DebugVar_t;
|
||||
|
||||
typedef long DebugValue_t;
|
||||
|
||||
/**
|
||||
* @brief Ñòðóêòóðà äàòû è âðåìåíè.
|
||||
*/
|
||||
typedef struct {
|
||||
int year;
|
||||
char month;
|
||||
char day;
|
||||
char hour;
|
||||
char minute;
|
||||
} DateTimeHex;
|
||||
uint16_t year; ///< Ãîä (íàïðèìåð, 2025)
|
||||
uint8_t month; ///< Ìåñÿö (1-12)
|
||||
uint8_t day; ///< Äåíü (1-31)
|
||||
uint8_t hour; ///< ×àñû (0-23)
|
||||
uint8_t minute; ///< Ìèíóòû (0-59)
|
||||
} DateTime_t;
|
||||
|
||||
/**
|
||||
* @brief Ñòðóêòóðà íèæíåãî óðîâíÿ îòëàäêè.
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
DateTime_t build_date; ///< Äàòà ñáîðêè
|
||||
unsigned int isVerified; ///< Ôëàã èíèöèàëèçàöèè íèçêîóðîâíåíîé îòëàäêè (0 — íåò, 1 — óñïåøíî)
|
||||
DebugVar_t dbg_var; ///< Ïåðåìåííàÿ äëÿ îòëàäêè
|
||||
}DebugLowLevel_t;
|
||||
extern DebugLowLevel_t debug_ll; ///< Ãëîáàëüíûé ýêçåìïëÿð îòëàäêè íèæíåãî óðîâíÿ
|
||||
|
||||
|
||||
extern int DebugVar_Qnt;
|
||||
extern DebugVar_t dbg_vars[];
|
||||
/** @brief Ìàêðîñ èíèöèàëèçàöèè äàòû */
|
||||
#define DATE_INIT {BUILD_YEAR, BUILD_MONTH, BUILD_DATA, BUILD_HOURS, BUILD_MINUTES}
|
||||
/** @brief Ìàêðîñ èíèöèàëèçàöèè ïåðåìåííîé îòëàäêè */
|
||||
#define DEBUG_VAR_INIT {0, pt_uint16, t_iq_none, t_iq_none, "\0"}
|
||||
/** @brief Ìàêðîñ èíèöèàëèçàöèè íèæíåãî óðîâíÿ îòëàäêè */
|
||||
#define DEBUG_LOWLEVEL_INIT {DATE_INIT, 0, DEBUG_VAR_INIT}
|
||||
|
||||
|
||||
int Debug_Test_Example(void);
|
||||
extern int DebugVar_Qnt; ///< Êîëè÷åñòâî ïåðåìåííûõ îòëàäêè
|
||||
extern DebugVar_t dbg_vars[]; ///< Ìàññèâ ïåðåìåííûõ îòëàäêè
|
||||
|
||||
int Debug_LowLevel_ReadVar(DebugVar_t *var_ll, long *return_long);
|
||||
int Debug_ReadVar(DebugVar_t *var, long *return_long);
|
||||
int Debug_ReadVarName(DebugVar_t *var, char *name_ptr);
|
||||
int Debug_LowLevel_Initialize(const char* external_date);
|
||||
|
||||
/* Ïðèìåð èñïîëüçîâàíèÿ îòëàäêè */
|
||||
void Debug_Test_Example(void);
|
||||
|
||||
/* ×èòàåò çíà÷åíèå ïåðåìåííîé ïî èíäåêñó */
|
||||
int Debug_ReadVar(int var_ind, int32_t *return_long);
|
||||
/* ×èòàåò èìÿ ïåðåìåííîé ïî èíäåêñó */
|
||||
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr);
|
||||
/* ×èòàåò çíà÷åíèå ïåðåìåííîé ñ íèæíåãî óðîâíÿ */
|
||||
int Debug_LowLevel_ReadVar(int32_t *return_long);
|
||||
/* Èíèöèàëèçèðóåò îòëàäêó íèæíåãî óðîâíÿ */
|
||||
int Debug_LowLevel_Initialize(DateTime_t *external_date);
|
||||
|
||||
#endif //DEBUG_TOOLS
|
||||
|
||||
337
debug_vars.c
337
debug_vars.c
@@ -1,337 +0,0 @@
|
||||
// Ýòîò ôàéë ñãåíåðèðîâàí àâòîìàòè÷åñêè
|
||||
#include "debug_tools.h"
|
||||
|
||||
|
||||
// Èíêëþäû äëÿ äîñòóïà ê ïåðåìåííûì
|
||||
#include "vector.h"
|
||||
#include "RS_Functions_modbus.h"
|
||||
#include "f281xpwm.h"
|
||||
#include "xp_project.h"
|
||||
#include "adc_tools.h"
|
||||
#include "errors.h"
|
||||
#include "xp_write_xpwm_time.h"
|
||||
#include "log_can.h"
|
||||
#include "pwm_vector_regul.h"
|
||||
#include "rotation_speed.h"
|
||||
#include "teta_calc.h"
|
||||
#include "v_pwm24.h"
|
||||
#include "dq_to_alphabeta_cos.h"
|
||||
#include "RS_Functions.h"
|
||||
#include "x_serial_bus.h"
|
||||
#include "x_parallel_bus.h"
|
||||
#include "Spartan2E_Functions.h"
|
||||
#include "xp_controller.h"
|
||||
#include "xp_rotation_sensor.h"
|
||||
#include "xPeriphSP6_loader.h"
|
||||
#include "detect_phase_break2.h"
|
||||
#include "CRC_Functions.h"
|
||||
#include "CAN_Setup.h"
|
||||
#include "log_params.h"
|
||||
#include "log_to_memory.h"
|
||||
#include "global_time.h"
|
||||
#include "pid_reg3.h"
|
||||
#include "svgen_dq.h"
|
||||
#include "IQmathLib.h"
|
||||
#include "doors_control.h"
|
||||
#include "isolation.h"
|
||||
#include "main22220.h"
|
||||
#include "optical_bus.h"
|
||||
#include "alarm_log_can.h"
|
||||
#include "bender.h"
|
||||
#include "can_watercool.h"
|
||||
#include "detect_phase_break.h"
|
||||
#include "modbus_read_table.h"
|
||||
#include "rmp_cntl_my1.h"
|
||||
|
||||
|
||||
// Ýêñòåðíû äëÿ äîñòóïà ê ïåðåìåííûì
|
||||
extern int ADC0finishAddr;
|
||||
extern int ADC0startAddr;
|
||||
extern int ADC1finishAddr;
|
||||
extern int ADC1startAddr;
|
||||
extern int ADC2finishAddr;
|
||||
extern int ADC2startAddr;
|
||||
extern int ADC_f[2][16];
|
||||
extern int ADC_sf[2][16];
|
||||
extern int ADDR_FOR_ALL;
|
||||
extern int BUSY;
|
||||
extern BENDER Bender[2];
|
||||
extern int CAN_answer_wait[32];
|
||||
extern int CAN_count_cycle_input_units[8];
|
||||
extern int CAN_no_answer[32];
|
||||
extern int CAN_refresh_cicle[32];
|
||||
extern int CAN_request_sent[32];
|
||||
extern int CAN_timeout[32];
|
||||
extern int CAN_timeout_cicle[32];
|
||||
extern int CNTRL_ADDR;
|
||||
extern const int CNTRL_ADDR_UNIVERSAL;
|
||||
extern _iq CONST_15;
|
||||
extern _iq CONST_23;
|
||||
extern int CanOpenUnites[30];
|
||||
extern int CanTimeOutErrorTR;
|
||||
extern XControll_reg Controll;
|
||||
extern int Dpwm;
|
||||
extern int Dpwm2;
|
||||
extern int Dpwm4;
|
||||
extern int EvaTimer1InterruptCount;
|
||||
extern int EvaTimer2InterruptCount;
|
||||
extern int EvbTimer3InterruptCount;
|
||||
extern int EvbTimer4InterruptCount;
|
||||
extern int Fpwm;
|
||||
extern int IN0finishAddr;
|
||||
extern int IN0startAddr;
|
||||
extern int IN1finishAddr;
|
||||
extern int IN1startAddr;
|
||||
extern int IN2finishAddr;
|
||||
extern int IN2startAddr;
|
||||
extern float IQ_OUT_NOM;
|
||||
extern long I_OUT_1_6_NOMINAL_IQ;
|
||||
extern long I_OUT_1_8_NOMINAL_IQ;
|
||||
extern float I_OUT_NOMINAL;
|
||||
extern long I_OUT_NOMINAL_IQ;
|
||||
extern long I_ZPT_NOMINAL_IQ;
|
||||
extern _iq Id_out_max_full;
|
||||
extern _iq Id_out_max_low_speed;
|
||||
extern _iq Iq_out_max;
|
||||
extern _iq Iq_out_nom;
|
||||
extern const unsigned long K_LEM_ADC[20];
|
||||
extern float KmodTerm;
|
||||
extern int ROTfinishAddr;
|
||||
extern unsigned int RS_Len[70];
|
||||
extern const unsigned int R_ADC[20];
|
||||
extern int RotPlaneStartAddr;
|
||||
extern _iq SQRT_32;
|
||||
extern int Unites[8][128];
|
||||
extern int VAR_FREQ_PWM_XTICS;
|
||||
extern int VAR_PERIOD_MAX_XTICS;
|
||||
extern int VAR_PERIOD_MIN_BR_XTICS;
|
||||
extern int VAR_PERIOD_MIN_XTICS;
|
||||
extern int Zpwm;
|
||||
extern WINDING a;
|
||||
extern volatile AddrToSent addrToSent;
|
||||
extern unsigned int adr_read_from_modbus3;
|
||||
extern ALARM_LOG_CAN alarm_log_can;
|
||||
extern ALARM_LOG_CAN_SETUP alarm_log_can_setup;
|
||||
extern ANALOG_VALUE analog;
|
||||
extern int ar_sa_all[3][6][4][7];
|
||||
extern _iq ar_tph[7];
|
||||
extern int block_size_counter_fast;
|
||||
extern int block_size_counter_slow;
|
||||
extern _iq break_result_1;
|
||||
extern _iq break_result_2;
|
||||
extern _iq break_result_3;
|
||||
extern _iq break_result_4;
|
||||
extern Byte byte;
|
||||
extern long c_s;
|
||||
extern int calibration1;
|
||||
extern int calibration2;
|
||||
extern test_functions callfunc;
|
||||
extern CANOPEN_CAN_SETUP canopen_can_setup;
|
||||
extern unsigned int capnum0;
|
||||
extern unsigned int capnum1;
|
||||
extern unsigned int capnum2;
|
||||
extern unsigned int capnum3;
|
||||
extern unsigned int chNum;
|
||||
extern BREAK_PHASE_I chanell1;
|
||||
extern BREAK_PHASE_I chanell2;
|
||||
extern int cmd_3_or_16;
|
||||
extern int compress_size;
|
||||
extern ControlReg controlReg;
|
||||
extern COS_FI_STRUCT cos_fi;
|
||||
extern unsigned int count_error_sync;
|
||||
extern int count_modbus_table_changed;
|
||||
extern int count_run_pch;
|
||||
extern WORD crc_16_tab[256];
|
||||
extern char crypt[34];
|
||||
extern int cur_position_buf_modbus16_can;
|
||||
extern CYCLE cycle[32];
|
||||
extern int delta_capnum;
|
||||
extern int delta_error;
|
||||
extern volatile DOORS_STATUS doors;
|
||||
extern int enable_can;
|
||||
extern int enable_can_recive_after_units_box;
|
||||
extern _iq err_level_adc;
|
||||
extern _iq err_level_adc_on_go;
|
||||
extern unsigned int err_main;
|
||||
extern int err_modbus16;
|
||||
extern int err_modbus3;
|
||||
extern ERRORS errors;
|
||||
extern FLAG f;
|
||||
extern volatile int fail;
|
||||
extern FAULTS faults;
|
||||
extern FIFO fifo;
|
||||
extern ANALOG_VALUE filter;
|
||||
extern int flag_buf;
|
||||
extern int flag_enable_can_from_mpu;
|
||||
extern int flag_enable_can_from_terminal;
|
||||
extern int flag_on_off_pch;
|
||||
extern unsigned int flag_received_first_mess_from_MPU;
|
||||
extern unsigned int flag_reverse;
|
||||
extern unsigned int flag_send_answer_rs;
|
||||
extern int flag_test_tabe_filled;
|
||||
extern int flag_we_int_pwm_on;
|
||||
extern _iq freq1;
|
||||
extern float freqTerm;
|
||||
extern GLOBAL_TIME global_time;
|
||||
extern int hb_logs_data;
|
||||
extern int i;
|
||||
extern BREAK2_PHASE i1_out;
|
||||
extern BREAK2_PHASE i2_out;
|
||||
extern int init_log[3];
|
||||
extern _iq19 iq19_k_norm_ADC[20];
|
||||
extern _iq19 iq19_zero_ADC[20];
|
||||
extern _iq iq_alfa_coef;
|
||||
extern _iq iq_k_norm_ADC[20];
|
||||
extern IQ_LOGSPARAMS iq_logpar;
|
||||
extern _iq iq_max;
|
||||
extern _iq iq_norm_ADC[20];
|
||||
extern ISOLATION isolation1;
|
||||
extern ISOLATION isolation2;
|
||||
extern _iq k1;
|
||||
extern float kI_D;
|
||||
extern float kI_D_Inv31;
|
||||
extern float kI_Q;
|
||||
extern float kI_Q_Inv31;
|
||||
extern float kP_D;
|
||||
extern float kP_D_Inv31;
|
||||
extern float kP_Q;
|
||||
extern float kP_Q_Inv31;
|
||||
extern _iq koef_Base_stop_run;
|
||||
extern _iq koef_Iabc_filter;
|
||||
extern _iq koef_Im_filter;
|
||||
extern _iq koef_Im_filter_long;
|
||||
extern _iq koef_K_stop_run;
|
||||
extern _iq koef_Krecup;
|
||||
extern _iq koef_Min_recup;
|
||||
extern _iq koef_TemperBSU_long_filter;
|
||||
extern _iq koef_Ud_fast_filter;
|
||||
extern _iq koef_Ud_long_filter;
|
||||
extern _iq koef_Wlong;
|
||||
extern _iq koef_Wout_filter;
|
||||
extern _iq koef_Wout_filter_long;
|
||||
extern long koeff_Fs_filter;
|
||||
extern long koeff_Idq_filter;
|
||||
extern _iq koeff_Iq_filter;
|
||||
extern long koeff_Iq_filter_slow;
|
||||
extern long koeff_Ud_filter;
|
||||
extern long koeff_Uq_filter;
|
||||
extern volatile unsigned long length;
|
||||
extern _iq level_on_off_break[13][2];
|
||||
extern logcan_TypeDef log_can;
|
||||
extern LOG_CAN_SETUP log_can_setup;
|
||||
extern TYPE_LOG_PARAMS log_params;
|
||||
extern long logbuf_sync1[10];
|
||||
extern LOGSPARAMS logpar;
|
||||
extern int m_PWM;
|
||||
extern MAILBOXS_CAN_SETUP mailboxs_can_setup;
|
||||
extern int manufactorerAndProductID;
|
||||
extern MODBUS_REG_STRUCT * modbus_table_can_in;
|
||||
extern MODBUS_REG_STRUCT * modbus_table_can_out;
|
||||
extern MODBUS_REG_STRUCT modbus_table_in[450];
|
||||
extern MODBUS_REG_STRUCT modbus_table_out[450];
|
||||
extern MODBUS_REG_STRUCT * modbus_table_rs_in;
|
||||
extern MODBUS_REG_STRUCT * modbus_table_rs_out;
|
||||
extern MODBUS_REG_STRUCT modbus_table_test[450];
|
||||
extern MPU_CAN_SETUP mpu_can_setup;
|
||||
extern NEW_CYCLE_FIFO new_cycle_fifo;
|
||||
extern int no_write;
|
||||
extern int no_write_slow;
|
||||
extern int number_modbus_table_changed;
|
||||
extern OPTICAL_BUS_DATA optical_read_data;
|
||||
extern OPTICAL_BUS_DATA optical_write_data;
|
||||
extern MODBUS_REG_STRUCT options_controller[200];
|
||||
extern _iq pidCur_Ki;
|
||||
extern PIDREG3 pidD;
|
||||
extern PIDREG3 pidD2;
|
||||
extern PIDREG3 pidFvect;
|
||||
extern int pidFvectKi_test;
|
||||
extern int pidFvectKp_test;
|
||||
extern PIDREG3 pidPvect;
|
||||
extern PIDREG3 pidQ;
|
||||
extern PIDREG3 pidQ2;
|
||||
extern PIDREG_KOEFFICIENTS pidReg_koeffs;
|
||||
extern PIDREG3 pidTetta;
|
||||
extern POWER_RATIO power_ratio;
|
||||
extern int prev_flag_buf;
|
||||
extern unsigned int prev_status_received;
|
||||
extern T_project project;
|
||||
extern PWMGEND pwmd;
|
||||
extern T_controller_read r_c_sbus;
|
||||
extern T_controller_read r_controller;
|
||||
extern FIFO refo;
|
||||
extern TMS_TO_TERMINAL_STRUCT reply;
|
||||
extern TMS_TO_TERMINAL_TEST_ALL_STRUCT reply_test_all;
|
||||
extern RMP_MY1 rmp_freq;
|
||||
extern RMP_MY1 rmp_wrot;
|
||||
extern T_rotation_sensor rotation_sensor;
|
||||
extern ROTOR_VALUE rotor;
|
||||
extern RS_DATA_STRUCT rs_a;
|
||||
extern RS_DATA_STRUCT rs_b;
|
||||
extern unsigned int sincronisationFault;
|
||||
extern char size_cmd15;
|
||||
extern char size_cmd16;
|
||||
extern int size_fast_done;
|
||||
extern int size_slow_done;
|
||||
extern int stop_log;
|
||||
extern int stop_log_slow;
|
||||
extern SVGENDQ svgen_dq_1;
|
||||
extern SVGENDQ svgen_dq_2;
|
||||
extern SVGEN_PWM24 svgen_pwm24_1;
|
||||
extern SVGEN_PWM24 svgen_pwm24_2;
|
||||
extern unsigned int temp;
|
||||
extern _iq temperature_limit_koeff;
|
||||
extern INVERTER_TEMPERATURES temperature_warning_BI1;
|
||||
extern INVERTER_TEMPERATURES temperature_warning_BI2;
|
||||
extern RECTIFIER_TEMPERATURES temperature_warning_BV1;
|
||||
extern RECTIFIER_TEMPERATURES temperature_warning_BV2;
|
||||
extern TERMINAL_CAN_SETUP terminal_can_setup;
|
||||
extern TETTA_CALC tetta_calc;
|
||||
extern int timCNT_alg;
|
||||
extern int timCNT_prev;
|
||||
extern unsigned int time;
|
||||
extern float time_alg;
|
||||
extern long time_pause_enable_can_from_mpu;
|
||||
extern long time_pause_enable_can_from_terminal;
|
||||
extern int time_pause_logs;
|
||||
extern int time_pause_titles;
|
||||
extern volatile int tryNumb;
|
||||
extern UNITES_CAN_SETUP unites_can_setup;
|
||||
extern VECTOR_CONTROL vect_control;
|
||||
extern WaterCooler water_cooler;
|
||||
extern _iq winding_displacement;
|
||||
extern Word word;
|
||||
extern WordReversed wordReversed;
|
||||
extern WordToReverse wordToReverse;
|
||||
extern X_PARALLEL_BUS x_parallel_bus_project;
|
||||
extern X_SERIAL_BUS x_serial_bus_project;
|
||||
extern unsigned int xeeprom_controll_fast;
|
||||
extern unsigned int xeeprom_controll_store;
|
||||
extern XPWM_TIME xpwm_time;
|
||||
extern _iq zadan_Id_min;
|
||||
extern int zero_ADC[20];
|
||||
|
||||
|
||||
// Îïðåäåëåíèå ìàññèâà ñ óêàçàòåëÿìè íà ïåðåìåííûå äëÿ îòëàäêè
|
||||
int DebugVar_Qnt = 19;
|
||||
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
|
||||
DebugVar_t dbg_vars[] = {\
|
||||
{(char *)&ADC0finishAddr, pt_int16, t_iq_none, t_iq_none, "ADC0EndAdr" }, \
|
||||
{(char *)&ADC_f[0][0], pt_int16, t_iq_none, t_iq_none, "ADC_f00" }, \
|
||||
{(char *)&ADC_f[0][1], pt_int16, t_iq_none, t_iq_none, "ADC_f01" }, \
|
||||
{(char *)&ADC_f[0][2], pt_int16, t_iq_none, t_iq_none, "ADC_f02" }, \
|
||||
{(char *)&ADC_f[0][3], pt_int16, t_iq_none, t_iq_none, "ADC_f03" }, \
|
||||
{(char *)&ADC_f[0][4], pt_int16, t_iq_none, t_iq_none, "ADC_f04" }, \
|
||||
{(char *)&ADC_f[0][5], pt_int16, t_iq_none, t_iq_none, "ADC_f05" }, \
|
||||
{(char *)&ADC_f[0][6], pt_int16, t_iq_none, t_iq_none, "ADC_f06" }, \
|
||||
{(char *)&ADC_f[0][7], pt_int16, t_iq, t_iq_none, "ADC_f07" }, \
|
||||
{(char *)&ADC_f[0][8], pt_int16, t_iq_none, t_iq_none, "ADC_f08" }, \
|
||||
{(char *)&ADC_f[0][9], pt_int16, t_iq_none, t_iq_none, "ADC_f09" }, \
|
||||
{(char *)&ADC_f[0][10], pt_int16, t_iq_none, t_iq_none, "ADC_f010" }, \
|
||||
{(char *)&ADC_f[0][11], pt_int16, t_iq_none, t_iq_none, "ADC_f011" }, \
|
||||
{(char *)&ADC_f[0][12], pt_int16, t_iq_none, t_iq_none, "ADC_f012" }, \
|
||||
{(char *)&ADC_f[0][13], pt_int16, t_iq_none, t_iq_none, "ADC_f013" }, \
|
||||
{(char *)&ADC_f[0][14], pt_int16, t_iq_none, t_iq_none, "ADC_f014" }, \
|
||||
{(char *)&ADC_f[0][15], pt_int16, t_iq_none, t_iq_none, "ADC_f015" }, \
|
||||
{(char *)&project.cds_tk[2].read.sbus.mask_protect_tk.all, pt_uint16, t_iq_none, t_iq_none, "tk2_ackcur" }, \
|
||||
{(char *)&project.cds_tk[1].plane_address, pt_uint16, t_iq_none, t_iq_none, "tk1_adr" }, \
|
||||
};
|
||||
23
debug_vars_example.c
Normal file
23
debug_vars_example.c
Normal file
@@ -0,0 +1,23 @@
|
||||
#include "debug_tools.h"
|
||||
|
||||
|
||||
// Инклюды для доступа к переменным
|
||||
#include "bender.h"
|
||||
|
||||
// Экстерны для доступа к переменным
|
||||
extern int ADC0finishAddr;
|
||||
|
||||
|
||||
// Определение массива с указателями на переменные для отладки
|
||||
int DebugVar_Qnt = 5;
|
||||
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
|
||||
// pointer_type iq_type return_iq_type short_name
|
||||
DebugVar_t dbg_vars[] = {\
|
||||
{(uint8_t *)&freqTerm, pt_float, t_iq_none, t_iq10, "freqT" }, \
|
||||
{(uint8_t *)&ADC_sf[0][0], pt_int16, t_iq_none, t_iq_none, "ADC_sf00" }, \
|
||||
{(uint8_t *)&ADC_sf[0][1], pt_int16, t_iq_none, t_iq_none, "ADC_sf01" }, \
|
||||
{(uint8_t *)&ADC_sf[0][2], pt_int16, t_iq_none, t_iq_none, "ADC_sf02" }, \
|
||||
{(uint8_t *)&ADC_sf[0][3], pt_int16, t_iq_none, t_iq_none, "ADC_sf03" }, \
|
||||
{(uint8_t *)&Bender[0].KOhms, pt_uint16, t_iq, t_iq10, "Bend0.KOhm" }, \
|
||||
{(uint8_t *)&Bender[0].Times, pt_uint16, t_iq_none, t_iq_none, "Bend0.Time" }, \
|
||||
};
|
||||
20181
structs.xml
20181
structs.xml
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user