604 lines
24 KiB
Python
604 lines
24 KiB
Python
# build command
|
||
# pyinstaller --onefile --name DebugVarEdit --add-binary "build/libclang.dll;build" --distpath ./ --workpath ./build_temp --specpath ./build_temp setupVars_GUI.py
|
||
|
||
import sys
|
||
import os
|
||
import subprocess
|
||
import xml.etree.ElementTree as ET
|
||
from generateVars import type_map
|
||
from enum import IntEnum
|
||
import threading
|
||
from scanVars import run_scan
|
||
from generateVars import run_generate
|
||
from setupVars import *
|
||
from VariableSelector import *
|
||
from VariableTable import VariableTableWidget, rows
|
||
|
||
from PySide6.QtWidgets import (
|
||
QApplication, QWidget, QTableWidget, QTableWidgetItem,
|
||
QCheckBox, QComboBox, QLineEdit, QVBoxLayout, QHBoxLayout, QPushButton,
|
||
QCompleter, QAbstractItemView, QLabel, QMessageBox, QFileDialog, QTextEdit,
|
||
QDialog, QTreeWidget, QTreeWidgetItem, QSizePolicy
|
||
)
|
||
from PySide6.QtGui import QTextCursor, QKeyEvent
|
||
from PySide6.QtCore import Qt, QProcess, QObject, Signal, QSettings
|
||
|
||
|
||
|
||
class EmittingStream(QObject):
|
||
text_written = Signal(str)
|
||
|
||
def __init__(self):
|
||
super().__init__()
|
||
self._buffer = ""
|
||
|
||
def write(self, text):
|
||
self._buffer += text
|
||
while '\n' in self._buffer:
|
||
line, self._buffer = self._buffer.split('\n', 1)
|
||
# Отправляем строку без '\n'
|
||
self.text_written.emit(line)
|
||
|
||
def flush(self):
|
||
if self._buffer:
|
||
self.text_written.emit(self._buffer)
|
||
self._buffer = ""
|
||
|
||
|
||
class ProcessOutputWindowDummy(QDialog):
|
||
def __init__(self, on_done_callback):
|
||
super().__init__()
|
||
self.setWindowTitle("Поиск переменных...")
|
||
self.resize(600, 400)
|
||
self.setModal(True) # сделаем окно модальным
|
||
|
||
self.layout = QVBoxLayout(self)
|
||
self.output_edit = QTextEdit()
|
||
self.output_edit.setReadOnly(True)
|
||
self.layout.addWidget(self.output_edit)
|
||
|
||
self.btn_close = QPushButton("Закрыть")
|
||
self.btn_close.setEnabled(False)
|
||
self.layout.addWidget(self.btn_close)
|
||
|
||
self.btn_close.clicked.connect(self.__handle_done)
|
||
self._on_done_callback = on_done_callback
|
||
|
||
def __handle_done(self):
|
||
if self._on_done_callback:
|
||
self._on_done_callback()
|
||
self.accept() # закрыть диалог
|
||
|
||
def append_text(self, text):
|
||
cursor = self.output_edit.textCursor()
|
||
cursor.movePosition(QTextCursor.End)
|
||
for line in text.splitlines():
|
||
self.output_edit.append(line)
|
||
self.output_edit.setTextCursor(cursor)
|
||
self.output_edit.ensureCursorVisible()
|
||
|
||
|
||
|
||
# 3. UI: таблица с переменными
|
||
class VarEditor(QWidget):
|
||
def __init__(self):
|
||
super().__init__()
|
||
self.vars_list = []
|
||
self.structs = {}
|
||
self.typedef_map = {}
|
||
|
||
self.proj_path = None
|
||
self.xml_path = None
|
||
self.makefile_path = None
|
||
self.structs_path = None
|
||
self.output_path = None
|
||
self._updating = False # Флаг блокировки рекурсии
|
||
self._resizing = False # флаг блокировки повторного вызова
|
||
self.initUI()
|
||
|
||
def initUI(self):
|
||
self.setWindowTitle("Variable Editor")
|
||
|
||
# --- Поля ввода пути проекта и XML ---
|
||
|
||
# XML Output
|
||
xml_layout = QHBoxLayout()
|
||
xml_layout.addWidget(QLabel("XML Output:"))
|
||
self.xml_output_edit = QLineEdit()
|
||
self.xml_output_edit.returnPressed.connect(self.update)
|
||
self.xml_output_edit.textChanged.connect(self.__on_xml_path_changed)
|
||
xml_layout.addWidget(self.xml_output_edit)
|
||
btn_xml_browse = QPushButton("...")
|
||
btn_xml_browse.setFixedWidth(30)
|
||
xml_layout.addWidget(btn_xml_browse)
|
||
btn_xml_browse.clicked.connect(self.__browse_xml_output)
|
||
|
||
# Project Path
|
||
proj_layout = QHBoxLayout()
|
||
proj_layout.addWidget(QLabel("Project Path:"))
|
||
self.proj_path_edit = QLineEdit()
|
||
self.proj_path_edit.returnPressed.connect(self.update)
|
||
self.proj_path_edit.textChanged.connect(self.__on_proj_path_changed)
|
||
proj_layout.addWidget(self.proj_path_edit)
|
||
btn_proj_browse = QPushButton("...")
|
||
btn_proj_browse.setFixedWidth(30)
|
||
proj_layout.addWidget(btn_proj_browse)
|
||
btn_proj_browse.clicked.connect(self.__browse_proj_path)
|
||
|
||
# Makefile Path
|
||
makefile_layout = QHBoxLayout()
|
||
makefile_layout.addWidget(QLabel("Makefile Path (relative path):"))
|
||
self.makefile_edit = QLineEdit()
|
||
self.makefile_edit.returnPressed.connect(self.update)
|
||
self.makefile_edit.textChanged.connect(self.__on_makefile_path_changed)
|
||
makefile_layout.addWidget(self.makefile_edit)
|
||
btn_makefile_browse = QPushButton("...")
|
||
btn_makefile_browse.setFixedWidth(30)
|
||
makefile_layout.addWidget(btn_makefile_browse)
|
||
btn_makefile_browse.clicked.connect(self.__browse_makefile)
|
||
|
||
|
||
|
||
# Source Output File/Directory
|
||
source_output_layout = QHBoxLayout()
|
||
source_output_layout.addWidget(QLabel("Source Output File:"))
|
||
self.source_output_edit = QLineEdit()
|
||
source_output_layout.addWidget(self.source_output_edit)
|
||
btn_source_output_browse = QPushButton("...")
|
||
btn_source_output_browse.setFixedWidth(30)
|
||
source_output_layout.addWidget(btn_source_output_browse)
|
||
btn_source_output_browse.clicked.connect(self.__browse_source_output)
|
||
|
||
|
||
self.btn_update_vars = QPushButton("Обновить данные о переменных")
|
||
self.btn_update_vars.clicked.connect(self.update_vars_data)
|
||
|
||
# Кнопка сохранения
|
||
btn_save = QPushButton("Build")
|
||
btn_save.clicked.connect(self.save_build)
|
||
|
||
# Кнопка добавления переменных
|
||
self.btn_add_vars = QPushButton("Add Variables")
|
||
self.btn_add_vars.clicked.connect(self.__open_variable_selector)
|
||
|
||
# Кнопка открыть output-файл с выбором программы
|
||
btn_open_output = QPushButton("Открыть Output File...")
|
||
btn_open_output.clicked.connect(self.__open_output_file_with_program)
|
||
# Таблица
|
||
self.table = VariableTableWidget()
|
||
# Основной layout
|
||
layout = QVBoxLayout()
|
||
layout.addLayout(xml_layout)
|
||
layout.addLayout(proj_layout)
|
||
layout.addLayout(makefile_layout)
|
||
layout.addWidget(self.btn_update_vars)
|
||
layout.addWidget(self.table)
|
||
layout.addWidget(self.btn_add_vars)
|
||
layout.addLayout(source_output_layout)
|
||
layout.addWidget(btn_save)
|
||
layout.addWidget(btn_open_output)
|
||
|
||
self.setLayout(layout)
|
||
|
||
|
||
|
||
def get_xml_path(self):
|
||
xml_path = self.xml_output_edit.text().strip()
|
||
return xml_path
|
||
|
||
def get_proj_path(self):
|
||
proj_path = self.proj_path_edit.text().strip()
|
||
return proj_path
|
||
|
||
def get_makefile_path(self):
|
||
proj_path = self.get_proj_path()
|
||
makefile_path = make_absolute_path(self.makefile_edit.text().strip(), proj_path)
|
||
return makefile_path
|
||
|
||
def get_struct_path(self):
|
||
proj_path = self.get_proj_path()
|
||
xml_path = self.get_xml_path()
|
||
root, tree = safe_parse_xml(xml_path)
|
||
if root is None:
|
||
return
|
||
# --- structs_path из атрибута ---
|
||
structs_path = root.attrib.get('structs_path', '').strip()
|
||
structs_path_full = make_absolute_path(structs_path, proj_path)
|
||
if structs_path_full and os.path.isfile(structs_path_full):
|
||
structs_path = structs_path_full
|
||
else:
|
||
structs_path = None
|
||
return structs_path
|
||
|
||
def get_output_path(self):
|
||
output_path = os.path.abspath(self.source_output_edit.text().strip())
|
||
return output_path
|
||
|
||
def update_all_paths(self):
|
||
self.proj_path = self.get_proj_path()
|
||
self.xml_path = self.get_xml_path()
|
||
self.makefile_path = self.get_makefile_path()
|
||
self.structs_path = self.get_struct_path()
|
||
self.output_path = self.get_output_path()
|
||
|
||
|
||
def update_vars_data(self):
|
||
self.update_all_paths()
|
||
|
||
if not self.proj_path or not self.xml_path:
|
||
QMessageBox.warning(self, "Ошибка", "Укажите пути проекта и XML.")
|
||
return
|
||
|
||
if not os.path.isfile(self.makefile_path):
|
||
QMessageBox.warning(self, "Ошибка", f"Makefile не найден:\n{self.makefile_path}")
|
||
return
|
||
|
||
|
||
# Создаём окно с кнопкой "Готово"
|
||
self.proc_win = ProcessOutputWindowDummy(self.__after_scanvars_finished)
|
||
self.proc_win.show()
|
||
|
||
self.emitting_stream = EmittingStream()
|
||
self.emitting_stream.text_written.connect(self.proc_win.append_text)
|
||
|
||
def run_scan_wrapper():
|
||
try:
|
||
old_stdout = sys.stdout
|
||
sys.stdout = self.emitting_stream
|
||
|
||
run_scan(self.proj_path, self.makefile_path, self.xml_path)
|
||
|
||
except Exception as e:
|
||
self.emitting_stream.text_written.emit(f"\n[ОШИБКА] {e}")
|
||
finally:
|
||
sys.stdout = old_stdout
|
||
self.emitting_stream.text_written.emit("\n--- Анализ завершён ---")
|
||
self.proc_win.btn_close.setEnabled(True)
|
||
|
||
threading.Thread(target=run_scan_wrapper, daemon=True).start()
|
||
|
||
|
||
def save_build(self):
|
||
vars_out = []
|
||
for row in range(self.table.rowCount()):
|
||
include_cb = self.table.cellWidget(row, rows.include)
|
||
if not include_cb.isChecked():
|
||
continue
|
||
name_edit = self.table.cellWidget(row, rows.name)
|
||
pt_type_combo = self.table.cellWidget(row, rows.pt_type)
|
||
iq_combo = self.table.cellWidget(row, rows.iq_type)
|
||
ret_combo = self.table.cellWidget(row, rows.ret_type)
|
||
short_name_edit = self.table.cellWidget(row, rows.short_name)
|
||
|
||
var_data = {
|
||
'name': name_edit.text(),
|
||
'type': 'pt_' + pt_type_combo.currentText(),
|
||
'iq_type': iq_combo.currentText(),
|
||
'return_type': ret_combo.currentText() if ret_combo.currentText() else 'int',
|
||
'short_name': short_name_edit.text(),
|
||
}
|
||
vars_out.append(var_data)
|
||
|
||
self.update_all_paths()
|
||
|
||
if not self.proj_path or not self.xml_path or not self.output_path:
|
||
QMessageBox.warning(self, "Ошибка", "Заполните все пути: проект, XML и output.")
|
||
return
|
||
|
||
try:
|
||
run_generate(self.proj_path, self.xml_path, self.output_path)
|
||
QMessageBox.information(self, "Готово", "Файл debug_vars.c успешно сгенерирован.")
|
||
self.update()
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка при генерации", str(e))
|
||
|
||
|
||
def update(self):
|
||
if self._updating:
|
||
return # Уже в процессе обновления — выходим, чтобы избежать рекурсии
|
||
self._updating = True
|
||
|
||
self.update_all_paths()
|
||
try:
|
||
if self.xml_path and not os.path.isfile(self.xml_path):
|
||
return
|
||
|
||
try:
|
||
root, tree = safe_parse_xml(self.xml_path)
|
||
if root is None:
|
||
return
|
||
|
||
if not self.proj_path:
|
||
# Если в поле ничего нет, пробуем взять из XML
|
||
proj_path_from_xml = root.attrib.get('proj_path', '').strip()
|
||
if proj_path_from_xml and os.path.isdir(proj_path_from_xml):
|
||
self.proj_path = proj_path_from_xml
|
||
self.proj_path_edit.setText(proj_path_from_xml)
|
||
else:
|
||
QMessageBox.warning(
|
||
self,
|
||
"Внимание",
|
||
"Путь к проекту (proj_path) не найден или не существует.\n"
|
||
"Пожалуйста, укажите его вручную в поле 'Project Path'."
|
||
)
|
||
else:
|
||
if not os.path.isdir(self.proj_path):
|
||
QMessageBox.warning(
|
||
self,
|
||
"Внимание",
|
||
f"Указанный путь к проекту не существует:\n{self.proj_path}\n"
|
||
"Пожалуйста, исправьте путь в поле 'Project Path'."
|
||
)
|
||
|
||
|
||
if not self.makefile_path:
|
||
# --- makefile_path из атрибута ---
|
||
makefile_path = root.attrib.get('makefile_path', '').strip()
|
||
makefile_path_full = make_absolute_path(makefile_path, self.proj_path)
|
||
if makefile_path_full and os.path.isfile(makefile_path_full):
|
||
self.makefile_edit.setText(makefile_path)
|
||
else:
|
||
self.makefile_path = None
|
||
|
||
# --- structs_path из атрибута ---
|
||
structs_path = root.attrib.get('structs_path', '').strip()
|
||
structs_path_full = make_absolute_path(structs_path, self.proj_path)
|
||
if structs_path_full and os.path.isfile(structs_path_full):
|
||
self.structs_path = structs_path_full
|
||
self.structs, self.typedef_map = parse_structs(structs_path_full)
|
||
else:
|
||
self.structs_path = None
|
||
|
||
self.vars_list = parse_vars(self.xml_path, self.typedef_map)
|
||
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
|
||
except Exception as e:
|
||
QMessageBox.warning(self, "Ошибка", f"Ошибка при чтении XML:\n{e}")
|
||
|
||
|
||
finally:
|
||
self._updating = False # Снимаем блокировку при выходе из функции
|
||
|
||
def __browse_proj_path(self):
|
||
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку проекта")
|
||
if dir_path:
|
||
self.proj_path_edit.setText(dir_path)
|
||
self.proj_path = dir_path
|
||
|
||
if self.makefile_path and self.proj_path:
|
||
path = make_relative_path(self.makefile_path, self.proj_path)
|
||
self.makefile_edit.setText(path)
|
||
self.makefile_path = path
|
||
|
||
def __browse_xml_output(self):
|
||
file_path, _ = QFileDialog.getSaveFileName(
|
||
self,
|
||
"Выберите XML файл",
|
||
filter="XML files (*.xml);;All Files (*)"
|
||
)
|
||
self.xml_output_edit.setText(file_path)
|
||
self.xml_path = file_path
|
||
|
||
def keyPressEvent(self, event: QKeyEvent):
|
||
if event.key() == Qt.Key_Delete:
|
||
self.delete_selected_rows()
|
||
else:
|
||
super().keyPressEvent(event)
|
||
|
||
def __browse_makefile(self):
|
||
file_path, _ = QFileDialog.getOpenFileName(
|
||
self, "Выберите Makefile", filter="Makefile (makefile);;All Files (*)"
|
||
)
|
||
if file_path and self.proj_path:
|
||
path = make_relative_path(file_path, self.proj_path)
|
||
else:
|
||
path = file_path
|
||
self.makefile_edit.setText(path)
|
||
self.makefile_path = path
|
||
|
||
def __browse_source_output(self):
|
||
dir_path = QFileDialog.getExistingDirectory(self, "Выберите папку для debug_vars.c")
|
||
if dir_path:
|
||
self.source_output_edit.setText(dir_path)
|
||
self.output_path = dir_path
|
||
else:
|
||
self.output_path = ''
|
||
|
||
|
||
def __on_xml_path_changed(self):
|
||
self.xml_path = self.get_xml_path()
|
||
self.update()
|
||
|
||
def __on_proj_path_changed(self):
|
||
self.proj_path = self.get_proj_path()
|
||
self.update()
|
||
|
||
def __on_makefile_path_changed(self):
|
||
self.makefile_path = self.get_makefile_path()
|
||
if self.makefile_path and self.proj_path:
|
||
path = make_relative_path(self.makefile_path, self.proj_path)
|
||
self.makefile_edit.setText(path)
|
||
self.update()
|
||
|
||
|
||
def __after_scanvars_finished(self):
|
||
self.update_all_paths()
|
||
if not os.path.isfile(self.xml_path):
|
||
QMessageBox.critical(self, "Ошибка", f"Файл не найден: {self.xml_path}")
|
||
return
|
||
|
||
try:
|
||
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
|
||
|
||
# Удаляем из vars_list те, у кого show_var == true и имя совпадает
|
||
filtered_vars = [v for v in self.vars_list if v.get('show_var', 'false') == 'true']
|
||
for row in selected_rows:
|
||
if 0 <= row < len(filtered_vars):
|
||
var_to_remove = filtered_vars[row]
|
||
for v in self.vars_list:
|
||
if v['name'] == var_to_remove['name']:
|
||
v['show_var'] = 'false'
|
||
break
|
||
|
||
self.table.populate(self.vars_list, self.structs, self.write_to_xml)
|
||
|
||
|
||
def __open_variable_selector(self):
|
||
if not self.vars_list:
|
||
QMessageBox.warning(self, "Нет переменных", "Сначала загрузите или обновите переменные.")
|
||
return
|
||
|
||
dlg = VariableSelectorDialog(self.vars_list, self.structs, self.typedef_map, self.xml_path, self)
|
||
if dlg.exec():
|
||
self.write_to_xml()
|
||
self.update()
|
||
|
||
|
||
def write_to_xml(self):
|
||
self.update_all_paths()
|
||
|
||
if not self.xml_path or not os.path.isfile(self.xml_path):
|
||
print("XML файл не найден или путь пустой")
|
||
return
|
||
if not self.proj_path or not os.path.isdir(self.proj_path):
|
||
print("Project path не найден или путь пустой")
|
||
return
|
||
if not self.makefile_path or not os.path.isfile(self.makefile_path):
|
||
print("makefile файл не найден или путь пустой")
|
||
return
|
||
|
||
try:
|
||
root, tree = safe_parse_xml(self.xml_path)
|
||
if root is None:
|
||
return
|
||
|
||
|
||
root.set("proj_path", self.proj_path.replace("\\", "/"))
|
||
|
||
if self.makefile_path and os.path.isfile(self.makefile_path):
|
||
rel_makefile = make_relative_path(self.makefile_path, self.proj_path)
|
||
root.set("makefile_path", rel_makefile)
|
||
|
||
if self.structs_path and os.path.isfile(self.structs_path):
|
||
rel_struct = make_relative_path(self.structs_path, self.proj_path)
|
||
root.set("structs_path", rel_struct)
|
||
|
||
vars_elem = root.find('variables')
|
||
if vars_elem is None:
|
||
vars_elem = ET.SubElement(root, 'variables')
|
||
|
||
original_info = {}
|
||
for var_elem in vars_elem.findall('var'):
|
||
name = var_elem.attrib.get('name')
|
||
if name:
|
||
original_info[name] = {
|
||
'file': var_elem.findtext('file', ''),
|
||
'extern': var_elem.findtext('extern', ''),
|
||
'static': var_elem.findtext('static', '')
|
||
}
|
||
|
||
# Читаем переменные из таблицы (активные/изменённые)
|
||
table_vars = {v['name']: v for v in self.table.read_data()}
|
||
# Все переменные (в том числе новые, которых нет в таблице)
|
||
all_vars_by_name = {v['name']: v for v in self.vars_list}
|
||
|
||
# Объединённый список переменных для записи
|
||
all_names = list(all_vars_by_name.keys())
|
||
for name in all_names:
|
||
v = all_vars_by_name[name]
|
||
v_table = table_vars.get(name)
|
||
var_elem = None
|
||
|
||
# Ищем уже существующий <var> в XML
|
||
for ve in vars_elem.findall('var'):
|
||
if ve.attrib.get('name') == name:
|
||
var_elem = ve
|
||
break
|
||
if var_elem is None:
|
||
var_elem = ET.SubElement(vars_elem, 'var', {'name': name})
|
||
|
||
def set_sub_elem_text(parent, tag, text):
|
||
el = parent.find(tag)
|
||
if el is None:
|
||
el = ET.SubElement(parent, tag)
|
||
el.text = str(text)
|
||
|
||
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)
|
||
|
||
# Тут подтягиваем из таблицы, если есть, иначе из 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', '')
|
||
|
||
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)
|
||
|
||
|
||
ET.indent(tree, space=" ", level=0)
|
||
tree.write(self.xml_path, encoding='utf-8', xml_declaration=True)
|
||
|
||
except Exception as e:
|
||
print(f"Ошибка при сохранении XML: {e}")
|
||
|
||
def __open_output_file_with_program(self):
|
||
output_path = self.get_output_path()
|
||
if not output_path:
|
||
QMessageBox.warning(self, "Ошибка", "Путь к output-файлу не задан.")
|
||
return
|
||
|
||
output_file = os.path.join(output_path, "debug_vars.c")
|
||
if not os.path.isfile(output_file):
|
||
QMessageBox.warning(self, "Ошибка", f"Файл не найден:\n{output_file}")
|
||
return
|
||
|
||
try:
|
||
# Открыть стандартное окно Windows "Открыть с помощью..."
|
||
subprocess.run([
|
||
"rundll32.exe",
|
||
"shell32.dll,OpenAs_RunDLL",
|
||
output_file
|
||
], shell=False)
|
||
except Exception as e:
|
||
QMessageBox.critical(self, "Ошибка", f"Не удалось открыть диалог 'Открыть с помощью':\n{e}")
|
||
|
||
|
||
|
||
|
||
if __name__ == "__main__":
|
||
app = QApplication(sys.argv)
|
||
|
||
editor = VarEditor()
|
||
editor.resize(900, 600)
|
||
editor.show()
|
||
|
||
sys.exit(app.exec())
|
||
|