17 Commits

Author SHA1 Message Date
Razvalyaev
369cfa808c чистка для релиза
переструктурировано чуть

и всякие мелкие фиксы
2025-07-15 15:37:29 +03:00
Razvalyaev
c32dc161f8 переструктурирвоаны исходники проекта 2025-07-15 13:31:18 +03:00
Razvalyaev
abfc507e4e регулярнка не работает в clean_remp 2025-07-15 13:13:08 +03:00
Razvalyaev
cb496bca0f добавлен readme
другая библиотека для xml (более быстрая)
сделан выбор элементов в выпадающем списке типов
исправлено вроде кривое выставлениеп return_type
изменено окно выбора переменых
    - кнопки добавит, удалить заменены на применить
    - исправлены кривые подсказки в .exe версии
2025-07-15 13:03:11 +03:00
Razvalyaev
7b720cbdf4 криво работает return_type, надо разобратся
чет поделано в коде для тмс
2025-07-14 17:54:49 +03:00
Razvalyaev
6428e523df сделано ленивое раскрытие подпеременных в структурах
(баово заглушки и если они раскрываются,то подставляются реальнгые)
2025-07-14 14:57:32 +03:00
Razvalyaev
c738acd871 фикс мелочей в таблицах выбора
+exe
+3.13 python installer заменен на 3.9 (3.13 не имеет PySide2)
2025-07-14 07:35:28 +03:00
Razvalyaev
05bde87c38 +exe 2025-07-13 22:53:58 +03:00
Razvalyaev
02f3124224 ну вроде чуть поменьше тормозит, но все равно неприятно. надо еще подумать 2025-07-13 22:47:33 +03:00
Razvalyaev
21082a38e0 ну вроде работает, но опять тормозит 2025-07-13 22:17:45 +03:00
Razvalyaev
42ac3eb65d наконец-то вроде сделан поиск.
есть кривости в подскасках
начата работа надо двумя таблицами (всех и выбранных переменных)
2025-07-13 18:53:01 +03:00
Razvalyaev
69c0bf1574 почему-то внуки, правнуки и так далее в поиске на находятся... 2025-07-12 18:13:51 +03:00
Razvalyaev
4f949e9854 таблица выбора элементов вынесена в отдельный класс 2025-07-12 08:54:53 +03:00
Razvalyaev
0d54031dd5 исправлен поиск и фильтрация
плюс делание всякого по коду для мк
2025-07-11 17:57:29 +03:00
Razvalyaev
e4fcfd11d7 Доработана функции для считывания переменных
Добавлены бета-функции для считывания переменны по адресу

+фиксы багов

future:
- в селекторе сделать две таблички для всех переменных и для выборанных
- по кнопке переносить переменные из всех в выбранные
- переменные из выбранных и добавлять в основную табличку
- сделать отдельный класс для таблички - который будет принимать спиоск переменных для отображения
2025-07-11 16:46:51 +03:00
Razvalyaev
d3f1e824fa оптимизировано. на первом запуске Выбора переменных конечно подвисает, но поиск работает относительно шустро 2025-07-11 10:48:52 +03:00
Razvalyaev
ad7b9126b7 массивы вроде работают но тормозит. начата работа над оптимизацией 2025-07-11 09:44:33 +03:00
24 changed files with 2796 additions and 26558 deletions

Binary file not shown.

138
README.md Normal file
View File

@@ -0,0 +1,138 @@
# 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` с переменными.
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="debugVars/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`)
- **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
```

View File

@@ -1,21 +1,21 @@
# 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
from enum import IntEnum
import threading
from scanVars import run_scan
from generateVars import run_generate
import setupVars
from VariableSelector import VariableSelectorDialog
from VariableTable import VariableTableWidget, rows
from scanVarGUI import ProcessOutputWindowDummy
import scanVars
from generate_debug_vars import run_generate
import var_setup
from var_selector_window import VariableSelectorDialog
from var_table import VariableTableWidget, rows
from scan_progress_gui import ProcessOutputWindow
import scan_vars
import myXML
import time
from PySide2.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
@@ -31,12 +31,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):
@@ -57,7 +63,7 @@ class VarEditor(QWidget):
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))
@@ -201,25 +207,10 @@ class VarEditor(QWidget):
# Создаём окно с кнопкой "Готово"
self.proc_win = ProcessOutputWindowDummy(self.__after_scanvars_finished)
self.emitting_stream = self.proc_win.emitting_stream # ключевая строка!
self.proc_win.show()
self.proc_win = ProcessOutputWindow(self.proj_path, self.makefile_path, self.xml_path,
self.__after_scan_vars_finished, self)
self.proc_win.start_scan()
def run_scan_wrapper():
try:
old_stdout = sys.stdout
sys.stdout = self.emitting_stream
run_scan(self.proj_path, self.makefile_path, self.xml_path)
except Exception as e:
self.emitting_stream.text_written.emit(f"\n[ОШИБКА] {e}")
finally:
sys.stdout = old_stdout
self.emitting_stream.text_written.emit("\n--- Анализ завершён ---")
self.proc_win.btn_close.setEnabled(True)
threading.Thread(target=run_scan_wrapper, daemon=True).start()
def save_build(self):
@@ -237,8 +228,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)
@@ -257,8 +248,8 @@ class VarEditor(QWidget):
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
@@ -311,11 +302,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}")
@@ -367,15 +358,15 @@ 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:
path = myXML.make_relative_path(file_path, self.proj_path)
else:
path = file_path
self.makefile_edit.setText(path)
self.makefile_path = path
file_path, _ = QFileDialog.getOpenFileName(
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)"
)
if file_path and self.proj_path:
path = myXML.make_relative_path(file_path, self.proj_path)
else:
path = file_path
self.makefile_edit.setText(path)
self.makefile_path = path
def __browse_source_output(self):
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
@@ -413,36 +404,35 @@ 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:
return
# Получаем имена всех выбранных переменных из первого столбца
selected_names = self.table.get_selected_var_names()
# Удаляем из vars_list те, у кого show_var == true и имя совпадает
filtered_vars = [v for v in self.vars_list if v.get('show_var', 'false') == 'true']
for row in selected_rows:
if 0 <= row < len(filtered_vars):
var_to_remove = filtered_vars[row]
for v in self.vars_list:
if v['name'] == var_to_remove['name']:
v['show_var'] = 'false'
break
if not selected_names:
return
# Меняем флаг show_var по имени
for var in self.vars_list:
if var.get('name') in selected_names:
var['show_var'] = 'false'
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
self.write_to_xml()
@@ -452,16 +442,28 @@ class VarEditor(QWidget):
if not self.vars_list:
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
return
dlg = VariableSelectorDialog(self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
self.update()
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
@@ -485,7 +487,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)
@@ -496,6 +497,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:
@@ -510,6 +512,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()}
@@ -518,59 +521,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
View 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
```

View File

