encapsulation of script parsing logic
This commit is contained in:
@@ -525,7 +525,7 @@ class WinetricksManagerDialog(QDialog):
|
|||||||
layout.addWidget(search_edit)
|
layout.addWidget(search_edit)
|
||||||
|
|
||||||
list_widget = QListWidget()
|
list_widget = QListWidget()
|
||||||
list_widget.itemChanged.connect(self._update_ui_state)
|
list_widget.itemChanged.connect(self._on_item_changed)
|
||||||
list_widget.currentItemChanged.connect(self._update_ui_state)
|
list_widget.currentItemChanged.connect(self._update_ui_state)
|
||||||
layout.addWidget(list_widget)
|
layout.addWidget(list_widget)
|
||||||
|
|
||||||
@@ -651,18 +651,8 @@ class WinetricksManagerDialog(QDialog):
|
|||||||
item = QListWidgetItem(item_text)
|
item = QListWidgetItem(item_text)
|
||||||
item.setData(Qt.UserRole, name)
|
item.setData(Qt.UserRole, name)
|
||||||
item.setFont(QFont("DejaVu Sans Mono", 10))
|
item.setFont(QFont("DejaVu Sans Mono", 10))
|
||||||
|
|
||||||
if is_checked:
|
|
||||||
# Если компонент уже установлен, делаем его неинтерактивным,
|
|
||||||
# так как удаление не поддерживается. Переустановка - через отдельную кнопку.
|
|
||||||
item.setFlags(item.flags() & ~Qt.ItemIsUserCheckable)
|
|
||||||
item.setCheckState(Qt.Checked)
|
|
||||||
item.setToolTip("Этот компонент уже установлен. Для переустановки выделите его и нажмите кнопку 'Переустановить'.")
|
|
||||||
else:
|
|
||||||
# Для неустановленных компонентов разрешаем установку через чекбокс.
|
|
||||||
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
||||||
item.setCheckState(Qt.Unchecked)
|
item.setCheckState(Qt.Checked if is_checked else Qt.Unchecked)
|
||||||
|
|
||||||
list_widget.addItem(item)
|
list_widget.addItem(item)
|
||||||
self.initial_states[name] = is_checked
|
self.initial_states[name] = is_checked
|
||||||
|
|
||||||
@@ -728,6 +718,21 @@ class WinetricksManagerDialog(QDialog):
|
|||||||
self.status_label.setText("Готово.")
|
self.status_label.setText("Готово.")
|
||||||
self._update_ui_state()
|
self._update_ui_state()
|
||||||
|
|
||||||
|
def _on_item_changed(self, item):
|
||||||
|
"""Обрабатывает изменение состояния чекбокса, предотвращая снятие галочки с установленных."""
|
||||||
|
name = item.data(Qt.UserRole)
|
||||||
|
# Если компонент был изначально установлен и пользователь пытается его снять
|
||||||
|
if name in self.initial_states and self.initial_states.get(name) is True:
|
||||||
|
if item.checkState() == Qt.Unchecked:
|
||||||
|
# Блокируем сигналы, чтобы избежать рекурсии, и возвращаем галочку на место.
|
||||||
|
list_widget = item.listWidget()
|
||||||
|
if list_widget:
|
||||||
|
list_widget.blockSignals(True)
|
||||||
|
item.setCheckState(Qt.Checked)
|
||||||
|
if list_widget:
|
||||||
|
list_widget.blockSignals(False)
|
||||||
|
self._update_ui_state()
|
||||||
|
|
||||||
def _update_ui_state(self, *args):
|
def _update_ui_state(self, *args):
|
||||||
"""Централизованно обновляет состояние кнопок 'Применить' и 'Переустановить'."""
|
"""Централизованно обновляет состояние кнопок 'Применить' и 'Переустановить'."""
|
||||||
# 1. Проверяем, есть ли изменения в чекбоксах (установка новых или снятие галочек с новых)
|
# 1. Проверяем, есть ли изменения в чекбоксах (установка новых или снятие галочек с новых)
|
||||||
@@ -919,6 +924,93 @@ class WinetricksManagerDialog(QDialog):
|
|||||||
self.log_output.append(message)
|
self.log_output.append(message)
|
||||||
self.log_output.moveCursor(QTextCursor.End)
|
self.log_output.moveCursor(QTextCursor.End)
|
||||||
|
|
||||||
|
class ScriptParser:
|
||||||
|
"""Утилитарный класс для парсинга информации из скриптов установки."""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_icons_from_script(script_path):
|
||||||
|
"""
|
||||||
|
Извлекает иконку для скрипта.
|
||||||
|
Сначала ищет переменную 'export PROG_ICON=', если не находит,
|
||||||
|
то ищет все вызовы 'create_desktop' и берет иконки из третьего аргумента.
|
||||||
|
Возвращает список имен иконок.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(script_path, 'r', encoding='utf-8') as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
|
||||||
|
# 1. Приоритет у PROG_ICON
|
||||||
|
for line in lines:
|
||||||
|
if line.strip().startswith('export PROG_ICON='):
|
||||||
|
icon_name = line.split('=', 1)[1].strip().strip('"\'')
|
||||||
|
if icon_name:
|
||||||
|
return [icon_name]
|
||||||
|
|
||||||
|
# 2. Если PROG_ICON не найден, ищем все вызовы create_desktop
|
||||||
|
icon_names = []
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
# Пропускаем закомментированные строки и пустые строки
|
||||||
|
if not line or line.startswith('#'):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if 'create_desktop' in line:
|
||||||
|
try:
|
||||||
|
parts = shlex.split(line)
|
||||||
|
# Ищем все вхождения, а не только первое
|
||||||
|
for i, part in enumerate(parts):
|
||||||
|
if part == 'create_desktop':
|
||||||
|
if len(parts) > i + 3:
|
||||||
|
icon_name = parts[i + 3]
|
||||||
|
if icon_name:
|
||||||
|
icon_names.append(icon_name)
|
||||||
|
except (ValueError, IndexError):
|
||||||
|
continue
|
||||||
|
return icon_names
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка чтения файла для извлечения иконки: {str(e)}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_prog_name_from_script(script_path):
|
||||||
|
"""Извлекает имя программы из строки PROG_NAME= в скрипте"""
|
||||||
|
try:
|
||||||
|
with open(script_path, 'r', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.strip().startswith(('export PROG_NAME=', 'PROG_NAME=')):
|
||||||
|
name = line.split('=', 1)[1].strip().strip('"\'')
|
||||||
|
if name:
|
||||||
|
return name
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка чтения файла для извлечения PROG_NAME: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_prog_url_from_script(script_path):
|
||||||
|
"""Извлекает URL из строки export PROG_URL= в скрипте"""
|
||||||
|
try:
|
||||||
|
with open(script_path, 'r', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith('export PROG_URL='):
|
||||||
|
return line.replace('export PROG_URL=', '').strip().strip('"\'')
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка чтения файла для извлечения PROG_URL: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def extract_info_ru(script_path):
|
||||||
|
"""Извлекает информацию из строки # info_ru: в скрипте"""
|
||||||
|
try:
|
||||||
|
with open(script_path, 'r', encoding='utf-8') as f:
|
||||||
|
for line in f:
|
||||||
|
if line.startswith('# info_ru:'):
|
||||||
|
return line.replace('# info_ru:', '').strip()
|
||||||
|
return "Описание отсутствует"
|
||||||
|
except Exception as e:
|
||||||
|
return f"Ошибка чтения файла: {str(e)}"
|
||||||
|
|
||||||
class WineHelperGUI(QMainWindow):
|
class WineHelperGUI(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -1235,77 +1327,6 @@ class WineHelperGUI(QMainWindow):
|
|||||||
if file_path:
|
if file_path:
|
||||||
self.install_path_edit.setText(file_path)
|
self.install_path_edit.setText(file_path)
|
||||||
|
|
||||||
def extract_icons_from_script(self, script_path):
|
|
||||||
"""
|
|
||||||
Извлекает иконку для скрипта.
|
|
||||||
Сначала ищет переменную 'export PROG_ICON=', если не находит,
|
|
||||||
то ищет все вызовы 'create_desktop' и берет иконки из третьего аргумента.
|
|
||||||
Возвращает список имен иконок.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
with open(script_path, 'r', encoding='utf-8') as f:
|
|
||||||
lines = f.readlines()
|
|
||||||
|
|
||||||
# 1. Приоритет у PROG_ICON
|
|
||||||
for line in lines:
|
|
||||||
if line.strip().startswith('export PROG_ICON='):
|
|
||||||
icon_name = line.split('=', 1)[1].strip().strip('"\'')
|
|
||||||
if icon_name:
|
|
||||||
return [icon_name]
|
|
||||||
|
|
||||||
# 2. Если PROG_ICON не найден, ищем все вызовы create_desktop
|
|
||||||
icon_names = []
|
|
||||||
for line in lines:
|
|
||||||
line = line.strip()
|
|
||||||
# Пропускаем закомментированные строки и пустые строки
|
|
||||||
if not line or line.startswith('#'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if 'create_desktop' in line:
|
|
||||||
try:
|
|
||||||
parts = shlex.split(line)
|
|
||||||
# Ищем все вхождения, а не только первое
|
|
||||||
for i, part in enumerate(parts):
|
|
||||||
if part == 'create_desktop':
|
|
||||||
if len(parts) > i + 3:
|
|
||||||
icon_name = parts[i + 3]
|
|
||||||
if icon_name:
|
|
||||||
icon_names.append(icon_name)
|
|
||||||
except (ValueError, IndexError):
|
|
||||||
continue
|
|
||||||
return icon_names
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Ошибка чтения файла для извлечения иконки: {str(e)}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def extract_prog_name_from_script(self, script_path):
|
|
||||||
"""Извлекает имя программы из строки PROG_NAME= в скрипте"""
|
|
||||||
try:
|
|
||||||
with open(script_path, 'r', encoding='utf-8') as f:
|
|
||||||
for line in f:
|
|
||||||
# Ищем строку, которая начинается с PROG_NAME= или export PROG_NAME=
|
|
||||||
if line.strip().startswith(('export PROG_NAME=', 'PROG_NAME=')):
|
|
||||||
# Отделяем имя переменной от значения и убираем кавычки
|
|
||||||
name = line.split('=', 1)[1].strip().strip('"\'')
|
|
||||||
if name:
|
|
||||||
return name
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Ошибка чтения файла для извлечения PROG_NAME: {str(e)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def extract_prog_url_from_script(self, script_path):
|
|
||||||
"""Извлекает URL из строки export PROG_URL= в скрипте"""
|
|
||||||
try:
|
|
||||||
with open(script_path, 'r', encoding='utf-8') as f:
|
|
||||||
for line in f:
|
|
||||||
if line.startswith('export PROG_URL='):
|
|
||||||
return line.replace('export PROG_URL=', '').strip().strip('"\'')
|
|
||||||
return None
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Ошибка чтения файла для извлечения PROG_URL: {str(e)}")
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _start_icon_fade_animation(self, button):
|
def _start_icon_fade_animation(self, button):
|
||||||
"""Запускает анимацию плавного перехода для иконки на кнопке с помощью QPropertyAnimation."""
|
"""Запускает анимацию плавного перехода для иконки на кнопке с помощью QPropertyAnimation."""
|
||||||
if button not in self.icon_animators:
|
if button not in self.icon_animators:
|
||||||
@@ -1426,13 +1447,13 @@ class WineHelperGUI(QMainWindow):
|
|||||||
button_index = 0
|
button_index = 0
|
||||||
for script in scripts_list:
|
for script in scripts_list:
|
||||||
script_path = os.path.join(Var.DATA_PATH, script_folder, script)
|
script_path = os.path.join(Var.DATA_PATH, script_folder, script)
|
||||||
prog_name = self.extract_prog_name_from_script(script_path)
|
prog_name = ScriptParser.extract_prog_name_from_script(script_path)
|
||||||
|
|
||||||
# Создаем кнопку, только если для скрипта указано имя программы
|
# Создаем кнопку, только если для скрипта указано имя программы
|
||||||
if not prog_name:
|
if not prog_name:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
icon_names = self.extract_icons_from_script(script_path)
|
icon_names = ScriptParser.extract_icons_from_script(script_path)
|
||||||
icon_paths = [os.path.join(Var.DATA_PATH, "image", f"{name}.png") for name in icon_names]
|
icon_paths = [os.path.join(Var.DATA_PATH, "image", f"{name}.png") for name in icon_names]
|
||||||
btn = self._create_app_button(prog_name, icon_paths, self.BUTTON_LIST_STYLE)
|
btn = self._create_app_button(prog_name, icon_paths, self.BUTTON_LIST_STYLE)
|
||||||
|
|
||||||
@@ -2224,17 +2245,6 @@ class WineHelperGUI(QMainWindow):
|
|||||||
self.installed_search_edit.text(), self.installed_buttons, self.installed_scroll_layout
|
self.installed_search_edit.text(), self.installed_buttons, self.installed_scroll_layout
|
||||||
)
|
)
|
||||||
|
|
||||||
def extract_info_ru(self, script_path):
|
|
||||||
"""Извлекает информацию из строки # info_ru: в скрипте"""
|
|
||||||
try:
|
|
||||||
with open(script_path, 'r', encoding='utf-8') as f:
|
|
||||||
for line in f:
|
|
||||||
if line.startswith('# info_ru:'):
|
|
||||||
return line.replace('# info_ru:', '').strip()
|
|
||||||
return "Описание отсутствует"
|
|
||||||
except Exception as e:
|
|
||||||
return f"Ошибка чтения файла: {str(e)}"
|
|
||||||
|
|
||||||
def show_script_info(self, script_name, button_widget):
|
def show_script_info(self, script_name, button_widget):
|
||||||
"""Показывает информацию о выбранном скрипте"""
|
"""Показывает информацию о выбранном скрипте"""
|
||||||
self._set_active_button(button_widget)
|
self._set_active_button(button_widget)
|
||||||
@@ -2264,10 +2274,10 @@ class WineHelperGUI(QMainWindow):
|
|||||||
QTimer.singleShot(0, lambda: scroll_area.ensureWidgetVisible(frame))
|
QTimer.singleShot(0, lambda: scroll_area.ensureWidgetVisible(frame))
|
||||||
|
|
||||||
# Обновляем информацию в правой панели
|
# Обновляем информацию в правой панели
|
||||||
description = self.extract_info_ru(script_path)
|
description = ScriptParser.extract_info_ru(script_path)
|
||||||
icon_names = self.extract_icons_from_script(script_path)
|
icon_names = ScriptParser.extract_icons_from_script(script_path)
|
||||||
prog_name = self.extract_prog_name_from_script(script_path)
|
prog_name = ScriptParser.extract_prog_name_from_script(script_path)
|
||||||
prog_url = self.extract_prog_url_from_script(script_path)
|
prog_url = ScriptParser.extract_prog_url_from_script(script_path)
|
||||||
display_name = prog_name if prog_name else script_name
|
display_name = prog_name if prog_name else script_name
|
||||||
self.current_display_name = display_name
|
self.current_display_name = display_name
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user