From 3bfdf5c01ac866f025d4631e6f0980b52cbcbfe2 Mon Sep 17 00:00:00 2001 From: Sergey Palcheh Date: Fri, 15 Aug 2025 10:48:43 +0600 Subject: [PATCH] encapsulation of script parsing logic --- winehelper_gui.py | 212 ++++++++++++++++++++++++---------------------- 1 file changed, 111 insertions(+), 101 deletions(-) diff --git a/winehelper_gui.py b/winehelper_gui.py index 39b2c2d..330e267 100644 --- a/winehelper_gui.py +++ b/winehelper_gui.py @@ -525,7 +525,7 @@ class WinetricksManagerDialog(QDialog): layout.addWidget(search_edit) 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) layout.addWidget(list_widget) @@ -651,18 +651,8 @@ class WinetricksManagerDialog(QDialog): item = QListWidgetItem(item_text) item.setData(Qt.UserRole, name) 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.setCheckState(Qt.Unchecked) - + item.setFlags(item.flags() | Qt.ItemIsUserCheckable) + item.setCheckState(Qt.Checked if is_checked else Qt.Unchecked) list_widget.addItem(item) self.initial_states[name] = is_checked @@ -728,6 +718,21 @@ class WinetricksManagerDialog(QDialog): self.status_label.setText("Готово.") 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): """Централизованно обновляет состояние кнопок 'Применить' и 'Переустановить'.""" # 1. Проверяем, есть ли изменения в чекбоксах (установка новых или снятие галочек с новых) @@ -919,6 +924,93 @@ class WinetricksManagerDialog(QDialog): self.log_output.append(message) 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): def __init__(self): super().__init__() @@ -1235,77 +1327,6 @@ class WineHelperGUI(QMainWindow): if 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): """Запускает анимацию плавного перехода для иконки на кнопке с помощью QPropertyAnimation.""" if button not in self.icon_animators: @@ -1426,13 +1447,13 @@ class WineHelperGUI(QMainWindow): button_index = 0 for script in scripts_list: 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: 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] 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 ) - 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): """Показывает информацию о выбранном скрипте""" self._set_active_button(button_widget) @@ -2264,10 +2274,10 @@ class WineHelperGUI(QMainWindow): QTimer.singleShot(0, lambda: scroll_area.ensureWidgetVisible(frame)) # Обновляем информацию в правой панели - description = self.extract_info_ru(script_path) - icon_names = self.extract_icons_from_script(script_path) - prog_name = self.extract_prog_name_from_script(script_path) - prog_url = self.extract_prog_url_from_script(script_path) + description = ScriptParser.extract_info_ru(script_path) + icon_names = ScriptParser.extract_icons_from_script(script_path) + prog_name = ScriptParser.extract_prog_name_from_script(script_path) + prog_url = ScriptParser.extract_prog_url_from_script(script_path) display_name = prog_name if prog_name else script_name self.current_display_name = display_name