@@ -1,537 +0,0 @@
import re
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 setupVars
import myXML
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
class VariableSelectorDialog(QDialog):
def __init__(self, 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.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.xml_path = xml_path # сохраняем путь к xml
# --- Добавляем чекбокс для автодополнения ---
self.autocomplete_checkbox = QCheckBox("Включить автодополнение")
self.autocomplete_checkbox.setChecked(True)
# Инициализируем QSettings с именем организации и приложения
self.settings = QSettings("SET", "DebugVarEdit_VarsSelector")
# Восстанавливаем сохранённое состояние чекбокса, если есть
checked = self.settings.value("autocomplete_enabled", True, type=bool)
self.autocomplete_checkbox.setChecked(checked)
# При изменении состояния чекбокса сохраняем его
self.autocomplete_checkbox.stateChanged.connect(self.save_checkbox_state)
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Поиск по имени переменной...")
self.search_input.textChanged.connect(self.on_search_text_changed)
self.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.populate_tree()
def add_tree_item_recursively(self, parent, var):
"""
Рекурсивно добавляет переменную и её дочерние поля в дерево.
Если parent == None, добавляет на верхний уровень.
"""
name = var['name']
type_str = var.get('type', '')
show_var = var.get('show_var', 'false') == 'true'
item = QTreeWidgetItem([name, type_str])
item.setData(0, Qt.UserRole, name)
# Делаем 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):
self.tree.clear()
for var in self.expanded_vars:
self.add_tree_item_recursively(None, var)
header = self.tree.header()
header.setSectionResizeMode(QHeaderView.Interactive) # вручную можно менять
self.tree.setColumnWidth(0, 400)
self.tree.resizeColumnToContents(1)
""" header.setSectionResizeMode(0, QHeaderView.Stretch)
header.setSectionResizeMode(1, QHeaderView.ResizeToContents) """
def filter_tree(self):
text = self.search_input.text().strip().lower()
path_parts = self.split_path(text) if text else []
def hide_all(item):
item.setHidden(True)
for i in range(item.childCount()):
hide_all(item.child(i))
def path_matches_search(name, search_parts):
name_parts = self.split_path(name.lower())
if len(name_parts) < len(search_parts):
return False
for sp, np in zip(search_parts, name_parts):
if not np.startswith(sp):
return False
return True
def show_matching_path(item, level=0):
name = item.text(0).lower()
# По умолчанию не совпадает
matched = False
# Проверяем путь
if not path_parts:
matched = True
elif path_matches_search(name, path_parts[:level+1]):
matched = True
# Исключаем "плоские" элементы на верхнем уровне при глубоком поиске
if level == 0 and item.childCount() == 0 and len(path_parts) > 1:
matched = False
item.setHidden(not matched)
# Раскрываем узел, если он соответствует и ещё есть путь вниз
if matched and level < len(path_parts) - 1:
item.setExpanded(True)
else:
item.setExpanded(False)
matched_any_child = False
for i in range(item.childCount()):
child = item.child(i)
if show_matching_path(child, level + 1):
matched_any_child = True
return matched or matched_any_child
# Если в поиске нет точки — особая логика для первого уровня
if '.' not in text and '->' not in text and text != '':
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
name = item.text(0).lower()
if text in name:
item.setHidden(False)
item.setExpanded(False) # НЕ раскрываем потомков
else:
hide_all(item)
item.setHidden(True)
else:
# Обычная логика с поиском по пути
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
hide_all(item)
show_matching_path(item, 0)
def update_completions(self, text = None):
if text is None:
text = self.search_input.text().strip()
else:
text = text.strip()
parts = self.split_path(text)
path_parts = parts[:-1]
prefix = parts[-1].lower() if not text.endswith(('.', '>')) else ''
# Если путь есть (например: project.adc или project.adc.), ищем внутри него
search_deep = len(path_parts) > 0
def find_path_items(path_parts):
items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
for part in path_parts:
part_lower = part.lower()
matched = []
for item in items:
# Берём последний фрагмент имени item, разделённого точками
item_name_part = self.split_path(item.text(0))[-1].lower()
if item_name_part == part_lower:
matched.append(item)
if not matched:
return []
items = []
# Собираем детей для следующего уровня поиска
for node in matched:
for i in range(node.childCount()):
items.append(node.child(i))
return matched
if not search_deep:
# Без точки — ищем только в топ-уровне, фильтруя по prefix
items = []
for i in range(self.tree.topLevelItemCount()):
item = self.tree.topLevelItem(i)
name_part = self.split_path(item.text(0))[-1].lower()
if name_part.startswith(prefix):
items.append(item)
completions = [item.text(0) for item in items]
else:
# С точкой — углубляемся по пути и показываем имена детей
if len(path_parts) == 0:
items = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
else:
items = find_path_items(path_parts)
completions = []
for item in items:
for i in range(item.childCount()):
child = item.child(i)
name_part = self.split_path(child.text(0))[-1].lower()
if prefix == '' or name_part.startswith(prefix):
completions.append(child.text(0))
self.completer.setModel(QStringListModel(completions))
return completions
def eventFilter(self, obj, event):
if obj == self.search_input and isinstance(event, QKeyEvent):
if event.key() == Qt.Key_Space and event.modifiers() & Qt.ControlModifier:
completions = self.update_completions()
self.completer.complete()
text = self.search_input.text().strip()
if len(completions) == 1 and completions[0].lower() == text.lower():
# Найдем узел с таким именем
def find_exact_item(name):
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
while stack:
node = stack.pop()
if node.text(0).lower() == name.lower():
return node
for i in range(node.childCount()):
stack.append(node.child(i))
return None
node = find_exact_item(completions[0])
if node and node.childCount() > 0:
# Используем первую подсказку, чтобы определить нужный разделитель
completions = self.update_completions(text + '.')
suggestion = completions[0]
# Ищем, какой символ идёт после текущего текста
separator = '.'
if suggestion.startswith(text):
rest = suggestion[len(text):]
if rest.startswith('->'):
separator = '->'
elif rest.startswith('.'):
separator = '.'
self.search_input.setText(text + separator)
completions = self.update_completions()
self.completer.setModel(QStringListModel(completions))
self.completer.complete()
return True
# Иначе просто показываем подсказки
self.completer.setModel(QStringListModel(completions))
if completions:
self.completer.complete()
return True
return super().eventFilter(obj, event)
# Функция для поиска узла с полным именем
def find_node_by_fullname(self, name):
stack = [self.tree.topLevelItem(i) for i in range(self.tree.topLevelItemCount())]
while stack:
node = stack.pop()
if node.text(0).lower() == name.lower():
return node
for i in range(node.childCount()):
stack.append(node.child(i))
return None
def insert_completion(self, text):
node = self.find_node_by_fullname(text)
if node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->')):
# Определяем разделитель по имени первого ребёнка
child_name = node.child(0).text(0)
if child_name.startswith(text + '->'):
text += '->'
else:
text += '.'
self.search_input.setText(text)
self.search_input.setCursorPosition(len(text))
self.update_completions()
self.completer.complete()
else:
self.search_input.setText(text)
self.search_input.setCursorPosition(len(text))
def on_search_text_changed(self, text):
if self.autocomplete_checkbox.isChecked():
completions = self.update_completions(text)
node = self.find_node_by_fullname(text)
should_show = False
if completions:
if len(completions) > 1:
should_show = True
elif len(completions) == 1:
single_node = self.find_node_by_fullname(completions[0])
if single_node and single_node.childCount() > 0:
should_show = True
elif node and node.childCount() > 0 and not (text.endswith('.') or text.endswith('->')):
should_show = True
if should_show:
self.completer.setModel(QStringListModel(completions))
self.completer.complete()
self.filter_tree()
def on_add_clicked(self):
self.selected_names = []
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
import xml.etree.ElementTree as ET
tree = ET.parse(self.xml_path)
root = tree.getroot()
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return
for var_elem in vars_section.findall('var'):
name = var_elem.attrib.get('name')
if name in selected_names:
def set_text(tag, value):
el = var_elem.find(tag)
if el is None:
el = ET.SubElement(var_elem, tag)
el.text = value
set_text('show_var', 'false')
set_text('enable', 'false')
myXML.fwrite(root, self.xml_path)
self.populate_tree()
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:
return
# Проверка пути к 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
tree = ET.parse(self.xml_path)
root = tree.getroot()
if root is None:
return
vars_section = root.find('variables')
if vars_section is None:
return
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]
if removed_any:
myXML.fwrite(root, self.xml_path)
self.populate_tree()
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):
# Разбиваем по точке или по -> (учитываем, что -> длиной 2 символа)
return re.split(r'\.|->', path)

