чистка для релиза

переструктурировано чуть

и всякие мелкие фиксы
This commit is contained in:
2025-07-15 15:37:29 +03:00
parent c32dc161f8
commit 369cfa808c
12 changed files with 122 additions and 25479 deletions

View File

@@ -10,8 +10,8 @@ from enum import IntEnum
import threading
from generate_debug_vars import run_generate
import var_setup
from var_selector_window import var_selector_windowDialog
from var_table import var_tableWidget, rows
from var_selector_window import VariableSelectorDialog
from var_table import VariableTableWidget, rows
from scan_progress_gui import ProcessOutputWindow
import scan_vars
import myXML
@@ -134,7 +134,7 @@ class VarEditor(QWidget):
btn_open_output = QPushButton(open_output_title)
btn_open_output.clicked.connect(self.__open_output_file_with_program)
# Таблица
self.table = var_tableWidget()
self.table = VariableTableWidget()
# Основной layout
layout = QVBoxLayout()
layout.addLayout(xml_layout)
@@ -208,7 +208,7 @@ class VarEditor(QWidget):
# Создаём окно с кнопкой "Готово"
self.proc_win = ProcessOutputWindow(self.proj_path, self.makefile_path, self.xml_path,
on_done_callback=self.__after_scan_vars_finished)
self.__after_scan_vars_finished, self)
self.proc_win.start_scan()
@@ -442,8 +442,9 @@ class VarEditor(QWidget):
if not self.vars_list:
QMessageBox.warning(self, "Нет переменных", f"Сначала загрузите переменные ({scan_title}).")
return
dlg = var_selector_windowDialog(self.table, 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()

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

@@ -0,0 +1,111 @@
import subprocess
import shutil
import os
from pathlib import Path
import PySide2
from PyInstaller.utils.hooks import collect_data_files
# install: pip install PySide2 lxml nuitka pyinstaller
# - PyInstaller
# - nuitka
# - PySide2
# - clang
# === Конфигурация ===
USE_NUITKA = True # True — сборка через Nuitka, False — через PyInstaller
SRC_PATH = Path("./Src/")
SCRIPT_PATH = SRC_PATH / "DebugVarEdit_GUI.py"
OUTPUT_NAME = "DebugVarEdit"
DIST_PATH = Path("./").resolve()
WORK_PATH = Path("./build_temp").resolve()
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"
}
# === PySide2 плагины ===
PySide2_path = Path(PySide2.__file__).parent
datas = []
datas += collect_data_files('PySide2', includes=['plugins/platforms/*'])
datas += collect_data_files('PySide2', includes=['plugins/styles/*'])
datas += collect_data_files('PySide2', includes=['plugins/imageformats/*'])
add_data_list = [f"{src};{dest}" for src, dest in datas]
# Проверка наличия DLL и добавление
add_binary_list = []
for name, path in LIBS.items():
if path.exists():
add_binary_list.append(f"{str(path)};{name}")
else:
print(f"WARNING: {path.name} не найден — он не будет включён в сборку")
def clean_temp():
for folder in TEMP_FOLDERS:
path = Path(folder)
if path.exists():
shutil.rmtree(path, ignore_errors=True)
if USE_NUITKA:
# Формируем include-data-file только для DLL
include_data_files = [f"--include-data-file={str(path)}={name}" for name, path in LIBS.items() if path.exists()]
# Добавляем icon.ico как встроенный ресурс
if ICON_ICO_PATH.exists():
include_data_files.append(f"--include-data-file={ICON_ICO_PATH}=icon.ico")
cmd = [
"python", "-m", "nuitka",
"--standalone",
"--onefile",
"--enable-plugin=pyside2",
"--windows-console-mode=disable",
f"--output-dir={DIST_PATH}",
f"--output-filename={OUTPUT_NAME}.exe",
f"--windows-icon-from-ico={ICON_ICO_PATH}",
*include_data_files,
str(SCRIPT_PATH)
]
else:
# PyInstaller
cmd = [
"pyinstaller",
"--name", OUTPUT_NAME,
"--distpath", str(DIST_PATH),
"--workpath", str(WORK_PATH),
"--specpath", str(SPEC_PATH),
"--windowed",
f"--icon={ICON_ICO_PATH}",
"--hidden-import=PySide2.QtWidgets",
"--hidden-import=PySide2.QtGui",
"--hidden-import=PySide2.QtCore",
*[arg for b in add_binary_list for arg in ("--add-binary", b)],
*[arg for d in add_data_list for arg in ("--add-data", d)],
str(SCRIPT_PATH)
]
# === Запуск сборки ===
print("Выполняется сборка с помощью " + ("Nuitka" if USE_NUITKA else "PyInstaller"))
print(" ".join(cmd))
try:
subprocess.run(cmd, check=True)
print("\nСборка успешно завершена!")
# Удаление временных папок после сборки
clean_temp()
except subprocess.CalledProcessError:
print("\nОшибка при сборке.")

View File

