чистка для релиза
переструктурировано чуть и всякие мелкие фиксы
This commit is contained in:
@@ -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
55
Src/README_DEVELOP.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Для разработчиков
|
||||
|
||||
### Структура проекта:
|
||||
|
||||
```bash
|
||||
Src
|
||||
├── build/
|
||||
│ └── build_and_clean.py # Билдинг проекта в .exe (через nuitka или pyinstaller)
|
||||
├── DebugVarEdit_GUI.py # Главное окно
|
||||
├── var_table.py # Таблица выбранных переменных
|
||||
├── var_selector_window.py # Окно выбора переменных
|
||||
├── var_selector_table.py # Таблица переменных в окне выбора переменных
|
||||
├── scan_progress_gui.py # Отображение процесса сканирования переменных
|
||||
├── scan_vars.py # Сканирование переменных среди .c/.h файлов
|
||||
├── generate_debug_vars.py # Генерация debug_vars.c
|
||||
├── myXML.py # Утилиты для XML
|
||||
├── makefile_parser.py # Парсинг makefile на .c/.h файлы
|
||||
├── var_setup.py # Подготовка переменных для окна выбора переменных
|
||||
├── libclang.dll # Бибилиотека clang
|
||||
├── icon.ico # Иконка
|
||||
```
|
||||
|
||||
### Зависимости
|
||||
|
||||
Для запуска приложения:
|
||||
- **Python 3.7+**
|
||||
- **clang** — используется для парсинга C-кода (требуется `libclang.dll`)
|
||||
- **PySide2** — GUI-фреймворк
|
||||
- **lxml** — работа с XML
|
||||
> Python 3.7 и PySide2 рекомендуется для совместимости с Windows 7
|
||||
|
||||
Для сборки `.exe`:
|
||||
- **Nuitka** — создает полностью автономный `.exe` без внешних зависимостей
|
||||
- **PyInstaller** — создает `.exe` с зависимостью от `pythonXX.dll` (Python должен быть установлен)
|
||||
|
||||
|
||||
### Сборка:
|
||||
Если вы хотите собрать `DebugVarEdit.exe` самостоятельно из исходников, используйте скрипт **build/build_and_clean.py**. Он автоматически собирает проект с помощью Nuitka или PyInstaller:
|
||||
- Собирает проект в `DebugVarEdit.exe` в корневой папке.
|
||||
- Включает:
|
||||
- все необходимые `.dll` (например, `libclang.dll`),
|
||||
- иконку (`icon.ico`),
|
||||
- плагины PySide2.
|
||||
- Очищает временные папки после сборки:
|
||||
- `build_temp`
|
||||
- `__pycache__`
|
||||
- `DebugVarEdit_GUI.*`
|
||||
|
||||
> Все пути, имена файлов, временные папки и выбор между Nuitka и PyInstaller можно настроить в начале файла `build_and_clean.py`.
|
||||
|
||||
### Установка зависимостей
|
||||
|
||||
```bash
|
||||
pip install PySide2 lxml nuitka pyinstaller
|
||||
```
|
||||
111
Src/build/build_and_clean.py
Normal file
111
Src/build/build_and_clean.py
Normal 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Ошибка при сборке.")
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user