View File

@@ -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)

View File

@@ -1,12 +1,12 @@
# 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
@@ -147,13 +147,14 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
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_]*)\s*,\s*(pt_\w+)\s*,\s*(t?iq_\w+)\s*,\s*"([^"]+)"',
line)
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)
if m:
full_varname = m.group(1) # e.g., some.deep.var.name
pt_type = m.group(2)
iq_type = m.group(3)
shortname = m.group(4)
return_type = m.group(4)
shortname = m.group(5)
parsed_vars[full_varname] = {
'pt_type': pt_type,
@@ -161,7 +162,7 @@ def add_new_vars_to_xml(proj_path, xml_rel_path, output_path):
'enable': True,
'show_var': True,
'shortname': shortname,
'return_type': 'int',
'return_type': return_type,
'type': '', # Можешь дополнить из externs
'file': '', # Можешь дополнить из externs
'extern': False,
@@ -330,8 +331,7 @@ def generate_vars_file(proj_path, xml_path, output_dir):
if not pt_type:
pt_type = map_type_to_pt(vtype, vname)
ret_type = info.get('pt_type')
ret_type = info.get('return_type')
if not ret_type:
pt_type = 't_iq_none'
@@ -348,7 +348,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'{{(char *)&{f_name:<57} {f_type:<15} {f_iq:<15} {f_ret_iq:<15} {f_short_name:<21}}}, \\{comment_str}'
new_debug_vars[vname] = line
else:

View File

@@ -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:

View File

@@ -1,125 +0,0 @@
import sys
import re
from VariableTable import VariableTableWidget, rows
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, QProgressBar
)
from PySide2.QtGui import QTextCursor, QKeyEvent
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QSettings
class EmittingStream(QObject):
text_written = Signal(str)
progress_updated = Signal(str, int, int) # bar_name, current, total
def __init__(self):
super().__init__()
self._buffer = ""
self._current_bar_name = None
def write(self, text):
self._buffer += text
while '\n' in self._buffer:
line, self._buffer = self._buffer.split('\n', 1)
if line.startswith('Progress: "') and line.endswith('"'):
bar_name = self._extract_bar_name(line)
if bar_name:
self._current_bar_name = bar_name
continue # не выводим строку
elif self._is_progress_line(line):
current, total = self._extract_progress(line)
if current is not None and total is not None:
name = self._current_bar_name if self._current_bar_name else "Progress"
self.progress_updated.emit(name, current, total)
continue # не выводим строку
# если не прогресс и не имя — выводим
self.text_written.emit(line)
def _extract_bar_name(self, line):
# точно обрезаем вручную
prefix = 'Progress: "'
suffix = '"'
if line.startswith(prefix) and line.endswith(suffix):
return line[len(prefix):-1]
return None
def _is_progress_line(self, line):
return re.match(r'^Progress:\s*\d+\s*/\s*\d+$', line) is not None
def _extract_progress(self, line):
match = re.match(r'^Progress:\s*(\d+)\s*/\s*(\d+)$', line)
if match:
return int(match.group(1)), int(match.group(2))
return None, None
def flush(self):
if self._buffer:
line = self._buffer.strip()
if not self._is_progress_line(line) and not self._extract_bar_name(line):
self.text_written.emit(line)
self._buffer = ""
class ProcessOutputWindowDummy(QDialog):
def __init__(self, on_done_callback):
super().__init__()
self.setWindowTitle("Поиск переменных...")
self.resize(600, 480)
self.setModal(True)
self.layout = QVBoxLayout(self)
self.output_edit = QTextEdit()
self.output_edit.setReadOnly(True)
self.layout.addWidget(self.output_edit)
# Метка с именем прогрессбара
self.progress_label = QLabel("Progress:")
self.layout.addWidget(self.progress_label)
# Прогрессбар
self.progress_bar = QProgressBar()
self.progress_bar.setMinimum(0)
self.progress_bar.setValue(0)
self.layout.addWidget(self.progress_bar)
self.btn_close = QPushButton("Закрыть")
self.btn_close.setEnabled(False)
self.layout.addWidget(self.btn_close)
self.btn_close.clicked.connect(self.__handle_done)
self._on_done_callback = on_done_callback
self.emitting_stream = EmittingStream()
self.emitting_stream.text_written.connect(self.append_text)
self.emitting_stream.progress_updated.connect(self.update_progress)
sys.stdout = self.emitting_stream
def __handle_done(self):
sys.stdout = sys.__stdout__ # восстановить stdout
if self._on_done_callback:
self._on_done_callback()
self.accept()
def append_text(self, text):
cursor = self.output_edit.textCursor()
cursor.movePosition(QTextCursor.End)
if not text.endswith('\n'):
text += '\n'
for line in text.splitlines(True):
cursor.insertText(line)
self.output_edit.setTextCursor(cursor)
self.output_edit.ensureCursorVisible()
def update_progress(self, bar_name, current, total):
self.progress_label.setText(f"{bar_name}")
self.progress_bar.setMaximum(total)
self.progress_bar.setValue(current)

219
Src/scan_progress_gui.py Normal file
View File