@@ -7,7 +7,7 @@ import contextlib
import io
import json
from scan_vars import run_scan
from var_table import var_tableWidget, rows
from var_table import VariableTableWidget, rows
from PySide2.QtWidgets import (
QApplication, QWidget, QTableWidget, QTableWidgetItem,
@@ -73,13 +73,13 @@ class EmittingStream(QObject):
self._buffer = ""
class ProcessOutputWindow(QDialog):
def __init__(self, proj_path, makefile_path, xml_path, on_done_callback=None):
super().__init__()
def __init__(self, proj_path, makefile_path, xml_path, on_done_callback=None, parent=None):
super().__init__(parent)
self.setWindowTitle("Поиск переменных...")
self.resize(600, 480)
self.setModal(True)
self.setAttribute(Qt.WA_DeleteOnClose)
self.proj_path = proj_path
self.makefile_path = makefile_path

View File

@@ -244,7 +244,6 @@ class VariableSelectWidget(QWidget):
def filter_tree(self):
text = self.search_input.text().strip().lower()
print(f"[{self.objectName()}] Filtering tree with text: '{text}'")
path_parts = split_path(text) if text else []
if '.' not in text and '->' not in text and '[' not in text and text != '':
@@ -358,8 +357,6 @@ class VariableSelectWidget(QWidget):
self.completer.setModel(QStringListModel(completions))
self.completer.complete()
print(f"[{self.objectName()}] Updating completions for text: '{text}' -> {len(completions)} completions found.")
print(f" Completions: {completions[:5]}") # Вывести первые 5 для проверки
return completions
@@ -469,7 +466,6 @@ class VariableSelectWidget(QWidget):
def on_search_text_changed(self, text):
sender_widget = self.sender()
sender_name = sender_widget.objectName() if sender_widget else "Unknown Sender"
print(f"[{self.objectName()}] (Sender: {sender_name}) Search text changed: '{text}'")
self.completer.setWidget(self.search_input)
self.filter_tree()

View File

@@ -15,7 +15,7 @@ import var_selector_table
array_re = re.compile(r'^(\w+)\[(\d+)\]$')
class var_selector_windowDialog(QDialog):
class VariableSelectorDialog(QDialog):
def __init__(self, table, all_vars, structs, typedefs, xml_path=None, parent=None):
super().__init__(parent)
self.setWindowTitle("Выбор переменных")
@@ -62,11 +62,29 @@ class var_selector_windowDialog(QDialog):
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 окна
tables_layout = QHBoxLayout() # горизонтальный 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)
@@ -101,7 +119,7 @@ class var_selector_windowDialog(QDialog):
self.btn_accept.clicked.connect(self.on_apply_clicked)
# Соединяем чекбокс с методом виджета
self.autocomplete_checkbox.stateChanged.connect(self.vars_widget.set_autocomplete)
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())
@@ -224,13 +242,6 @@ class var_selector_windowDialog(QDialog):
self.accept()
def save_checkbox_state(self):
self.settings.setValue("autocomplete_enabled", self.autocomplete_checkbox.isChecked())
# Обнови on_left_tree_double_click:
def on_left_tree_double_click(self, item, column):
selected_names = [item.text(0)]
@@ -373,3 +384,9 @@ class var_selector_windowDialog(QDialog):
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)

View File

@@ -60,8 +60,14 @@ class FilterDialog(QDialog):
return [cb.text() for cb in self.checkboxes if cb.isChecked()]
class var_tableWidget(QTableWidget):
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)
# Таблица переменных
@@ -82,11 +88,12 @@ class var_tableWidget(QTableWidget):
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', 'iq24', 'iq19', 'iq15', 'iq10']
self.iq_types = ['iq_none', 'iq', 'iq10', 'iq15', 'iq19', 'iq24']
# Фильтруем типы из type_map.values() исключая те, что содержат 'arr' или 'ptr'
self.type_options = [t for t in set(type_map.values()) if 'arr' not in t and 'ptr' not in t]
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 self.type_options]
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)
@@ -117,9 +124,6 @@ class var_tableWidget(QTableWidget):
def populate(self, vars_list, structs, on_change_callback):
self.var_list = vars_list
self.type_options = list(dict.fromkeys(type_map.values()))
self.pt_types = [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:
@@ -168,7 +172,7 @@ class var_tableWidget(QTableWidget):
self.setItem(row, rows.type, origin_item)
# pt_type
pt_combo = QComboBox()
pt_combo = CtrlScrollComboBox()
pt_combo.addItems(self.pt_types)
value = var['pt_type'].replace('pt_', '')
if value not in self.pt_types:
@@ -176,11 +180,10 @@ class var_tableWidget(QTableWidget):
pt_combo.setCurrentText(value)
pt_combo.currentTextChanged.connect(on_change_callback)
pt_combo.setStyleSheet(style_with_padding)
pt_combo.wheelEvent = lambda e: e.ignore()
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:
@@ -188,17 +191,15 @@ class var_tableWidget(QTableWidget):
iq_combo.setCurrentText(value)
iq_combo.currentTextChanged.connect(on_change_callback)
iq_combo.setStyleSheet(style_with_padding)
iq_combo.wheelEvent = lambda e: e.ignore()
self.setCellWidget(row, rows.iq_type, iq_combo)
# return_type
ret_combo = QComboBox()
ret_combo = CtrlScrollComboBox()
ret_combo.addItems(self.iq_types)
value = var['return_type'].replace('t_', '')
ret_combo.setCurrentText(value)
ret_combo.currentTextChanged.connect(on_change_callback)
ret_combo.setStyleSheet(style_with_padding)
ret_combo.wheelEvent = lambda e: e.ignore()
self.setCellWidget(row, rows.ret_type, ret_combo)
# short_name
@@ -276,7 +277,7 @@ class var_tableWidget(QTableWidget):
def on_header_clicked(self, logicalIndex):
if logicalIndex == rows.pt_type:
dlg = FilterDialog(self, self.pt_types_all, self._pt_type_filter, "Выберите Pointer Types")
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})
@@ -309,6 +310,7 @@ class var_tableWidget(QTableWidget):
current = combo.currentText()
combo.blockSignals(True)
combo.clear()
combo.addItems(allowed_items)
if current in allowed_items:
combo.setCurrentText(current)