forked from CastroFidel/winehelper
(gui): adding type annotations
This commit is contained in:
@@ -16,22 +16,23 @@ from PyQt5.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout, QH
|
||||
from PyQt5.QtCore import Qt, QProcess, QSize, QTimer, QProcessEnvironment, QPropertyAnimation, QEasingCurve, pyqtSignal, QRect
|
||||
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QPixmap, QPainter, QCursor, QTextCharFormat, QColor, QPalette
|
||||
from PyQt5.QtNetwork import QLocalServer, QLocalSocket
|
||||
from typing import Optional, Dict, Any, Callable, List, Set, Tuple, Union
|
||||
|
||||
|
||||
class Var:
|
||||
# Переменные определяемые в скрипте winehelper
|
||||
SCRIPT_NAME = os.environ.get("SCRIPT_NAME")
|
||||
USER_WORK_PATH = os.environ.get("USER_WORK_PATH")
|
||||
RUN_SCRIPT = os.environ.get("RUN_SCRIPT")
|
||||
DATA_PATH = os.environ.get("DATA_PATH")
|
||||
CHANGELOG_FILE = os.environ.get("CHANGELOG_FILE")
|
||||
WH_ICON_PATH = os.environ.get("WH_ICON_PATH")
|
||||
WH_ICON_TRAY = os.environ.get("WH_ICON_TRAY")
|
||||
LICENSE_FILE = os.environ.get("LICENSE_FILE")
|
||||
LICENSE_AGREEMENT_FILE = os.environ.get("AGREEMENT")
|
||||
THIRD_PARTY_FILE = os.environ.get("THIRD_PARTY_FILE")
|
||||
GENERAL = os.environ.get("GENERAL")
|
||||
WH_WINETRICKS = os.environ.get("WH_WINETRICKS")
|
||||
SCRIPT_NAME: Optional[str] = os.environ.get("SCRIPT_NAME")
|
||||
USER_WORK_PATH: Optional[str] = os.environ.get("USER_WORK_PATH")
|
||||
RUN_SCRIPT: Optional[str] = os.environ.get("RUN_SCRIPT")
|
||||
DATA_PATH: Optional[str] = os.environ.get("DATA_PATH")
|
||||
CHANGELOG_FILE: Optional[str] = os.environ.get("CHANGELOG_FILE")
|
||||
WH_ICON_PATH: Optional[str] = os.environ.get("WH_ICON_PATH")
|
||||
WH_ICON_TRAY: Optional[str] = os.environ.get("WH_ICON_TRAY")
|
||||
LICENSE_FILE: Optional[str] = os.environ.get("LICENSE_FILE")
|
||||
LICENSE_AGREEMENT_FILE: Optional[str] = os.environ.get("AGREEMENT")
|
||||
THIRD_PARTY_FILE: Optional[str] = os.environ.get("THIRD_PARTY_FILE")
|
||||
GENERAL: Optional[str] = os.environ.get("GENERAL")
|
||||
WH_WINETRICKS: Optional[str] = os.environ.get("WH_WINETRICKS")
|
||||
|
||||
|
||||
class WinetricksManagerDialog(QDialog):
|
||||
@@ -46,21 +47,21 @@ class WinetricksManagerDialog(QDialog):
|
||||
|
||||
installation_complete = pyqtSignal()
|
||||
|
||||
def __init__(self, prefix_path, winetricks_path, parent=None, wine_executable=None):
|
||||
def __init__(self, prefix_path: str, winetricks_path: str, parent: Optional[QWidget] = None, wine_executable: Optional[str] = None):
|
||||
super().__init__(parent)
|
||||
self.prefix_path = prefix_path
|
||||
self.winetricks_path = winetricks_path
|
||||
self.wine_executable = wine_executable or 'wine'
|
||||
self.initial_states = {}
|
||||
self.apply_process = None
|
||||
self.installation_finished = False
|
||||
self.user_cancelled = False
|
||||
self.processes = {}
|
||||
self.category_statuses = {}
|
||||
self.previous_tab_widget = None
|
||||
self.cache_dir = os.path.join(os.path.expanduser("~"), ".cache", "winehelper", "winetricks")
|
||||
self.prefix_path: str = prefix_path
|
||||
self.winetricks_path: str = winetricks_path
|
||||
self.wine_executable: str = wine_executable or 'wine'
|
||||
self.initial_states: Dict[str, bool] = {}
|
||||
self.apply_process: Optional[QProcess] = None
|
||||
self.installation_finished: bool = False
|
||||
self.user_cancelled: bool = False
|
||||
self.processes: Dict[str, QProcess] = {}
|
||||
self.category_statuses: Dict[str, str] = {}
|
||||
self.previous_tab_widget: Optional[QWidget] = None
|
||||
self.cache_dir: str = os.path.join(os.path.expanduser("~"), ".cache", "winehelper", "winetricks")
|
||||
os.makedirs(self.cache_dir, exist_ok=True)
|
||||
self.is_reloading_after_cache_clear = False
|
||||
self.is_reloading_after_cache_clear: bool = False
|
||||
|
||||
self.setWindowTitle(f"Менеджер компонентов для префикса: {os.path.basename(prefix_path)}")
|
||||
self.setMinimumSize(800, 500)
|
||||
@@ -126,7 +127,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
# Устанавливаем начальное состояние для отслеживания покинутой вкладки
|
||||
self.previous_tab_widget = self.tabs.currentWidget()
|
||||
|
||||
def on_tab_switched(self, index):
|
||||
def on_tab_switched(self, index: int) -> None:
|
||||
"""
|
||||
Обрабатывает переключение вкладок.
|
||||
Если установка только что завершилась, сбрасывает лог к информационному тексту.
|
||||
@@ -144,7 +145,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
# Сохраняем текущую вкладку для следующего переключения
|
||||
self.previous_tab_widget = self.tabs.widget(index)
|
||||
|
||||
def _create_category_tab(self, title):
|
||||
def _create_category_tab(self, title: str) -> Tuple[QListWidget, QLineEdit]:
|
||||
"""Создает вкладку с поиском и списком."""
|
||||
tab = QWidget()
|
||||
layout = QVBoxLayout(tab)
|
||||
@@ -163,23 +164,23 @@ class WinetricksManagerDialog(QDialog):
|
||||
self.tabs.addTab(tab, title)
|
||||
return list_widget, search_edit
|
||||
|
||||
def filter_list(self, text, list_widget):
|
||||
def filter_list(self, text: str, list_widget: QListWidget) -> None:
|
||||
"""Фильтрует элементы в списке."""
|
||||
for i in range(list_widget.count()):
|
||||
item = list_widget.item(i)
|
||||
item.setHidden(text.lower() not in item.text().lower())
|
||||
|
||||
def load_all_categories(self):
|
||||
def load_all_categories(self) -> None:
|
||||
"""Запускает загрузку всех категорий."""
|
||||
self.loading_count = len(self.categories)
|
||||
for internal_name in self.categories.values():
|
||||
self._start_load_process(internal_name)
|
||||
|
||||
def _get_cache_path(self, category):
|
||||
def _get_cache_path(self, category: str) -> str:
|
||||
"""Возвращает путь к файлу кэша для указанной категории."""
|
||||
return os.path.join(self.cache_dir, f"{category}.json")
|
||||
|
||||
def _get_winetricks_hash(self):
|
||||
def _get_winetricks_hash(self) -> Optional[str]:
|
||||
"""Вычисляет хэш файла winetricks для проверки его обновления."""
|
||||
try:
|
||||
hasher = hashlib.sha256()
|
||||
@@ -190,7 +191,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
except (IOError, OSError):
|
||||
return None
|
||||
|
||||
def _start_load_process(self, category):
|
||||
def _start_load_process(self, category: str) -> None:
|
||||
"""Запускает QProcess для получения списка компонентов, используя кэш."""
|
||||
cache_path = self._get_cache_path(category)
|
||||
cache_ttl_seconds = 86400 # 24 часа
|
||||
@@ -225,9 +226,9 @@ class WinetricksManagerDialog(QDialog):
|
||||
process.finished.connect(partial(self._on_load_finished, category))
|
||||
process.start(self.winetricks_path, [category, "list"])
|
||||
|
||||
def _parse_winetricks_log(self):
|
||||
def _parse_winetricks_log(self) -> Set[str]:
|
||||
"""Читает winetricks.log и возвращает множество установленных компонентов."""
|
||||
installed_verbs = set()
|
||||
installed_verbs: Set[str] = set()
|
||||
log_path = os.path.join(self.prefix_path, "winetricks.log")
|
||||
if not os.path.exists(log_path):
|
||||
return installed_verbs
|
||||
@@ -242,7 +243,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
self._log(f"--- Предупреждение: не удалось прочитать {log_path}: {e} ---")
|
||||
return installed_verbs
|
||||
|
||||
def _parse_winetricks_list_output(self, output, installed_verbs, list_widget, category):
|
||||
def _parse_winetricks_list_output(self, output: str, installed_verbs: Set[str], list_widget: QListWidget, category: str) -> bool:
|
||||
"""Парсит вывод 'winetricks list' и заполняет QListWidget."""
|
||||
# Regex, который обрабатывает строки как с префиксом статуса '[ ]', так и без него.
|
||||
# 1. `(?:\[(.)]\s+)?` - опциональная группа для статуса (напр. '[x]').
|
||||
@@ -250,9 +251,9 @@ class WinetricksManagerDialog(QDialog):
|
||||
# 3. `(.*)` - оставшаяся часть строки (описание).
|
||||
|
||||
# Определяем шаблоны для фильтрации на основе категории
|
||||
dlls_blacklist_pattern = None
|
||||
fonts_blacklist_pattern = None
|
||||
settings_blacklist_pattern = None
|
||||
dlls_blacklist_pattern: Optional[re.Pattern] = None
|
||||
fonts_blacklist_pattern: Optional[re.Pattern] = None
|
||||
settings_blacklist_pattern: Optional[re.Pattern] = None
|
||||
|
||||
if category == 'dlls':
|
||||
# Исключаем dont_use, dxvk*, vkd3d*, galliumnine, faudio*, Foundation
|
||||
@@ -309,7 +310,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
|
||||
return found_items
|
||||
|
||||
def _on_load_finished(self, category, exit_code, exit_status, from_cache=None):
|
||||
def _on_load_finished(self, category: str, exit_code: int, exit_status: QProcess.ProcessState, from_cache: Optional[str] = None) -> None:
|
||||
"""Обрабатывает завершение загрузки списка компонентов."""
|
||||
if from_cache is not None:
|
||||
output = from_cache
|
||||
@@ -370,7 +371,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
self._log("\n=== Списки успешно обновлены ===")
|
||||
self.is_reloading_after_cache_clear = False # Сбрасываем флаг
|
||||
|
||||
def _on_item_changed(self, item):
|
||||
def _on_item_changed(self, item: QListWidgetItem) -> None:
|
||||
"""Обрабатывает изменение состояния чекбокса, предотвращая снятие галочки с установленных."""
|
||||
name = item.data(Qt.UserRole)
|
||||
# Если компонент был изначально установлен и пользователь пытается его снять
|
||||
@@ -385,7 +386,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
list_widget.blockSignals(False)
|
||||
self._update_ui_state()
|
||||
|
||||
def _update_ui_state(self, *args):
|
||||
def _update_ui_state(self, *args) -> None:
|
||||
"""Централизованно обновляет состояние кнопок 'Применить' и 'Переустановить'."""
|
||||
# 1. Проверяем, есть ли изменения в чекбоксах (установка новых или снятие галочек с новых)
|
||||
has_changes = False
|
||||
@@ -419,7 +420,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
|
||||
self.reinstall_button.setEnabled(is_reinstallable)
|
||||
|
||||
def reinstall_selected(self):
|
||||
def reinstall_selected(self) -> None:
|
||||
"""Переустанавливает выбранный компонент."""
|
||||
current_list_widget = self.tabs.currentWidget().findChild(QListWidget)
|
||||
if not current_list_widget:
|
||||
@@ -442,7 +443,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
verbs_to_reinstall = [name]
|
||||
self._start_install_process(verbs_to_reinstall)
|
||||
|
||||
def apply_changes(self):
|
||||
def apply_changes(self) -> None:
|
||||
"""Применяет выбранные изменения."""
|
||||
# Собираем все компоненты, которые были отмечены для установки.
|
||||
verbs_to_install = []
|
||||
@@ -471,7 +472,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
|
||||
self._start_install_process(verbs_to_install)
|
||||
|
||||
def _start_install_process(self, verbs_to_install):
|
||||
def _start_install_process(self, verbs_to_install: List[str]) -> None:
|
||||
"""Запускает процесс установки/переустановки winetricks."""
|
||||
# Добавляем флаг --force, чтобы разрешить переустановку
|
||||
self._log(f"Выполнение установки: winetricks --unattended --force {' '.join(verbs_to_install)}")
|
||||
@@ -485,7 +486,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
self.apply_process.finished.connect(self.on_apply_finished)
|
||||
self.apply_process.start(self.winetricks_path, ["--unattended", "--force"] + verbs_to_install)
|
||||
|
||||
def on_apply_finished(self, exit_code, exit_status):
|
||||
def on_apply_finished(self, exit_code: int, exit_status: QProcess.ProcessState) -> None:
|
||||
"""Обрабатывает завершение применения изменений."""
|
||||
# 1. Проверяем, была ли отмена пользователем
|
||||
if self.user_cancelled:
|
||||
@@ -525,7 +526,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
self.installation_complete.emit()
|
||||
self.installation_finished = True
|
||||
|
||||
def clear_winetricks_cache(self):
|
||||
def clear_winetricks_cache(self) -> None:
|
||||
"""Запускает очистку кэша Winetricks."""
|
||||
reply = self._show_message_box(
|
||||
"Очистка кэша Winetricks",
|
||||
@@ -551,12 +552,12 @@ class WinetricksManagerDialog(QDialog):
|
||||
self.cache_clear_process = QProcess(self)
|
||||
self.cache_clear_process.setProcessChannelMode(QProcess.MergedChannels)
|
||||
|
||||
def handle_output():
|
||||
def handle_output() -> None:
|
||||
output = self.cache_clear_process.readAll().data().decode('utf-8', 'ignore').strip()
|
||||
if output:
|
||||
self._log(output)
|
||||
|
||||
def handle_finish(exit_code, exit_status):
|
||||
def handle_finish(exit_code: int, exit_status: QProcess.ProcessState) -> None:
|
||||
if exit_code == 0:
|
||||
self.is_reloading_after_cache_clear = True # Устанавливаем флаг перед перезагрузкой
|
||||
self.category_statuses.clear() # Очищаем статусы перед новой загрузкой
|
||||
@@ -580,10 +581,10 @@ class WinetricksManagerDialog(QDialog):
|
||||
winehelper_path = self.parent().winehelper_path if hasattr(self.parent(), 'winehelper_path') else Var.RUN_SCRIPT
|
||||
args = ["clear-winetricks-cache", "--force"]
|
||||
|
||||
self._log(f"Выполнение: {shlex.quote(winehelper_path)} {' '.join(args)}\n")
|
||||
self._log(f"Выполнение: {shlex.quote(winehelper_path or '')} {' '.join(args)}\n")
|
||||
self.cache_clear_process.start(winehelper_path, args)
|
||||
|
||||
def closeEvent(self, event):
|
||||
def closeEvent(self, event) -> None:
|
||||
"""Обрабатывает закрытие окна, чтобы предотвратить выход во время установки."""
|
||||
# Проверяем, запущен ли процесс установки/переустановки
|
||||
if self.apply_process and self.apply_process.state() == QProcess.Running:
|
||||
@@ -602,7 +603,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
else:
|
||||
event.accept() # Процесс не запущен, можно закрывать
|
||||
|
||||
def _show_message_box(self, title, text, icon, config):
|
||||
def _show_message_box(self, title: str, text: str, icon, config: dict) -> Optional[str]:
|
||||
"""Централизованный метод для создания и показа QMessageBox."""
|
||||
msg_box = QMessageBox(self)
|
||||
msg_box.setWindowTitle(title)
|
||||
@@ -622,7 +623,7 @@ class WinetricksManagerDialog(QDialog):
|
||||
clicked_button = msg_box.clickedButton()
|
||||
return clicked_button.text() if clicked_button else None
|
||||
|
||||
def _log(self, message, color=None):
|
||||
def _log(self, message: str, color: Optional[str] = None) -> None:
|
||||
"""Добавляет сообщение в лог с возможностью указания цвета."""
|
||||
if color:
|
||||
self.log_output.append(f'<span style="color:{color};">{message}</span>')
|
||||
@@ -636,7 +637,7 @@ class ScriptParser:
|
||||
"""Утилитарный класс для парсинга информации из скриптов установки."""
|
||||
|
||||
@staticmethod
|
||||
def extract_icons_from_script(script_path):
|
||||
def extract_icons_from_script(script_path: str) -> List[str]:
|
||||
"""
|
||||
Извлекает иконку для скрипта.
|
||||
Сначала ищет переменную 'export PROG_ICON=', если не находит,
|
||||
@@ -662,7 +663,7 @@ class ScriptParser:
|
||||
return icon_names_str.split()
|
||||
|
||||
# 3. Если ничего не найдено, ищем все вызовы create_desktop
|
||||
icon_names = []
|
||||
icon_names: List[str] = []
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
# Пропускаем закомментированные строки и пустые строки
|
||||
@@ -688,7 +689,7 @@ class ScriptParser:
|
||||
return []
|
||||
|
||||
@staticmethod
|
||||
def extract_prog_name_from_script(script_path):
|
||||
def extract_prog_name_from_script(script_path: str) -> Optional[str]:
|
||||
"""Извлекает имя программы из строки PROG_NAME= в скрипте"""
|
||||
try:
|
||||
with open(script_path, 'r', encoding='utf-8') as f:
|
||||
@@ -703,7 +704,7 @@ class ScriptParser:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def extract_prog_url_from_script(script_path):
|
||||
def extract_prog_url_from_script(script_path: str) -> Optional[str]:
|
||||
"""Извлекает URL из строки export PROG_URL= в скрипте"""
|
||||
try:
|
||||
with open(script_path, 'r', encoding='utf-8') as f:
|
||||
@@ -716,7 +717,7 @@ class ScriptParser:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def extract_info_ru(script_path):
|
||||
def extract_info_ru(script_path: str) -> str:
|
||||
"""Извлекает информацию из строки # info_ru: в скрипте"""
|
||||
try:
|
||||
with open(script_path, 'r', encoding='utf-8') as f:
|
||||
@@ -730,13 +731,13 @@ class ScriptParser:
|
||||
class WineVersionSelectionDialog(QDialog):
|
||||
"""Диалог для выбора версии Wine/Proton с группировкой."""
|
||||
|
||||
def __init__(self, architecture, parent=None):
|
||||
def __init__(self, architecture: str, parent: Optional[QWidget] = None):
|
||||
super().__init__(parent)
|
||||
self.architecture = architecture
|
||||
self.selected_version = None
|
||||
self.wine_versions_data = {}
|
||||
self.system_wine_display_name = "Системная версия"
|
||||
self.selected_display_text = None
|
||||
self.architecture: str = architecture
|
||||
self.selected_version: Optional[str] = None
|
||||
self.wine_versions_data: Dict[str, List[str]] = {}
|
||||
self.system_wine_display_name: str = "Системная версия"
|
||||
self.selected_display_text: Optional[str] = None
|
||||
|
||||
self.setWindowTitle(f"Выбор версии Wine/Proton для {architecture} префикса")
|
||||
self.setMinimumSize(900, 500)
|
||||
@@ -754,7 +755,7 @@ class WineVersionSelectionDialog(QDialog):
|
||||
|
||||
self.load_versions()
|
||||
|
||||
def load_versions(self):
|
||||
def load_versions(self) -> None:
|
||||
"""Запускает процесс получения списка версий Wine."""
|
||||
self.main_tabs.clear()
|
||||
loading_widget = QWidget()
|
||||
@@ -772,16 +773,16 @@ class WineVersionSelectionDialog(QDialog):
|
||||
|
||||
self.main_tabs.setEnabled(True)
|
||||
|
||||
def _parse_sha256_list(self):
|
||||
def _parse_sha256_list(self) -> None:
|
||||
"""Парсит sha256sum.list для получения списка версий."""
|
||||
sha256_path = os.path.join(Var.DATA_PATH, "sha256sum.list")
|
||||
sha256_path = os.path.join(Var.DATA_PATH or "", "sha256sum.list")
|
||||
if not os.path.exists(sha256_path):
|
||||
QMessageBox.warning(self, "Ошибка", f"Файл с версиями не найден:\n{sha256_path}")
|
||||
self.wine_versions_data = {}
|
||||
return
|
||||
|
||||
self.wine_versions_data = {}
|
||||
current_group = None
|
||||
current_group: Optional[str] = None
|
||||
|
||||
try:
|
||||
with open(sha256_path, 'r', encoding='utf-8') as f:
|
||||
@@ -814,9 +815,9 @@ class WineVersionSelectionDialog(QDialog):
|
||||
QMessageBox.warning(self, "Ошибка", f"Не удалось прочитать файл версий:\n{e}")
|
||||
self.wine_versions_data = {}
|
||||
|
||||
def _get_installed_versions(self):
|
||||
def _get_installed_versions(self) -> List[str]:
|
||||
"""Возвращает список локально установленных версий Wine."""
|
||||
dist_path = os.path.join(Var.USER_WORK_PATH, "dist")
|
||||
dist_path = os.path.join(Var.USER_WORK_PATH or "", "dist")
|
||||
if not os.path.isdir(dist_path):
|
||||
return []
|
||||
try:
|
||||
@@ -827,7 +828,7 @@ class WineVersionSelectionDialog(QDialog):
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
def populate_ui(self):
|
||||
def populate_ui(self) -> None:
|
||||
"""Заполняет UI отфильтрованными версиями."""
|
||||
self.main_tabs.clear()
|
||||
|
||||
@@ -844,7 +845,7 @@ class WineVersionSelectionDialog(QDialog):
|
||||
self.installed_grid_layout = QGridLayout(installed_content)
|
||||
self.installed_grid_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
installed_versions_for_grid = []
|
||||
installed_versions_for_grid: List[Union[Tuple[str, str], str]] = []
|
||||
|
||||
# Системная версия
|
||||
if shutil.which('wine'):
|
||||
@@ -902,7 +903,7 @@ class WineVersionSelectionDialog(QDialog):
|
||||
|
||||
self.filter_versions()
|
||||
|
||||
def _create_version_tab(self, title, versions_list, is_download_tab=True):
|
||||
def _create_version_tab(self, title: str, versions_list: List[Union[Tuple[str, str], str]], is_download_tab: bool = True) -> None:
|
||||
"""Создает вкладку с сеткой кнопок для переданного списка версий."""
|
||||
tab_page = QWidget()
|
||||
tab_layout = QVBoxLayout(tab_page)
|
||||
@@ -922,7 +923,7 @@ class WineVersionSelectionDialog(QDialog):
|
||||
self._create_grid_buttons(grid_layout, versions_list, is_download_tab=is_download_tab)
|
||||
self.version_tabs.addTab(tab_page, title)
|
||||
|
||||
def _create_grid_buttons(self, grid_layout, versions_list, is_download_tab=False):
|
||||
def _create_grid_buttons(self, grid_layout: QGridLayout, versions_list: List[Union[Tuple[str, str], str]], is_download_tab: bool = False) -> None:
|
||||
is_win64_prefix = self.architecture == "win64"
|
||||
is_win32_prefix = self.architecture == "win32"
|
||||
re_32bit_version = re.compile(r'i[3-6]86|i586')
|
||||
@@ -961,7 +962,7 @@ class WineVersionSelectionDialog(QDialog):
|
||||
col = 0
|
||||
row += 1
|
||||
|
||||
def filter_versions(self):
|
||||
def filter_versions(self) -> None:
|
||||
"""Фильтрует видимость кнопок версий на основе текста поиска."""
|
||||
search_text = self.search_edit.text().lower()
|
||||
|
||||
@@ -978,7 +979,7 @@ class WineVersionSelectionDialog(QDialog):
|
||||
any_visible = self._filter_grid(grid_layout, search_text)
|
||||
self.version_tabs.setTabEnabled(i, any_visible)
|
||||
|
||||
def _filter_grid(self, grid_layout, search_text):
|
||||
def _filter_grid(self, grid_layout: QGridLayout, search_text: str) -> bool:
|
||||
"""Helper-функция для фильтрации кнопок в одной сетке."""
|
||||
any_visible_in_grid = False
|
||||
if not grid_layout:
|
||||
@@ -996,7 +997,7 @@ class WineVersionSelectionDialog(QDialog):
|
||||
|
||||
return any_visible_in_grid
|
||||
|
||||
def on_version_selected(self, version_name):
|
||||
def on_version_selected(self, version_name: str) -> None:
|
||||
"""Обрабатывает выбор версии."""
|
||||
self.selected_version = version_name
|
||||
if version_name == 'system':
|
||||
@@ -1008,20 +1009,20 @@ class WineVersionSelectionDialog(QDialog):
|
||||
class CreatePrefixDialog(QDialog):
|
||||
"""Диалог для создания нового префикса."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
def __init__(self, parent: Optional[QWidget] = None):
|
||||
super().__init__(parent)
|
||||
self.parent_gui = parent # Сохранить ссылку на главное окно
|
||||
self.parent_gui: Optional[QWidget] = parent # Сохранить ссылку на главное окно
|
||||
self.setWindowTitle("Создание нового префикса")
|
||||
self.setMinimumSize(680, 250)
|
||||
self.setModal(True)
|
||||
|
||||
# Attributes to store results
|
||||
self.prefix_name = None
|
||||
self.wine_arch = None
|
||||
self.base_pfx = None
|
||||
self.prepared_prefixes = {}
|
||||
self.selected_wine_version_value = None
|
||||
self.selected_wine_version_display = None
|
||||
self.prefix_name: Optional[str] = None
|
||||
self.wine_arch: Optional[str] = None
|
||||
self.base_pfx: Optional[str] = None
|
||||
self.prepared_prefixes: Dict[str, List[Tuple[str, str]]] = {'win32': [], 'win64': []}
|
||||
self.selected_wine_version_value: Optional[str] = None
|
||||
self.selected_wine_version_display: Optional[str] = None
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
form_layout = QFormLayout()
|
||||
@@ -1098,17 +1099,17 @@ class CreatePrefixDialog(QDialog):
|
||||
self._load_prepared_prefixes()
|
||||
self.on_architecture_changed()
|
||||
|
||||
def _load_prepared_prefixes(self):
|
||||
def _load_prepared_prefixes(self) -> None:
|
||||
"""Загружает и парсит шаблоны префиксов из sha256sum.list."""
|
||||
self.prepared_prefixes = {'win32': [], 'win64': []}
|
||||
sha256_file = os.path.join(Var.DATA_PATH, "sha256sum.list")
|
||||
sha256_file = os.path.join(Var.DATA_PATH or "", "sha256sum.list")
|
||||
if not os.path.exists(sha256_file):
|
||||
QMessageBox.warning(self, "Ошибка", f"Файл с описаниями префиксов не найден: {sha256_file}")
|
||||
return
|
||||
|
||||
in_prefix_section = False
|
||||
current_description = ""
|
||||
current_prefix_name = None
|
||||
current_prefix_name: Optional[str] = None
|
||||
|
||||
try:
|
||||
with open(sha256_file, 'r', encoding='utf-8') as f:
|
||||
@@ -1166,7 +1167,7 @@ class CreatePrefixDialog(QDialog):
|
||||
self.prepared_prefixes['win32'].insert(0, ('none', 'Создать чистый префикс без дополнительных библиотек'))
|
||||
self.prepared_prefixes['win64'].insert(0, ('none', 'Создать чистый префикс без дополнительных библиотек'))
|
||||
|
||||
def open_wine_version_dialog(self):
|
||||
def open_wine_version_dialog(self) -> None:
|
||||
"""Открывает диалог выбора версии Wine."""
|
||||
architecture = "win32" if self.arch_win32_radio.isChecked() else "win64"
|
||||
dialog = WineVersionSelectionDialog(architecture, self)
|
||||
@@ -1174,7 +1175,7 @@ class CreatePrefixDialog(QDialog):
|
||||
self.wine_version_edit.setText(dialog.selected_display_text)
|
||||
self.selected_wine_version_value = dialog.selected_version
|
||||
|
||||
def on_architecture_changed(self):
|
||||
def on_architecture_changed(self) -> None:
|
||||
"""Обновляет список шаблонов и сбрасывает выбор версии Wine при смене архитектуры."""
|
||||
# Сбрасываем выбор версии
|
||||
self.wine_version_edit.clear()
|
||||
@@ -1197,7 +1198,7 @@ class CreatePrefixDialog(QDialog):
|
||||
self.prefix_template_selector.currentIndexChanged.connect(lambda i: self.prefix_template_selector.setToolTip(self.prefix_template_selector.itemData(i)['tooltip']))
|
||||
self.prefix_template_selector.setToolTip(self.prefix_template_selector.itemData(0)['tooltip'])
|
||||
|
||||
def validate_prefix_name(self, text):
|
||||
def validate_prefix_name(self, text: str) -> None:
|
||||
"""Проверяет имя префикса в реальном времени и показывает/скрывает предупреждение."""
|
||||
valid_pattern = r'^[a-zA-Z0-9_-]*$'
|
||||
if re.match(valid_pattern, text):
|
||||
@@ -1251,13 +1252,13 @@ class CreatePrefixDialog(QDialog):
|
||||
class FileAssociationsDialog(QDialog):
|
||||
"""Диалог для управления ассоциациями файлов (WH_XDG_OPEN)."""
|
||||
|
||||
def __init__(self, current_associations, parent=None):
|
||||
def __init__(self, current_associations: str, parent: Optional[QWidget] = None):
|
||||
super().__init__(parent)
|
||||
self.setWindowTitle("Настройка ассоциаций файлов")
|
||||
self.setMinimumWidth(450)
|
||||
self.setModal(True)
|
||||
|
||||
self.new_associations = current_associations
|
||||
self.new_associations: str = current_associations
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setSpacing(10) # Добавляем вертикальный отступ между виджетами
|
||||
@@ -1291,7 +1292,7 @@ class FileAssociationsDialog(QDialog):
|
||||
button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(button_box)
|
||||
|
||||
def validate_and_accept(self):
|
||||
def validate_and_accept(self) -> None:
|
||||
"""Проверяет введенные данные перед закрытием."""
|
||||
forbidden_extensions = {"cpl", "dll", "exe", "lnk", "msi"}
|
||||
|
||||
@@ -1320,11 +1321,11 @@ class FileAssociationsDialog(QDialog):
|
||||
class ComponentVersionSelectionDialog(QDialog):
|
||||
"""Диалог для выбора версии компонента (DXVK, VKD3D)."""
|
||||
|
||||
def __init__(self, component_group, title, parent=None, add_extra_options=True):
|
||||
def __init__(self, component_group: str, title: str, parent: Optional[QWidget] = None, add_extra_options: bool = True):
|
||||
super().__init__(parent)
|
||||
self.component_group = component_group
|
||||
self.selected_version = None
|
||||
self.versions_data = []
|
||||
self.component_group: str = component_group
|
||||
self.selected_version: Optional[str] = None
|
||||
self.versions_data: List[str] = []
|
||||
|
||||
self.setWindowTitle(title)
|
||||
self.setMinimumSize(600, 400)
|
||||
@@ -1348,7 +1349,7 @@ class ComponentVersionSelectionDialog(QDialog):
|
||||
self.grid_layout = QGridLayout(scroll_content)
|
||||
self.grid_layout.setAlignment(Qt.AlignTop)
|
||||
|
||||
self.buttons = []
|
||||
self.buttons: List[QPushButton] = []
|
||||
# Кнопка "Удалить" теперь находится вне сетки, поэтому начинаем с 0 строки.
|
||||
self.load_versions(start_row=0)
|
||||
|
||||
@@ -1370,19 +1371,19 @@ class ComponentVersionSelectionDialog(QDialog):
|
||||
|
||||
main_layout.addLayout(button_layout)
|
||||
|
||||
def load_versions(self, start_row):
|
||||
def load_versions(self, start_row: int) -> None:
|
||||
"""Загружает и отображает версии."""
|
||||
self._parse_sha256_list()
|
||||
self.populate_ui(start_row)
|
||||
|
||||
def _parse_sha256_list(self):
|
||||
def _parse_sha256_list(self) -> None:
|
||||
"""Парсит sha256sum.list для получения списка версий."""
|
||||
sha256_path = os.path.join(Var.DATA_PATH, "sha256sum.list")
|
||||
sha256_path = os.path.join(Var.DATA_PATH or "", "sha256sum.list")
|
||||
if not os.path.exists(sha256_path):
|
||||
self.versions_data = []
|
||||
return
|
||||
|
||||
current_group = None
|
||||
current_group: Optional[str] = None
|
||||
try:
|
||||
with open(sha256_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
@@ -1403,7 +1404,7 @@ class ComponentVersionSelectionDialog(QDialog):
|
||||
except IOError:
|
||||
self.versions_data = []
|
||||
|
||||
def populate_ui(self, start_row):
|
||||
def populate_ui(self, start_row: int) -> None:
|
||||
"""Заполняет UI кнопками версий."""
|
||||
versions = sorted(self.versions_data, reverse=True)
|
||||
num_columns = 3
|
||||
@@ -1418,7 +1419,7 @@ class ComponentVersionSelectionDialog(QDialog):
|
||||
col = 0
|
||||
row += 1
|
||||
|
||||
def filter_versions(self):
|
||||
def filter_versions(self) -> None:
|
||||
"""Фильтрует видимость кнопок версий и перестраивает сетку для плотного отображения."""
|
||||
search_text = self.search_edit.text().lower()
|
||||
|
||||
@@ -1438,7 +1439,7 @@ class ComponentVersionSelectionDialog(QDialog):
|
||||
self.grid_layout.addWidget(btn_widget, row, col)
|
||||
btn_widget.setVisible(True)
|
||||
|
||||
def on_version_selected(self, version_name):
|
||||
def on_version_selected(self, version_name: str) -> None:
|
||||
"""Обрабатывает выбор версии."""
|
||||
self.selected_version = version_name
|
||||
self.accept()
|
||||
@@ -1461,7 +1462,7 @@ class WineHelperGUI(QMainWindow):
|
||||
)
|
||||
|
||||
# Стиль для кнопок в списках
|
||||
self.BUTTON_LIST_STYLE = """
|
||||
self.BUTTON_LIST_STYLE: str = """
|
||||
QPushButton {
|
||||
text-align: left;
|
||||
padding-left: 10px;
|
||||
@@ -1473,12 +1474,12 @@ class WineHelperGUI(QMainWindow):
|
||||
}
|
||||
"""
|
||||
|
||||
self.INSTALLED_BUTTON_LIST_STYLE = self.BUTTON_LIST_STYLE.replace(
|
||||
self.INSTALLED_BUTTON_LIST_STYLE: str = self.BUTTON_LIST_STYLE.replace(
|
||||
"padding-left: 10px;", "padding-left: 15px;"
|
||||
)
|
||||
|
||||
# Стиль для кнопок тестовых программ
|
||||
self.TEST_BUTTON_LIST_STYLE = """
|
||||
self.TEST_BUTTON_LIST_STYLE: str = """
|
||||
QPushButton {
|
||||
background-color: #ffdc64; /* Более темный желтый фон */
|
||||
color: black; /* Черный цвет текста для контраста */
|
||||
@@ -1491,40 +1492,40 @@ class WineHelperGUI(QMainWindow):
|
||||
"""
|
||||
|
||||
# Стили для оберток кнопок (для рамки выделения)
|
||||
self.FRAME_STYLE_DEFAULT = "QFrame { border: 2px solid transparent; border-radius: 8px; padding: 0px; }"
|
||||
self.FRAME_STYLE_SELECTED = "QFrame { border: 2px solid #0078d7; border-radius: 8px; padding: 0px; }"
|
||||
self.FRAME_STYLE_DEFAULT: str = "QFrame { border: 2px solid transparent; border-radius: 8px; padding: 0px; }"
|
||||
self.FRAME_STYLE_SELECTED: str = "QFrame { border: 2px solid #0078d7; border-radius: 8px; padding: 0px; }"
|
||||
|
||||
# Стили для кнопок Запустить/Остановить
|
||||
self.RUN_BUTTON_STYLE = """
|
||||
self.RUN_BUTTON_STYLE: str = """
|
||||
QPushButton {
|
||||
background-color: #4CAF50; color: white;
|
||||
font-weight: bold;
|
||||
}
|
||||
"""
|
||||
self.STOP_BUTTON_STYLE = """
|
||||
self.STOP_BUTTON_STYLE: str = """
|
||||
QPushButton { background-color: #d32f2f; color: white; font-weight: bold; }
|
||||
"""
|
||||
|
||||
# Основные переменные
|
||||
self.winehelper_path = Var.RUN_SCRIPT
|
||||
self.process = None
|
||||
self.current_script = None
|
||||
self.install_process = None
|
||||
self.current_display_name = None
|
||||
self.install_dialog = None
|
||||
self.current_active_button = None
|
||||
self.installed_buttons = []
|
||||
self.install_tabs_data = {}
|
||||
self.running_apps = {} # {desktop_path: QProcess}
|
||||
self.current_selected_app = None
|
||||
self.icon_animators = {}
|
||||
self.previous_tab_index = 0
|
||||
self.current_managed_prefix_name = None # Имя префикса, выбранного в выпадающем списке
|
||||
self.prefixes_before_install = set()
|
||||
self.winehelper_path: str = Var.RUN_SCRIPT
|
||||
self.process: Optional[QProcess] = None
|
||||
self.current_script: Optional[str] = None
|
||||
self.install_process: Optional[QProcess] = None
|
||||
self.current_display_name: Optional[str] = None
|
||||
self.install_dialog: Optional[QDialog] = None
|
||||
self.current_active_button: Optional[QPushButton] = None
|
||||
self.installed_buttons: List[QPushButton] = []
|
||||
self.install_tabs_data: Dict[str, Dict[str, Any]] = {}
|
||||
self.running_apps: Dict[str, QProcess] = {} # {desktop_path: QProcess}
|
||||
self.current_selected_app: Optional[Dict[str, Any]] = None
|
||||
self.icon_animators: Dict[QPushButton, Dict[str, Any]] = {}
|
||||
self.previous_tab_index: int = 0
|
||||
self.current_managed_prefix_name: Optional[str] = None # Имя префикса, выбранного в выпадающем списке
|
||||
self.prefixes_before_install: Set[str] = set()
|
||||
|
||||
self.is_quitting = False # Флаг для корректного выхода из приложения
|
||||
self.command_output_buffer = ""
|
||||
self.command_last_line_was_progress = False
|
||||
self.is_quitting: bool = False # Флаг для корректного выхода из приложения
|
||||
self.command_output_buffer: str = ""
|
||||
self.command_last_line_was_progress: bool = False
|
||||
# Создаем главный виджет и layout
|
||||
self.main_widget = QWidget()
|
||||
self.setCentralWidget(self.main_widget)
|
||||
@@ -1998,7 +1999,7 @@ class WineHelperGUI(QMainWindow):
|
||||
button_list.append(btn)
|
||||
button_index += 1
|
||||
|
||||
def _create_searchable_grid_tab(self, placeholder_text, filter_slot, add_stretch=True):
|
||||
def _create_searchable_grid_tab(self, placeholder_text: str, filter_slot: Callable, add_stretch: bool = True) -> Tuple[QWidget, QGridLayout, QLineEdit, QScrollArea]:
|
||||
"""
|
||||
Создает стандартную вкладку с полем поиска и сеточным макетом с прокруткой.
|
||||
Возвращает кортеж (главный виджет вкладки, сеточный макет, поле поиска, область прокрутки).
|
||||
@@ -2057,7 +2058,7 @@ class WineHelperGUI(QMainWindow):
|
||||
|
||||
return tab_widget, grid_layout, search_edit, scroll_area
|
||||
|
||||
def _create_and_populate_install_tab(self, tab_title, script_folders, search_placeholder, filter_slot):
|
||||
def _create_and_populate_install_tab(self, tab_title: str, script_folders: List[str], search_placeholder: str, filter_slot: Callable) -> Tuple[List[str], List[QPushButton], QGridLayout, QLineEdit, QScrollArea]:
|
||||
"""
|
||||
Создает и заполняет вкладку для установки (автоматической или ручной).
|
||||
Возвращает кортеж со скриптами, кнопками и виджетами.
|
||||
@@ -2066,10 +2067,10 @@ class WineHelperGUI(QMainWindow):
|
||||
search_placeholder, filter_slot
|
||||
)
|
||||
|
||||
scripts = []
|
||||
buttons_list = []
|
||||
scripts: List[str] = []
|
||||
buttons_list: List[QPushButton] = []
|
||||
for folder in script_folders:
|
||||
script_path = os.path.join(Var.DATA_PATH, folder)
|
||||
script_path = os.path.join(Var.DATA_PATH or "", folder)
|
||||
if os.path.isdir(script_path):
|
||||
try:
|
||||
folder_scripts = sorted(os.listdir(script_path))
|
||||
@@ -2082,7 +2083,7 @@ class WineHelperGUI(QMainWindow):
|
||||
|
||||
return scripts, buttons_list, grid_layout, search_edit, scroll_area
|
||||
|
||||
def create_auto_install_tab(self):
|
||||
def create_auto_install_tab(self) -> None:
|
||||
"""Создает вкладку для автоматической установки программ"""
|
||||
(
|
||||
scripts, buttons, layout,
|
||||
@@ -2090,7 +2091,7 @@ class WineHelperGUI(QMainWindow):
|
||||
) = self._create_and_populate_install_tab(
|
||||
"Автоматическая установка", ["autoinstall"], "Поиск скрипта автоматической установки...", partial(self.filter_buttons, 'auto')
|
||||
)
|
||||
self.autoinstall_scripts = scripts
|
||||
self.autoinstall_scripts: List[str] = scripts
|
||||
self.install_tabs_data['auto'] = {
|
||||
'buttons': buttons, 'layout': layout, 'search_edit': search_edit, 'scroll_area': scroll_area
|
||||
}
|
||||
@@ -2111,7 +2112,7 @@ class WineHelperGUI(QMainWindow):
|
||||
# Сохраняем чекбокс для доступа в будущем
|
||||
self.install_tabs_data['auto']['test_checkbox'] = test_checkbox
|
||||
|
||||
def create_manual_install_tab(self):
|
||||
def create_manual_install_tab(self) -> None:
|
||||
"""Создает вкладку для ручной установки программ"""
|
||||
(
|
||||
scripts, buttons, layout,
|
||||
@@ -2119,12 +2120,12 @@ class WineHelperGUI(QMainWindow):
|
||||
) = self._create_and_populate_install_tab(
|
||||
"Ручная установка", ["manualinstall"], "Поиск скрипта ручной установки...", partial(self.filter_buttons, 'manual')
|
||||
)
|
||||
self.manualinstall_scripts = scripts
|
||||
self.manualinstall_scripts: List[str] = scripts
|
||||
self.install_tabs_data['manual'] = {
|
||||
'buttons': buttons, 'layout': layout, 'search_edit': search_edit, 'scroll_area': scroll_area
|
||||
}
|
||||
|
||||
def update_auto_install_list(self):
|
||||
def update_auto_install_list(self) -> None:
|
||||
"""Обновляет список на вкладке 'Автоматическая установка' при изменении чекбокса."""
|
||||
data = self.install_tabs_data.get('auto')
|
||||
if not data:
|
||||
@@ -2136,12 +2137,12 @@ class WineHelperGUI(QMainWindow):
|
||||
# Если нужно показать тестовые версии и они еще не добавлены
|
||||
if is_checked and not test_buttons:
|
||||
test_script_folder = "testinstall"
|
||||
script_path = os.path.join(Var.DATA_PATH, test_script_folder)
|
||||
script_path = os.path.join(Var.DATA_PATH or "", test_script_folder)
|
||||
if os.path.isdir(script_path):
|
||||
try:
|
||||
folder_scripts = sorted(os.listdir(script_path))
|
||||
# Запоминаем, какие кнопки являются тестовыми
|
||||
new_test_buttons = []
|
||||
new_test_buttons: List[QPushButton] = []
|
||||
self._populate_install_grid(data['layout'], folder_scripts, test_script_folder, new_test_buttons)
|
||||
data['test_buttons'] = new_test_buttons
|
||||
data['buttons'].extend(new_test_buttons)
|
||||
@@ -2187,20 +2188,20 @@ class WineHelperGUI(QMainWindow):
|
||||
# Очищаем список тестовых кнопок
|
||||
data['test_buttons'].clear()
|
||||
# Обновляем список скриптов
|
||||
self.autoinstall_scripts = [s for s in self.autoinstall_scripts if not os.path.exists(os.path.join(Var.DATA_PATH, "testinstall", s))]
|
||||
self.autoinstall_scripts = [s for s in self.autoinstall_scripts if not os.path.exists(os.path.join(Var.DATA_PATH or "", "testinstall", s))]
|
||||
|
||||
# В любом случае применяем фильтр, чтобы скрыть/показать кнопки в соответствии с поиском
|
||||
if data['test_checkbox'].isChecked():
|
||||
self.filter_buttons('auto')
|
||||
|
||||
def create_installed_tab(self):
|
||||
def create_installed_tab(self) -> None:
|
||||
"""Создает вкладку для отображения установленных программ в виде кнопок"""
|
||||
installed_tab, self.installed_scroll_layout, self.installed_search_edit, self.installed_scroll_area = self._create_searchable_grid_tab(
|
||||
"Поиск установленной программы...", self.filter_installed_buttons, add_stretch=True
|
||||
)
|
||||
self.add_tab(installed_tab, "Установленные")
|
||||
|
||||
def create_prefix_tab(self):
|
||||
def create_prefix_tab(self) -> None:
|
||||
"""Создает вкладку 'Менеджер префиксов'"""
|
||||
self.prefix_tab = QWidget()
|
||||
layout = QVBoxLayout(self.prefix_tab)
|
||||
|
||||
Reference in New Issue
Block a user