@@ -0,0 +1,219 @@
import sys
import re
import multiprocessing
import sys
import contextlib
import io
import json
from scan_vars import run_scan
from var_table import VariableTableWidget, rows
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, QProgressBar
)
from PySide2.QtGui import QTextCursor, QKeyEvent
from PySide2.QtCore import Qt, QProcess, QObject, Signal, QTimer
class EmittingStream(QObject):
text_written = Signal(str)
progress_updated = Signal(str, int, int) # bar_name, current, total
def __init__(self):
super().__init__()
self._buffer = ""
self._current_bar_name = None
def write(self, text):
self._buffer += text
while '\n' in self._buffer:
line, self._buffer = self._buffer.split('\n', 1)
if line.startswith('Progress: "') and line.endswith('"'):
bar_name = self._extract_bar_name(line)
if bar_name:
self._current_bar_name = bar_name
continue # не выводим строку
elif self._is_progress_line(line):
current, total = self._extract_progress(line)
if current is not None and total is not None:
name = self._current_bar_name if self._current_bar_name else "Progress"
self.progress_updated.emit(name, current, total)
continue # не выводим строку
# если не прогресс и не имя — выводим
self.text_written.emit(line)
def _extract_bar_name(self, line):
# точно обрезаем вручную
prefix = 'Progress: "'
suffix = '"'
if line.startswith(prefix) and line.endswith(suffix):
return line[len(prefix):-1]
return None
def _is_progress_line(self, line):
return re.match(r'^Progress:\s*\d+\s*/\s*\d+$', line) is not None
def _extract_progress(self, line):
match = re.match(r'^Progress:\s*(\d+)\s*/\s*(\d+)$', line)
if match:
return int(match.group(1)), int(match.group(2))
return None, None
def flush(self):
if self._buffer:
line = self._buffer.strip()
if not self._is_progress_line(line) and not self._extract_bar_name(line):
self.text_written.emit(line)
self._buffer = ""
class ProcessOutputWindow(QDialog):
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
self.xml_path = xml_path
self._on_done_callback = on_done_callback
self.layout = QVBoxLayout(self)
self.output_edit = QTextEdit()
self.output_edit.setReadOnly(True)
self.layout.addWidget(self.output_edit)
self.progress_label = QLabel("Progress:")
self.layout.addWidget(self.progress_label)
self.progress_bar = QProgressBar()
self.progress_bar.setMinimum(0)
self.progress_bar.setValue(0)
self.layout.addWidget(self.progress_bar)
self.btn_close = QPushButton("Закрыть")
self.btn_close.setEnabled(False)
self.layout.addWidget(self.btn_close)
self.btn_close.clicked.connect(self.__handle_done)
self.queue = None
self.proc = None
def start_scan(self):
self.queue = multiprocessing.Queue()
self.proc = multiprocessing.Process(
target=run_scan_process,
args=(self.proj_path, self.makefile_path, self.xml_path, self.queue),
daemon=True)
self.proc.start()
self.timer = QTimer(self)
self.timer.timeout.connect(self.poll_queue)
self.timer.start(100)
self.show()
def poll_queue(self):
try:
while True:
msg = self.queue.get_nowait()
if msg is None:
# Конец процесса
self.btn_close.setEnabled(True)
self.append_text("\n--- Анализ завершён ---")
self.timer.stop()
return
# Пытаемся разобрать JSON-сообщение
if isinstance(msg, str) and msg.startswith("PROGRESS_MSG:"):
try:
data = json.loads(msg[len("PROGRESS_MSG:"):])
self.update_progress(data["bar_name"], data["current"], data["total"])
except Exception:
# Если не удалось распарсить, выводим как текст
self.append_text(msg)
else:
self.append_text(msg)
except Exception:
pass # Очередь пустая
def append_text(self, text):
cursor = self.output_edit.textCursor()
cursor.movePosition(QTextCursor.End)
if not text.endswith('\n'):
text += '\n'
cursor.insertText(text)
self.output_edit.setTextCursor(cursor)
self.output_edit.ensureCursorVisible()
def update_progress(self, bar_name, current, total):
self.progress_label.setText(f"{bar_name}")
self.progress_bar.setMaximum(total)
self.progress_bar.setValue(current)
def __handle_done(self):
self.close()
def closeEvent(self, event):
if self.proc and self.proc.is_alive():
self.proc.terminate()
self.proc.join()
self.btn_close.setEnabled(True)
self.append_text("Сканирование прервано.")
def run_scan_process(proj_path, makefile_path, xml_path, queue):
class QueueWriter(io.TextIOBase):
def __init__(self):
self._buffer = ""
self._current_bar_name = None
def write(self, txt):
self._buffer += txt
while '\n' in self._buffer:
line, self._buffer = self._buffer.split('\n', 1)
# Обработка прогресса
if line.startswith('Progress: "') and line.endswith('"'):
# Название прогресс-бара
bar_name = line[len('Progress: "'):-1]
self._current_bar_name = bar_name
elif re.match(r'^Progress:\s*\d+\s*/\s*\d+$', line):
m = re.match(r'^Progress:\s*(\d+)\s*/\s*(\d+)$', line)
if m:
current = int(m.group(1))
total = int(m.group(2))
bar_name = self._current_bar_name or "Progress"
# Отправляем специальное сообщение в очередь в формате JSON
msg = {
"bar_name": bar_name,
"current": current,
"total": total
}
queue.put("PROGRESS_MSG:" + json.dumps(msg))
else:
# Обычный вывод
queue.put(line)
def flush(self):
if self._buffer.strip():
queue.put(self._buffer.strip())
self._buffer = ""
sys.stdout = QueueWriter()
sys.stderr = sys.stdout
try:
run_scan(proj_path, makefile_path, xml_path)
except Exception as e:
queue.put(f"[ОШИБКА] {e}")
finally:
queue.put(None) # сигнал окончания

View File

@@ -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,12 +9,11 @@ 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_makefile
from collections import deque
import argparse
from setupVars import make_relative_path
import myXML
BITFIELD_WIDTHS = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32}
@@ -546,7 +545,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'),
@@ -582,12 +581,12 @@ def generate_xml_output(proj_path, xml_path, unique_vars, h_files_needed, vars_n
}
if makefile_path:
rel_makefile = make_relative_path(makefile_path, proj_path)
rel_makefile = myXML.make_relative_path(makefile_path, proj_path)
if not os.path.isabs(rel_makefile):
analysis_attrs["makefile_path"] = rel_makefile.replace("\\", "/")
if structs_xml_path:
rel_struct = make_relative_path(structs_xml_path, proj_path)
rel_struct = myXML.make_relative_path(structs_xml_path, proj_path)
if not os.path.isabs(rel_struct):
analysis_attrs["structs_path"] = rel_struct.replace("\\", "/")
@@ -609,7 +608,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),
@@ -628,7 +627,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("\\", "/")
@@ -919,6 +918,5 @@ def run_scan(proj_path, makefile_path, output_xml, verbose=2):
print("[XML] Creating vars.xml...")
generate_xml_output(proj_path, output_xml, vars, includes, externs, structs_xml, makefile_path)
print("Progress: 2/2")
print('Progress: "Done"')
print('Progress: 1/1')
print("Progress: 2/2")

View File

@@ -1,264 +0,0 @@
import sys
import os
import re
import xml.etree.ElementTree as ET
from generateVars import map_type_to_pt, get_iq_define, type_map
from enum import IntEnum
import scanVars
import myXML
def make_absolute_path(path, base_path):
if not os.path.isabs(path) and os.path.isdir(base_path):
try:
return os.path.abspath(os.path.join(base_path, path))
except Exception:
pass # На случай сбоя в os.path.join или abspath
elif os.path.isabs(path):
return os.path.abspath(path)
else:
return path
def make_relative_path(abs_path, base_path):
abs_path = os.path.abspath(abs_path)
base_path = os.path.abspath(base_path)
# Разбиваем на списки директорий
abs_parts = abs_path.split(os.sep)
base_parts = base_path.split(os.sep)
# Проверяем, является ли base_path настоящим префиксом пути (по папкам)
if abs_parts[:len(base_parts)] == base_parts:
rel_parts = abs_parts[len(base_parts):]
return "/".join(rel_parts)
# Иначе пробуем relpath
try:
return os.path.relpath(abs_path, base_path).replace("\\", "/")
except Exception:
return abs_path.replace("\\", "/")
def parse_vars(filename, typedef_map=None):
root, tree = myXML.safe_parse_xml(filename)
if root is None:
return []
if typedef_map is None:
typedef_map = {}
vars_list = []
variables_elem = root.find('variables')
if variables_elem is not None:
for var in variables_elem.findall('var'):
name = var.attrib.get('name', '')
var_type = var.findtext('type', 'unknown').strip()
# Вычисляем pt_type и iq_type
pt_type = var.findtext('pt_type')
if not pt_type:
pt_type = map_type_to_pt(var_type, name, typedef_map)
iq_type = var.findtext('iq_type')
if not iq_type:
iq_type = get_iq_define(var_type)
# Записываем iq_type в XML
iq_type_elem = var.find('iq_type')
if iq_type_elem is None:
iq_type_elem = ET.SubElement(var, 'iq_type')
iq_type_elem.text = iq_type
# Вычисляем pt_type и iq_type
pt_type = var.findtext('pt_type')
if not pt_type:
pt_type = map_type_to_pt(var_type, name, typedef_map)
# Записываем pt_type в XML
pt_type_elem = var.find('pt_type')
if pt_type_elem is None:
pt_type_elem = ET.SubElement(var, 'pt_type')
pt_type_elem.text = pt_type
vars_list.append({
'name': name,
'show_var': var.findtext('show_var', 'false'),
'enable': var.findtext('enable', 'false'),
'shortname': var.findtext('shortname', name),
'pt_type': pt_type,
'iq_type': iq_type,
'return_type': var.findtext('return_type', 'int'),
'type': var_type,
'file': var.findtext('file', ''),
'extern': var.findtext('extern', 'false') == 'true',
'static': var.findtext('static', 'false') == 'true',
})
myXML.fwrite(root, filename)
return vars_list
# 2. Парсим structSup.xml
def parse_structs(filename):
root, tree = myXML.safe_parse_xml(filename)
if root is None:
return {}, {}
structs = {}
typedef_map = {}
def parse_struct_element(elem):
fields = {}
for field in elem.findall("field"):
fname = field.attrib.get("name")
ftype = field.attrib.get("type", "")
# Проверка на вложенную структуру
nested_struct_elem = field.find("struct")
if nested_struct_elem is not None:
# Рекурсивно парсим вложенную структуру и вставляем её как подсловарь
nested_fields = parse_struct_element(nested_struct_elem)
# Оборачиваем в dict с ключом 'type' для хранения типа из XML
fields[fname] = {
'type': ftype, # здесь тип, например "BENDER_ERROR"
**nested_fields # развёрнутые поля вложенной структуры
}
else:
# Обычное поле
fields[fname] = ftype
return fields
structs_elem = root.find("structs")
if structs_elem is not None:
for struct in structs_elem.findall("struct"):
name = struct.attrib.get("name")
if name and name not in structs:
fields = parse_struct_element(struct)
structs[name] = fields
# typedefs без изменений
typedefs_elem = root.find("typedefs")
if typedefs_elem is not None:
for typedef in typedefs_elem.findall("typedef"):
name = typedef.attrib.get('name')
target_type = typedef.attrib.get('type')
if name and target_type:
typedef_map[name.strip()] = target_type.strip()
return structs, typedef_map
def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0):
if depth > 10:
return []
# Если type_str — словарь структуры
if isinstance(type_str, dict):
fields = type_str
else:
base_type = scanVars.strip_ptr_and_array(type_str)
fields = structs.get(base_type)
if not isinstance(fields, dict):
return []
children = []
for field_name, field_value in fields.items():
# Пропускаем поле 'type', оно служит для хранения имени типа
if field_name == 'type':
continue
# Определяем разделитель между prefix и полем
if prefix.endswith('*'):
separator = '->'
# Для красоты можно убрать пробелы у указателя
# например, если prefix="ptr*" -> "ptr->field"
full_name = f"{prefix[:-1]}{separator}{field_name}"
else:
separator = '.'
full_name = f"{prefix}{separator}{field_name}"
if isinstance(field_value, dict):
# Если вложенная структура — берем её имя типа из поля 'type' или пустую строку
type_name = field_value.get('type', '')
child = {
'name': full_name,
'type': type_name,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
if '*' in type_name and not full_name.endswith('*'):
full_name += '*'
# Рекурсивно раскрываем вложенные поля
subchildren = expand_struct_recursively(full_name, field_value, structs, typedefs, var_attrs, depth + 1)
if subchildren:
child['children'] = subchildren
else:
# Простое поле — строка типа
# Пропускаем указатели на функции
if isinstance(field_value, str) and "(" in field_value and "*" in field_value and ")" in field_value:
continue
child = {
'name': full_name,
'type': field_value,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
children.append(child)
return children
def expand_vars(vars_list, structs, typedefs):
"""
Раскрывает структуры и массивы структур в деревья.
"""
expanded = []
for var in vars_list:
pt_type = var.get('pt_type', '')
raw_type = var.get('type', '')
base_type = scanVars.strip_ptr_and_array(raw_type)
fields = structs.get(base_type)
if pt_type.startswith('pt_ptr_') and isinstance(fields, dict):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name']+'*', raw_type, structs, typedefs, var)
expanded.append(new_var)
if pt_type.startswith('pt_arr_') and isinstance(fields, dict):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
elif pt_type == 'pt_struct' and isinstance(fields, dict):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
elif pt_type == 'pt_union' and isinstance(fields, dict):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
else:
expanded.append(var)
return expanded

View File

606
Src/var_selector_table.py Normal file
View 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
View 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)

619
Src/var_setup.py Normal file
View File

@@ -0,0 +1,619 @@
import sys
import os
import re
import lxml.etree as ET
from generate_debug_vars import map_type_to_pt, get_iq_define, type_map
from enum import IntEnum
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):
if not os.path.isabs(path) and os.path.isdir(base_path):
try:
return os.path.abspath(os.path.join(base_path, path))
except Exception:
pass # На случай сбоя в os.path.join или abspath
elif os.path.isabs(path):
return os.path.abspath(path)
else:
return path
def make_relative_path(abs_path, base_path):
abs_path = os.path.abspath(abs_path)
base_path = os.path.abspath(base_path)
# Разбиваем на списки директорий
abs_parts = abs_path.split(os.sep)
base_parts = base_path.split(os.sep)
# Проверяем, является ли base_path настоящим префиксом пути (по папкам)
if abs_parts[:len(base_parts)] == base_parts:
rel_parts = abs_parts[len(base_parts):]
return "/".join(rel_parts)
# Иначе пробуем relpath
try:
return os.path.relpath(abs_path, base_path).replace("\\", "/")
except Exception:
return abs_path.replace("\\", "/")
def parse_vars(filename, typedef_map=None):
root, tree = myXML.safe_parse_xml(filename)
if root is None:
return []
if typedef_map is None:
typedef_map = {}
vars_list = []
variables_elem = root.find('variables')
if variables_elem is not None:
for var in variables_elem.findall('var'):
name = var.attrib.get('name', '')
var_type = var.findtext('type', 'unknown').strip()
# Вычисляем pt_type и iq_type
pt_type = var.findtext('pt_type')
if not pt_type:
pt_type = map_type_to_pt(var_type, name, typedef_map)
iq_type = var.findtext('iq_type')
if not iq_type:
iq_type = get_iq_define(var_type)
# Записываем iq_type в XML
iq_type_elem = var.find('iq_type')
if iq_type_elem is None:
iq_type_elem = ET.SubElement(var, 'iq_type')
iq_type_elem.text = iq_type
# Вычисляем pt_type и iq_type
pt_type = var.findtext('pt_type')
if not pt_type:
pt_type = map_type_to_pt(var_type, name, typedef_map)
# Записываем pt_type в XML
pt_type_elem = var.find('pt_type')
if pt_type_elem is None:
pt_type_elem = ET.SubElement(var, 'pt_type')
pt_type_elem.text = pt_type
vars_list.append({
'name': name,
'show_var': var.findtext('show_var', 'false'),
'enable': var.findtext('enable', 'false'),
'shortname': var.findtext('shortname', name),
'pt_type': pt_type,
'iq_type': iq_type,
'return_type': var.findtext('return_type', 't_iq_none'),
'type': var_type,
'file': var.findtext('file', ''),
'extern': var.findtext('extern', 'false') == 'true',
'static': var.findtext('static', 'false') == 'true',
})
myXML.fwrite(root, filename)
return vars_list
# 2. Парсим structSup.xml
def parse_structs(filename):
root, tree = myXML.safe_parse_xml(filename)
if root is None:
return {}, {}
structs = {}
typedef_map = {}
def parse_struct_element(elem):
fields = {}
for field in elem.findall("field"):
fname = field.attrib.get("name")
ftype = field.attrib.get("type", "")
# Проверка на вложенную структуру
nested_struct_elem = field.find("struct")
if nested_struct_elem is not None:
# Рекурсивно парсим вложенную структуру и вставляем её как подсловарь
nested_fields = parse_struct_element(nested_struct_elem)
# Оборачиваем в dict с ключом 'type' для хранения типа из XML
fields[fname] = {
'type': ftype, # здесь тип, например "BENDER_ERROR"
**nested_fields # развёрнутые поля вложенной структуры
}
else:
# Обычное поле
fields[fname] = ftype
return fields
structs_elem = root.find("structs")
if structs_elem is not None:
for struct in structs_elem.findall("struct"):
name = struct.attrib.get("name")
if name and name not in structs:
fields = parse_struct_element(struct)
structs[name] = fields
# typedefs без изменений
typedefs_elem = root.find("typedefs")
if typedefs_elem is not None:
for typedef in typedefs_elem.findall("typedef"):
name = typedef.attrib.get('name')
target_type = typedef.attrib.get('type')
if name and target_type:
typedef_map[name.strip()] = target_type.strip()
return structs, typedef_map
def parse_array_dims(type_str):
"""Возвращает базовый тип и список размеров массива"""
dims = list(map(int, re.findall(r'\[(\d+)\]', type_str)))
base_type = re.sub(r'\[\d+\]', '', type_str).strip()
return base_type, dims
def generate_array_names(prefix, dims, depth=0):
"""Рекурсивно генерирует имена для всех элементов многомерного массива"""
if not dims:
return [prefix]
result = []
for i in range(dims[0]):
new_prefix = f"{prefix}[{i}]"
children = generate_array_names(new_prefix, dims[1:], depth + 1)
result.append({
'name': new_prefix,
'children': children if len(dims) > 1 else None
})
return result
def flatten_array_tree(array_tree):
"""Разворачивает дерево массивов в линейный список с вложенными children"""
result = []
for node in array_tree:
entry = {'name': node['name']}
if node['children']:
entry['children'] = flatten_array_tree(node['children'])
result.append(entry)
return result
def expand_struct_recursively(prefix, type_str, structs, typedefs, var_attrs, depth=0):
if depth > 10:
return []
# Вспомогательная функция для обработки массивов
def process_array(prefix, type_str, structs, typedefs, var_attrs, depth=0):
base_type, array_dims = parse_array_dims(type_str)
if not array_dims:
return []
# На текущем уровне берем первый размер массива
current_dim = array_dims[0]
# Оставшиеся размеры — все, кроме первого
remaining_dims = array_dims[1:]
# Для создания типа с оставшимися размерами:
if remaining_dims:
# Формируем строку типа для оставшихся измерений массива, например int[16]
remaining_type_str = f"{base_type}{''.join(f'[{d}]' for d in remaining_dims)}"
else:
remaining_type_str = base_type
array_tree = []
for i in range(current_dim):
name = f"{prefix}[{i}]"
# Для каждого элемента передаем уже оставшийся тип массива
children = expand_struct_recursively(name, remaining_type_str, structs, typedefs, var_attrs, depth + 1)
node = {
'name': name,
'type': remaining_type_str if remaining_dims else base_type,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
if children:
node['children'] = children
array_tree.append(node)
return array_tree
# Если type_str — уже распарсенная структура (dict)
if isinstance(type_str, dict):
fields = type_str
else:
# Проверяем, массив ли это
base_type, array_dims = parse_array_dims(type_str)
if array_dims:
return process_array(prefix, type_str, structs, typedefs, var_attrs, depth)
# Ищем структуру по имени типа
base_type = scan_vars.strip_ptr_and_array(type_str)
fields = structs.get(base_type)
if not isinstance(fields, dict):
# Не структура и не массив — просто возвращаем пустой список
return []
children = []
for field_name, field_value in fields.items():
if field_name == 'type':
continue
# Формируем полное имя поля
if prefix.endswith('*'):
separator = '->'
full_name = f"{prefix[:-1]}{separator}{field_name}"
else:
separator = '.'
full_name = f"{prefix}{separator}{field_name}"
# Определяем тип поля
if isinstance(field_value, dict) and isinstance(field_value.get('type'), str):
field_type_str = field_value['type']
elif isinstance(field_value, str):
field_type_str = field_value
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)
if sub_dims:
# Массив — раскрываем элементы
array_parent = {
'name': full_name,
'type': field_type_str,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
array_children = []
flat_names = generate_array_names(full_name, sub_dims)
for node in flat_names:
# node — dict с ключом 'name' и (возможно) 'children'
sub_items = expand_struct_recursively(node['name'], base_subtype, structs, typedefs, var_attrs, depth + 1)
child_node = {
'name': node['name'],
'type': base_subtype,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
if sub_items:
child_node['children'] = sub_items
array_children.append(child_node)
array_parent['children'] = array_children
children.append(array_parent)
continue
# Игнорируем указатели на функции
if "(" in field_type_str and "*" in field_type_str and ")" in field_type_str:
continue
if isinstance(field_value, dict):
# Это одиночная структура — раскрываем рекурсивно
sub_items = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1)
child = {
'name': full_name,
'type': field_type_str,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
if sub_items:
child['children'] = sub_items
children.append(child)
else:
# Обычное поле (int, float, etc.)
child = {
'name': full_name,
'type': field_type_str,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
children.append(child)
continue
# Если поле — dict без 'type' или со сложной структурой, обрабатываем как вложенную структуру
if isinstance(field_value, dict):
type_name = field_value.get('type', '')
child = {
'name': full_name,
'type': type_name,
'pt_type': '',
'iq_type': '',
'return_type': '',
'file': var_attrs.get('file'),
'extern': var_attrs.get('extern'),
'static': var_attrs.get('static'),
}
subchildren = expand_struct_recursively(full_name_prefix, field_value, structs, typedefs, var_attrs, depth + 1)
if subchildren:
child['children'] = subchildren
children.append(child)
return children
def expand_vars(vars_list, structs, typedefs):
"""
Раскрывает структуры и массивы структур в деревья.
"""
expanded = []
for var in vars_list:
pt_type = var.get('pt_type', '')
raw_type = var.get('type', '')
if var['name'] == 'project':
a = 1
if pt_type.startswith('pt_ptr_'):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name']+'*', raw_type, structs, typedefs, var)
expanded.append(new_var)
if pt_type.startswith('pt_arr_'):
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
elif pt_type == 'pt_struct':
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
elif pt_type == 'pt_union':
new_var = var.copy()
new_var['children'] = expand_struct_recursively(var['name'], raw_type, structs, typedefs, var)
expanded.append(new_var)
else:
expanded.append(var)
return expanded
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

View File

@@ -1,11 +1,14 @@
from PySide2.QtWidgets import (
QTableWidget, QTableWidgetItem, QCheckBox, QComboBox, QLineEdit, QCompleter,
QAbstractItemView, QHeaderView, QLabel
QAbstractItemView, QHeaderView, QLabel,
QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QScrollArea, QWidget
)
from PySide2.QtGui import QColor, QBrush, QPalette
from PySide2.QtCore import Qt
from enum import IntEnum
from generateVars import type_map
from generate_debug_vars import type_map
import time
from typing import Dict, List
class rows(IntEnum):
No = 0
@@ -17,7 +20,53 @@ class rows(IntEnum):
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 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)
@@ -27,7 +76,7 @@ class VariableTableWidget(QTableWidget):
'En',
'Name',
'Origin Type',
'Pointer Type',
'Base Type',
'IQ Type',
'Return Type',
'Short Name'
@@ -36,9 +85,19 @@ class VariableTableWidget(QTableWidget):
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)]
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), чтобы они заняли всю оставшуюся ширину
@@ -60,13 +119,11 @@ class VariableTableWidget(QTableWidget):
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
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:
@@ -115,10 +172,10 @@ class VariableTableWidget(QTableWidget):
self.setItem(row, rows.type, origin_item)
# pt_type
pt_combo = QComboBox()
pt_combo.addItems(self.display_type_options)
pt_combo = CtrlScrollComboBox()
pt_combo.addItems(self.pt_types)
value = var['pt_type'].replace('pt_', '')
if value not in self.display_type_options:
if value not in self.pt_types:
pt_combo.addItem(value)
pt_combo.setCurrentText(value)
pt_combo.currentTextChanged.connect(on_change_callback)
@@ -126,7 +183,7 @@ class VariableTableWidget(QTableWidget):
self.setCellWidget(row, rows.pt_type, pt_combo)
# iq_type
iq_combo = QComboBox()
iq_combo = CtrlScrollComboBox()
iq_combo.addItems(self.iq_types)
value = var['iq_type'].replace('t_', '')
if value not in self.iq_types:
@@ -137,9 +194,10 @@ class VariableTableWidget(QTableWidget):
self.setCellWidget(row, rows.iq_type, iq_combo)
# return_type
ret_combo = QComboBox()
ret_combo = CtrlScrollComboBox()
ret_combo.addItems(self.iq_types)
ret_combo.setCurrentText(var.get('return_type', ''))
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)
@@ -154,25 +212,28 @@ class VariableTableWidget(QTableWidget):
self.check()
def check(self):
warning_color = (QColor("#FFFACD")) # Жёлтый для длинных shortname
error_color = (QColor("#FFB6C1")) # Светло-красный для отсутствующих переменных
warning_color = QColor("#FFFACD")
error_color = QColor("#FFB6C1")
tooltip_shortname = "Short Name длиннее 10 символов — будет обрезано при генерации"
tooltip_missing = f'Имя переменной не найдено среди переменных. Добавьте её через кнопку "Добавить переменные"'
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()):
# Получаем имя переменной (столбец `name`)
t1 = time.time()
name_widget = self.cellWidget(row, rows.name)
t2 = time.time()
name = name_widget.text() if name_widget else ""
# Получаем shortname
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) > 10
found = any(v.get('name') == name for v in self.var_list)
# Выбираем цвет и подсказку
found = name in var_names_set
color = None
tooltip = ""
if not found:
@@ -183,6 +244,10 @@ class VariableTableWidget(QTableWidget):
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):
@@ -208,6 +273,53 @@ class VariableTableWidget(QTableWidget):
})
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})
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 # предотвращаем рекурсию
@@ -255,17 +367,32 @@ class VariableTableWidget(QTableWidget):
item = self.item(row, col)
widget = self.cellWidget(row, col)
# Подсветка обычной item-ячейки (например, тип переменной)
if item is not None:
if color:
current_bg = item.background().color() if item.background() else None
if color and current_bg != color:
item.setBackground(QBrush(color))
item.setToolTip(tooltip)
else:
elif not color and current_bg is not None:
item.setBackground(QBrush(Qt.NoBrush))
item.setToolTip("")
# Подсветка виджетов — здесь главная доработка
elif widget is not None:
# Надёжная подсветка: через styleSheet
widget.setStyleSheet(css_color)
widget.setToolTip(tooltip if color else "")
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

View File

@@ -1,274 +1,246 @@
#include "debug_tools.h"
#include "IQmathLib.h"
DebugLowLevel_t debug_ll = DEBUG_LOWLEVEL_INIT;
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var);
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var, DebugVarIQType_t iq_type_final);
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var);
DebugVarIQType_t dbg_type = t_iq24;
long Debug_ReadVar(DebugVar_t *var, DebugVarIQType_t iq_type_final)
///////////////////////////----EXAPLE-----//////////////////////////////
long var_numb = 1;
DebugVarName_t var_name;
long return_var;
long return_ll_var;
int result;
char ext_date[] = {7, 233, 11, 07, 16, 50};
void Debug_Test_Example(void)
{
long tmp_var;
if((var->ptr_type == pt_struct) || (var->ptr_type == pt_union) || (var->ptr_type == pt_unknown))
return;
result = Debug_ReadVar(var_numb, &return_var);
result = Debug_ReadVarName(var_numb, var_name);
convertDebugVarToIQx(var, &tmp_var, dbg_type);
return tmp_var;
if(Debug_LowLevel_Initialize(ext_date) == 0)
result = Debug_LowLevel_ReadVar(&return_ll_var);
}
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var, DebugVarIQType_t iq_type_final)
///////////////////////////----PUBLIC-----//////////////////////////////
int Debug_ReadVar(int var_ind, long *return_long)
{
long iq_numb, iq_united, iq_final;
float float_numb;
if(return_long == NULL)
return 1;
long tmp_var;
if(getDebugVar(var, &iq_numb, &float_numb) == 1)
return 1;
if (var_ind >= DebugVar_Qnt)
return 1;
if((dbg_vars[var_numb].ptr_type == pt_struct) || (dbg_vars[var_numb].ptr_type == pt_union) ||
(dbg_vars[var_numb].ptr_type == pt_unknown))
return 1;
// ïðèâåäåíèå ê îäíîìó IQ
switch(var->iq_type)
return convertDebugVarToIQx(&dbg_vars[var_numb], return_long);
}
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr)
{
if(name_ptr == NULL)
return 1;
if (var_ind >= DebugVar_Qnt)
return 1;
int i;
// Êîïèðîâàíèå ñ çàùèòîé îò ïåðåïîëíåíèÿ è ÿâíîé îñòàíîâêîé ïî '\0'
for (i = 0; i < sizeof(dbg_vars[var_numb].name); i++)
{
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);
name_ptr[i] = dbg_vars[var_numb].name[i];
if (dbg_vars[var_numb].name[i] == '\0')
break;
}
// Ãàðàíòèðîâàííîå çàâåðøåíèå ñòðîêè (íà ñëó÷àé, åñëè â var->name íå áûëî '\0')
name_ptr[sizeof(dbg_vars[var_numb].name) - 1] = '\0';
// ïðèâåäåíèå îáùåãî IQ ê çàïðàøèâàåìîìó
switch(iq_type_final)
{
case t_iq_none:
iq_final = (int)_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;
}
*ret_var = iq_final;
return 0;
}
int Debug_LowLevel_ReadVar(long *return_long)
{
if (return_long == NULL)
return 1;
if (debug_ll.isVerified == 0)
return 1;
char *addr = debug_ll.dbg_var.Ptr;
unsigned long addr_val = (unsigned long)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_long);
;
}
int Debug_LowLevel_Initialize(const char* external_date)
{
if (external_date == NULL) {
return -1;
}
// Ïðåîáðàçóåì external_date â ñòðóêòóðó
DateTime_t 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];
// Ñðàâíåíèå âñåõ ïîëåé
if (ext.year == debug_ll.build_date.year &&
ext.month == debug_ll.build_date.month &&
ext.day == debug_ll.build_date.day &&
ext.hour == debug_ll.build_date.hour &&
ext.minute == debug_ll.build_date.minute)
{
debug_ll.isVerified = 1;
return 0; // Ñîâïàëî
}
debug_ll.isVerified = 0;
return 1; // Íå ñîâïàëî
}
/////////////////////----INTERNAL FUNCTIONS-----////////////////////////
static int iqTypeToQ(DebugVarIQType_t t)
{
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; // îøèáêà
}
static int convertDebugVarToIQx(DebugVar_t *var, long *ret_var)
{
long iq_numb, iq_united, iq_final;
float float_numb;
if(getDebugVar(var, &iq_numb, &float_numb) != 0)
return 1;
int src_q = iqTypeToQ(var->iq_type);
int dst_q = iqTypeToQ(var->return_type);
if (src_q < 0 || dst_q < 0)
return 2; // íåïðàâèëüíûé ôîðìàò
long long iq_united64 = 0;
long long iq_final64 = 0;
// Êîíâåðòàöèÿ ê GLOBAL_Q (64-áèò)
if (var->iq_type == t_iq_none) {
if (var->ptr_type == pt_float) {
// float_numb óìíîæàåì íà 2^GLOBAL_Q (2^24=16777216)
// Ðåçóëüòàò ïðèâîäèì ê long long
iq_united64 = (long long)(float_numb * 16777216.0f);
} else {
iq_united64 = ((long long)iq_numb) << GLOBAL_Q;
}
} else {
int shift = GLOBAL_Q - src_q;
if (shift >= 0)
iq_united64 = ((long long)iq_numb) << shift;
else
iq_united64 = ((long long)iq_numb) >> (-shift);
}
// Êîíâåðòàöèÿ èç GLOBAL_Q â öåëåâîé IQ (64-áèò)
if (var->return_type == t_iq_none) {
// Âîçâðàùàåì öåëîå, îòáðîñèâ äðîáíóþ ÷àñòü
*ret_var = (long)(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 > LONG_MAX || iq_final64 < LONG_MIN)
return 3; // ïåðåïîëíåíèå
*ret_var = (long)iq_final64;
}
return 0;
}
static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
{
if (!var || !int_var || !float_var)
if (!var || !int_var || !float_var || !var->Ptr)
return 1; // îøèáêà: null óêàçàòåëü
char *addr = var->Ptr;
unsigned long addr_val = (unsigned long)addr;
switch (var->ptr_type)
{
case pt_int8: // signed char
*int_var = *((signed char *)var->Ptr);
case pt_int8: // 8 áèò
case pt_uint8:
// âûðàâíèâàíèå íå íóæíî äëÿ 8 áèò
*int_var = *((volatile char *)addr);
break;
case pt_int16: // int
*int_var = *((int *)var->Ptr);
case pt_int16: // 16 áèò (int)
case pt_uint16:
*int_var = *((volatile int *)addr);
break;
case pt_int32: // long
*int_var = *((long *)var->Ptr);
case pt_int32: // 32 áèò (long)
case pt_uint32:
if (addr_val & 0x1) // ïðîâåðÿåì âûðàâíèâàíèå ïî 2 ñëîâàì (4 áàéòà)
return 3; // îøèáêà âûðàâíèâàíèÿ
*int_var = *((volatile long *)addr);
break;
case pt_uint8: // unsigned char
*int_var = *((unsigned char *)var->Ptr);
break;
case pt_uint16: // unsigned int
*int_var = *((unsigned int *)var->Ptr);
break;
case pt_uint32: // unsigned long
*int_var = *((unsigned long *)var->Ptr);
break;
case pt_float: // float
*float_var = *((float *)var->Ptr);
// case pt_int64: // 64 áèò (long long)
// case pt_uint64:
// if (addr_val & 0x3) // ïðîâåðêà âûðàâíèâàíèÿ ïî 4 ñëîâàì (8 áàéòàì)
// return 2; // îøèáêà âûðàâíèâàíèÿ
// // Òóò ïðîñòî ÷èòàåì, íî long long ìîæåò íå ïîìåñòèòüñÿ â *int_var
// // Ìîæíî çàìåíèòü ëîãèêó ïîä 64-áèòíîå ÷òåíèå ïðè íåîáõîäèìîñòè
// *int_var = *((volatile long long *)addr);
// break;
case pt_float: // float (4 áàéòà)
if (addr_val & 0x1) // ïðîâåðêà âûðàâíèâàíèÿ ïî 2 ñëîâàì
return 4; // îøèáêà âûðàâíèâàíèÿ
*float_var = *((volatile float *)addr);
break;
default:
return 1; // íåïîääåðæèâàåìûé òèï
// äëÿ óêàçàòåëåé è ìàññèâîâ íå ïîääåðæèâàåòñÿ ÷òåíèå
// case pt_ptr_int8:
// case pt_ptr_int16:
@@ -282,9 +254,216 @@ static int getDebugVar(DebugVar_t *var, long *int_var, float *float_var)
// case pt_arr_uint8:
// case pt_arr_uint16:
// case pt_arr_uint32:
default:
return 1;
}
return 0; // óñïåõ
}
///////////// OUTDATE ////////////////
//
// // ïðèâåäåíèå ê îäíîìó 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;
// }
//
// // ïðèâåäåíèå îáùåãî 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;
// }
// *ret_var = iq_final;

View File

@@ -9,9 +9,11 @@ typedef enum
pt_int8, // signed char
pt_int16, // int
pt_int32, // long
pt_int64, // long
pt_uint8, // unsigned char
pt_uint16, // unsigned int
pt_uint32, // unsigned long
pt_uint64, // unsigned long
pt_float, // float
pt_struct, // struct
pt_union, // struct
@@ -65,17 +67,45 @@ typedef enum
t_iq30
}DebugVarIQType_t;
typedef char DebugVarName_t[11];
typedef struct
{
char* Ptr;
DebugVarPtrType_t ptr_type;
DebugVarIQType_t iq_type;
char name[10];
DebugVarIQType_t return_type;
DebugVarName_t name;
}DebugVar_t;
typedef struct {
int year;
char month;
char day;
char hour;
char minute;
} DateTime_t;
typedef struct
{
DateTime_t build_date;
unsigned int isVerified;
DebugVar_t dbg_var;
}DebugLowLevel_t;
extern DebugLowLevel_t debug_ll;
#define DATE_INIT {BUILD_YEAR, BUILD_MONTH, BUILD_DATA, BUILD_HOURS, BUILD_MINUTES}
#define DEBUG_VAR_INIT {0, pt_uint16, t_iq_none, t_iq_none, "\0"}
#define DEBUG_LOWLEVEL_INIT {DATE_INIT, 0, DEBUG_VAR_INIT}
extern int DebugVar_Qnt;
extern DebugVar_t dbg_vars[];
long Debug_ReadVar(DebugVar_t *var, DebugVarIQType_t iq_type_final);
void Debug_Test_Example(void);
int Debug_ReadVar(int var_ind, long *return_long);
int Debug_ReadVarName(int var_ind, DebugVarName_t name_ptr);
int Debug_LowLevel_ReadVar(long *return_long);
int Debug_LowLevel_Initialize(const char* external_date);
#endif //DEBUG_TOOLS

View File

@@ -1,322 +0,0 @@
// Ýòîò ôàéë ñãåíåðèðîâàí àâòîìàòè÷åñêè
#include "debug_tools.h"
// Èíêëþäû äëÿ äîñòóïà ê ïåðåìåííûì
#include "RS_Functions_modbus.h"
#include "xp_project.h"
#include "xp_write_xpwm_time.h"
#include "teta_calc.h"
#include "vector.h"
#include "v_pwm24.h"
#include "errors.h"
#include "dq_to_alphabeta_cos.h"
#include "log_can.h"
#include "f281xpwm.h"
#include "pwm_vector_regul.h"
#include "adc_tools.h"
#include "rotation_speed.h"
#include "RS_Functions.h"
#include "xp_controller.h"
#include "xPeriphSP6_loader.h"
#include "xp_rotation_sensor.h"
#include "x_serial_bus.h"
#include "Spartan2E_Functions.h"
#include "x_parallel_bus.h"
#include "svgen_dq.h"
#include "detect_phase_break2.h"
#include "log_to_memory.h"
#include "CRC_Functions.h"
#include "global_time.h"
#include "CAN_Setup.h"
#include "log_params.h"
#include "pid_reg3.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 long return_var;
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 long var_numb;
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 = 2;
#pragma DATA_SECTION(dbg_vars,".dbgvar_info")
DebugVar_t dbg_vars[] = {\
{(char *)&ADC0finishAddr, pt_uint8, t_iq7, pt_uint8, "asdasjjjjj" }, \
{(char *)&project.cds_tk.count_elements_pbus, pt_uint16, t_iq_none, pt_uint16, "project.cd" }, \
};

20181
structs.xml

File diff suppressed because it is too large Load Diff

4712
vars.xml

File diff suppressed because it is too large Load Diff