forked from Boria138/PortProtonQt
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			84d5e46a74
			...
			fdd5a0a3d5
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| fdd5a0a3d5 | |||
| 792e52d981 | 
| @@ -21,9 +21,9 @@ Current translation status: | ||||
|  | ||||
| | Locale | Progress | Translated | | ||||
| | :----- | -------: | ---------: | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 241 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 241 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 241 of 241 | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 247 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 247 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 247 of 247 | | ||||
|  | ||||
| --- | ||||
|  | ||||
|   | ||||
| @@ -21,9 +21,9 @@ | ||||
|  | ||||
| | Локаль | Прогресс | Переведено | | ||||
| | :----- | -------: | ---------: | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 241 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 241 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 241 из 241 | | ||||
| | [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 247 | | ||||
| | [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 247 | | ||||
| | [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 247 из 247 | | ||||
|  | ||||
| --- | ||||
|  | ||||
|   | ||||
| @@ -91,6 +91,130 @@ def generate_thumbnail(inputfile, outfile, size=128, force_resize=True): | ||||
|         logger.error(f"Ошибка при сохранении миниатюры: {e}") | ||||
|         return False | ||||
|  | ||||
| def create_dialog_hints_widget(theme, main_window, input_manager, context='default'): | ||||
|     """ | ||||
|     Common function to create hints widget for all dialogs. | ||||
|     Uses main_window for get_button_icon/get_nav_icon, input_manager for gamepad detection. | ||||
|     """ | ||||
|     theme_manager = ThemeManager() | ||||
|     current_theme_name = read_theme_from_config() | ||||
|  | ||||
|     hintsWidget = QWidget() | ||||
|     hintsWidget.setStyleSheet(theme.STATUS_BAR_STYLE) | ||||
|     hintsLayout = QHBoxLayout(hintsWidget) | ||||
|     hintsLayout.setContentsMargins(10, 0, 10, 0) | ||||
|     hintsLayout.setSpacing(20) | ||||
|  | ||||
|     dialog_actions = [] | ||||
|  | ||||
|     # Context-specific actions (gamepad only, no keyboard) | ||||
|     if context == 'file_explorer': | ||||
|         dialog_actions = [ | ||||
|             ("confirm", _("Open")),        # A / Cross | ||||
|             ("add_game", _("Select Dir")), # X / Triangle | ||||
|             ("prev_dir", _("Prev Dir")),   # Y / Square | ||||
|             ("back", _("Cancel")),         # B / Circle | ||||
|             ("context_menu", _("Menu")),   # Start / Options | ||||
|         ] | ||||
|     elif context == 'winetricks': | ||||
|         dialog_actions = [ | ||||
|             ("confirm", _("Toggle")),         # A / Cross | ||||
|             ("add_game", _("Install")),       # X / Triangle | ||||
|             ("prev_dir", _("Force Install")), # Y / Square | ||||
|             ("back", _("Cancel")),            # B / Circle | ||||
|             ("prev_tab", _("Prev Tab")),      # LB / L1 | ||||
|             ("next_tab", _("Next Tab")),      # RB / R1 | ||||
|         ] | ||||
|  | ||||
|     hints_labels = []  # Store for updates (returned for class storage) | ||||
|  | ||||
|     def make_hint(icon_name, text, action=None): | ||||
|         container = QWidget() | ||||
|         hlayout = QHBoxLayout(container) | ||||
|         hlayout.setContentsMargins(0, 5, 0, 0) | ||||
|         hlayout.setSpacing(6) | ||||
|  | ||||
|         icon_label = QLabel() | ||||
|         icon_label.setFixedSize(26, 26) | ||||
|         icon_label.setAlignment(Qt.AlignmentFlag.AlignCenter) | ||||
|  | ||||
|         pixmap = QPixmap() | ||||
|         icon_path = theme_manager.get_theme_image(icon_name, current_theme_name) | ||||
|         if icon_path: | ||||
|             pixmap.load(str(icon_path)) | ||||
|         if not pixmap.isNull(): | ||||
|             icon_label.setPixmap(pixmap.scaled(26, 26, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) | ||||
|  | ||||
|         hlayout.addWidget(icon_label) | ||||
|  | ||||
|         text_label = QLabel(text) | ||||
|         text_label.setStyleSheet(theme.LAST_LAUNCH_VALUE_STYLE) | ||||
|         text_label.setAlignment(Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft) | ||||
|         hlayout.addWidget(text_label) | ||||
|  | ||||
|         # Initially hidden; show only if gamepad connected | ||||
|         container.setVisible(False) | ||||
|         hints_labels.append((container, icon_label, action)) | ||||
|  | ||||
|         hintsLayout.addWidget(container) | ||||
|  | ||||
|     # Add gamepad hints only | ||||
|     for action, text in dialog_actions: | ||||
|         make_hint("placeholder", text, action) | ||||
|  | ||||
|     hintsLayout.addStretch() | ||||
|  | ||||
|     # Return widget and labels for class storage | ||||
|     return hintsWidget, hints_labels | ||||
|  | ||||
| def update_dialog_hints(hints_labels, main_window, input_manager, theme_manager, current_theme_name): | ||||
|     """ | ||||
|     Common function to update hints for any dialog. | ||||
|     """ | ||||
|     if not input_manager or not main_window: | ||||
|         # Hide all if no input_manager or main_window | ||||
|         for container, _, _ in hints_labels: | ||||
|             container.setVisible(False) | ||||
|         return | ||||
|  | ||||
|     is_gamepad = input_manager.gamepad is not None | ||||
|     if not is_gamepad: | ||||
|         # Hide all hints if no gamepad | ||||
|         for container, _, _ in hints_labels: | ||||
|             container.setVisible(False) | ||||
|         return | ||||
|  | ||||
|     gtype = input_manager.gamepad_type | ||||
|     gamepad_actions = ['confirm', 'back', 'context_menu', 'add_game', 'prev_dir', 'prev_tab', 'next_tab'] | ||||
|  | ||||
|     for container, icon_label, action in hints_labels: | ||||
|         if action and action in gamepad_actions: | ||||
|             container.setVisible(True) | ||||
|             # Update icon using main_window methods | ||||
|             if action in ['confirm', 'back', 'context_menu', 'add_game', 'prev_dir']: | ||||
|                 icon_name = main_window.get_button_icon(action, gtype) | ||||
|             else:  # only prev_tab/next_tab (treat as nav) | ||||
|                 direction = 'left' if action == 'prev_tab' else 'right' | ||||
|                 icon_name = main_window.get_nav_icon(direction, gtype) | ||||
|             icon_path = theme_manager.get_theme_image(icon_name, current_theme_name) | ||||
|             pixmap = QPixmap() | ||||
|             if icon_path: | ||||
|                 pixmap.load(str(icon_path)) | ||||
|             if not pixmap.isNull(): | ||||
|                 icon_label.setPixmap(pixmap.scaled( | ||||
|                     26, 26, | ||||
|                     Qt.AspectRatioMode.KeepAspectRatio, | ||||
|                     Qt.TransformationMode.SmoothTransformation | ||||
|                 )) | ||||
|             else: | ||||
|                 # Fallback to placeholder | ||||
|                 placeholder = theme_manager.get_theme_image("placeholder", current_theme_name) | ||||
|                 if placeholder: | ||||
|                     pixmap.load(str(placeholder)) | ||||
|                     icon_label.setPixmap(pixmap.scaled(26, 26, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation)) | ||||
|         else: | ||||
|             container.setVisible(False) | ||||
|  | ||||
| class FileSelectedSignal(QObject): | ||||
|     file_selected = Signal(str)  # Сигнал с путем к выбранному файлу | ||||
|  | ||||
| @@ -185,6 +309,7 @@ class FileExplorer(QDialog): | ||||
|         self.initial_path = initial_path  # Store initial path if provided | ||||
|         self.thumbnail_cache = {}  # Cache for loaded thumbnails | ||||
|         self.pending_thumbnails = set()  # Track files pending thumbnail loading | ||||
|         self.main_window = None  # Add reference to MainWindow | ||||
|         self.setup_ui() | ||||
|  | ||||
|         # Window settings | ||||
| @@ -198,6 +323,7 @@ class FileExplorer(QDialog): | ||||
|         while parent: | ||||
|             if hasattr(parent, 'input_manager'): | ||||
|                 self.input_manager = cast("MainWindow", parent).input_manager | ||||
|                 self.main_window = parent | ||||
|             if hasattr(parent, 'context_menu_manager'): | ||||
|                 self.context_menu_manager = cast("MainWindow", parent).context_menu_manager | ||||
|             parent = parent.parent() | ||||
| @@ -214,6 +340,17 @@ class FileExplorer(QDialog): | ||||
|             self.current_path = os.path.expanduser("~")  # Fallback to home if initial path is invalid | ||||
|         self.update_file_list() | ||||
|  | ||||
|         # Create hints widget using common function | ||||
|         self.current_theme_name = read_theme_from_config() | ||||
|         self.hints_widget, self.hints_labels = create_dialog_hints_widget(self.theme, self.main_window, self.input_manager, context='file_explorer') | ||||
|         self.main_layout.addWidget(self.hints_widget) | ||||
|  | ||||
|         # Connect signals | ||||
|         if self.input_manager: | ||||
|             self.input_manager.button_event.connect(lambda *args: update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name)) | ||||
|             self.input_manager.dpad_moved.connect(lambda *args: update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name)) | ||||
|             update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name) | ||||
|  | ||||
|     class ThumbnailLoader(QRunnable): | ||||
|         """Class for asynchronous thumbnail loading in a separate thread.""" | ||||
|         class Signals(QObject): | ||||
| @@ -1037,8 +1174,6 @@ Icon={icon_path} | ||||
|         return desktop_entry, desktop_path | ||||
|  | ||||
| class WinetricksDialog(QDialog): | ||||
|     """Dialog for managing Winetricks components in a prefix.""" | ||||
|  | ||||
|     def __init__(self, parent=None, theme=None, prefix_path: str | None = None, wine_use: str | None = None): | ||||
|         super().__init__(parent) | ||||
|         self.theme = theme if theme else theme_manager.apply_theme(read_theme_from_config()) | ||||
| @@ -1071,6 +1206,36 @@ class WinetricksDialog(QDialog): | ||||
|         self.setup_ui() | ||||
|         self.load_lists() | ||||
|  | ||||
|         # Find input_manager and main_window | ||||
|         self.input_manager = None | ||||
|         self.main_window = None | ||||
|         parent = self.parent() | ||||
|         while parent: | ||||
|             if hasattr(parent, 'input_manager'): | ||||
|                 self.input_manager = cast("MainWindow", parent).input_manager | ||||
|                 self.main_window = parent | ||||
|             parent = parent.parent() | ||||
|  | ||||
|         self.current_theme_name = read_theme_from_config() | ||||
|  | ||||
|         # Enable Winetricks-specific mode | ||||
|         if self.input_manager: | ||||
|             self.input_manager.enable_winetricks_mode(self) | ||||
|  | ||||
|         # Create hints widget using common function | ||||
|         self.hints_widget, self.hints_labels = create_dialog_hints_widget(self.theme, self.main_window, self.input_manager, context='winetricks') | ||||
|         self.main_layout.addWidget(self.hints_widget) | ||||
|  | ||||
|         # Connect signals (use self.theme_manager) | ||||
|         if self.input_manager: | ||||
|             self.input_manager.button_event.connect( | ||||
|                 lambda *args: update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name) | ||||
|             ) | ||||
|             self.input_manager.dpad_moved.connect( | ||||
|                 lambda *args: update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name) | ||||
|             ) | ||||
|             update_dialog_hints(self.hints_labels, self.main_window, self.input_manager, theme_manager, self.current_theme_name) | ||||
|  | ||||
|     def update_winetricks(self): | ||||
|         """Update the winetricks script.""" | ||||
|         if not self.downloader.has_internet(): | ||||
| @@ -1143,15 +1308,15 @@ class WinetricksDialog(QDialog): | ||||
|  | ||||
|     def setup_ui(self): | ||||
|         """Set up the user interface with tabs and tables.""" | ||||
|         main_layout = QVBoxLayout(self) | ||||
|         main_layout.setContentsMargins(10, 10, 10, 10) | ||||
|         main_layout.setSpacing(10) | ||||
|         self.main_layout = QVBoxLayout(self) | ||||
|         self.main_layout.setContentsMargins(10, 10, 10, 10) | ||||
|         self.main_layout.setSpacing(10) | ||||
|  | ||||
|         # Log output | ||||
|         self.log_output = QTextEdit() | ||||
|         self.log_output.setReadOnly(True) | ||||
|         self.log_output.setStyleSheet(self.theme.WINETRICKS_LOG_STYLE) | ||||
|         main_layout.addWidget(self.log_output) | ||||
|         self.main_layout.addWidget(self.log_output) | ||||
|  | ||||
|         # Tab widget | ||||
|         self.tab_widget = QTabWidget() | ||||
| @@ -1258,7 +1423,7 @@ class WinetricksDialog(QDialog): | ||||
|             "settings": self.settings_container | ||||
|         } | ||||
|  | ||||
|         main_layout.addWidget(self.tab_widget) | ||||
|         self.main_layout.addWidget(self.tab_widget) | ||||
|  | ||||
|         # Buttons | ||||
|         button_layout = QHBoxLayout() | ||||
| @@ -1272,7 +1437,7 @@ class WinetricksDialog(QDialog): | ||||
|         button_layout.addWidget(self.cancel_button) | ||||
|         button_layout.addWidget(self.force_button) | ||||
|         button_layout.addWidget(self.install_button) | ||||
|         main_layout.addLayout(button_layout) | ||||
|         self.main_layout.addLayout(button_layout) | ||||
|  | ||||
|         self.cancel_button.clicked.connect(self.reject) | ||||
|         self.force_button.clicked.connect(lambda: self.install_selected(force=True)) | ||||
| @@ -1497,3 +1662,15 @@ class WinetricksDialog(QDialog): | ||||
|         """Добавляет в лог.""" | ||||
|         self.log_output.append(message) | ||||
|         self.log_output.moveCursor(QTextCursor.MoveOperation.End) | ||||
|  | ||||
|     def closeEvent(self, event): | ||||
|         """Disable mode on close.""" | ||||
|         if self.input_manager: | ||||
|             self.input_manager.disable_winetricks_mode() | ||||
|         super().closeEvent(event) | ||||
|  | ||||
|     def reject(self): | ||||
|         """Disable mode on reject.""" | ||||
|         if self.input_manager: | ||||
|             self.input_manager.disable_winetricks_mode() | ||||
|         super().reject() | ||||
|   | ||||
| @@ -5,7 +5,7 @@ from typing import Protocol, cast | ||||
| from evdev import InputDevice, InputEvent, ecodes, list_devices, ff | ||||
| from enum import Enum | ||||
| from pyudev import Context, Monitor, MonitorObserver, Device | ||||
| from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView, QMessageBox, QListWidget, QTableWidget, QAbstractItemView | ||||
| from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView, QMessageBox, QListWidget, QTableWidget, QAbstractItemView, QTableWidgetItem | ||||
| from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot, QTimer | ||||
| from PySide6.QtGui import QKeyEvent, QMouseEvent | ||||
| from portprotonqt.logger import get_logger | ||||
| @@ -13,7 +13,7 @@ from portprotonqt.image_utils import FullscreenDialog | ||||
| from portprotonqt.custom_widgets import NavLabel, AutoSizeButton | ||||
| from portprotonqt.game_card import GameCard | ||||
| from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config, read_gamepad_type | ||||
| from portprotonqt.dialogs import AddGameDialog, WinetricksDialog | ||||
| from portprotonqt.dialogs import AddGameDialog | ||||
| from portprotonqt.virtual_keyboard import VirtualKeyboard | ||||
|  | ||||
| logger = get_logger(__name__) | ||||
| @@ -455,6 +455,171 @@ class InputManager(QObject): | ||||
|         except Exception as e: | ||||
|             logger.error("Error in FileExplorer dpad handler: %s", e) | ||||
|  | ||||
|     def enable_winetricks_mode(self, winetricks_dialog): | ||||
|         """Setup gamepad handling for WinetricksDialog""" | ||||
|         try: | ||||
|             self.winetricks_dialog = winetricks_dialog | ||||
|             self.original_button_handler = self.handle_button_slot | ||||
|             self.original_dpad_handler = self.handle_dpad_slot | ||||
|             self.original_gamepad_state = self._gamepad_handling_enabled | ||||
|             self.handle_button_slot = self.handle_winetricks_button | ||||
|             self.handle_dpad_slot = self.handle_winetricks_dpad | ||||
|             self._gamepad_handling_enabled = True | ||||
|             # Reset dpad timer for table nav | ||||
|             self.dpad_timer.stop() | ||||
|             self.current_dpad_code = None | ||||
|             self.current_dpad_value = 0 | ||||
|             logger.debug("Gamepad handling successfully connected for WinetricksDialog") | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error connecting gamepad handlers for Winetricks: {e}") | ||||
|  | ||||
|     def disable_winetricks_mode(self): | ||||
|         """Restore original main window handlers""" | ||||
|         try: | ||||
|             if self.winetricks_dialog: | ||||
|                 self.handle_button_slot = self.original_button_handler | ||||
|                 self.handle_dpad_slot = self.original_dpad_handler | ||||
|                 self._gamepad_handling_enabled = self.original_gamepad_state | ||||
|                 self.winetricks_dialog = None | ||||
|                 self.dpad_timer.stop() | ||||
|                 self.current_dpad_code = None | ||||
|                 self.current_dpad_value = 0 | ||||
|                 logger.debug("Gamepad handling successfully restored from Winetricks") | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error restoring gamepad handlers from Winetricks: {e}") | ||||
|  | ||||
|     def handle_winetricks_button(self, button_code, value): | ||||
|         if self.winetricks_dialog is None: | ||||
|             return | ||||
|         if value == 0:  # Ignore releases | ||||
|             return | ||||
|         try: | ||||
|             # Always check for popups first, including QMessageBox | ||||
|             popup = QApplication.activePopupWidget() | ||||
|             if popup: | ||||
|                 if isinstance(popup, QMessageBox): | ||||
|                     if button_code in BUTTONS['confirm'] or button_code in BUTTONS['back']: | ||||
|                         popup.accept()  # Close QMessageBox with A or B | ||||
|                         return | ||||
|                 elif isinstance(popup, QMenu): | ||||
|                     if button_code in BUTTONS['confirm']:  # A: Select menu item | ||||
|                         focused = popup.activeAction() | ||||
|                         if focused: | ||||
|                             focused.trigger() | ||||
|                         return | ||||
|                     elif button_code in BUTTONS['back']:  # B: Close menu | ||||
|                         popup.close() | ||||
|                         return | ||||
|  | ||||
|             # Additional check for top-level QMessageBox (in case not active popup yet) | ||||
|             for widget in QApplication.topLevelWidgets(): | ||||
|                 if isinstance(widget, QMessageBox) and widget.isVisible(): | ||||
|                     if button_code in BUTTONS['confirm'] or button_code in BUTTONS['back']: | ||||
|                         widget.accept() | ||||
|                         return | ||||
|  | ||||
|             focused = QApplication.focusWidget() | ||||
|             if button_code in BUTTONS['confirm']:  # A: Toggle checkbox | ||||
|                 if isinstance(focused, QTableWidget): | ||||
|                     current_row = focused.currentRow() | ||||
|                     if current_row >= 0: | ||||
|                         checkbox_item = focused.item(current_row, 0) | ||||
|                         if checkbox_item and isinstance(checkbox_item, QTableWidgetItem): | ||||
|                             new_state = Qt.CheckState.Checked if checkbox_item.checkState() == Qt.CheckState.Unchecked else Qt.CheckState.Unchecked | ||||
|                             checkbox_item.setCheckState(new_state) | ||||
|                 return | ||||
|             elif button_code in BUTTONS['add_game']:  # X: Install (no force) | ||||
|                 self.winetricks_dialog.install_selected(force=False) | ||||
|                 return | ||||
|             elif button_code in BUTTONS['prev_dir']:  # Y: Force Install | ||||
|                 self.winetricks_dialog.install_selected(force=True) | ||||
|                 return | ||||
|             elif button_code in BUTTONS['back']:  # B: Cancel | ||||
|                 self.winetricks_dialog.reject() | ||||
|                 return | ||||
|             elif button_code in BUTTONS['prev_tab']:  # LB: Prev Tab | ||||
|                 current_index = self.winetricks_dialog.tab_widget.currentIndex() | ||||
|                 new_index = max(0, current_index - 1) | ||||
|                 self.winetricks_dialog.tab_widget.setCurrentIndex(new_index) | ||||
|                 self._focus_first_row_in_current_table() | ||||
|                 return | ||||
|             elif button_code in BUTTONS['next_tab']:  # RB: Next Tab | ||||
|                 current_index = self.winetricks_dialog.tab_widget.currentIndex() | ||||
|                 new_index = min(self.winetricks_dialog.tab_widget.count() - 1, current_index + 1) | ||||
|                 self.winetricks_dialog.tab_widget.setCurrentIndex(new_index) | ||||
|                 self._focus_first_row_in_current_table() | ||||
|                 return | ||||
|             # Fallback: Activate focused widget (e.g., buttons) | ||||
|             self._parent.activateFocusedWidget() | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error in handle_winetricks_button: {e}") | ||||
|  | ||||
|     def handle_winetricks_dpad(self, code, value, now): | ||||
|         if self.winetricks_dialog is None: | ||||
|             return | ||||
|         try: | ||||
|             if value == 0:  # Release: Stop repeat | ||||
|                 self.dpad_timer.stop() | ||||
|                 self.current_dpad_code = None | ||||
|                 self.current_dpad_value = 0 | ||||
|                 return | ||||
|  | ||||
|             # Start/update repeat timer for hold navigation | ||||
|             if self.current_dpad_code != code or self.current_dpad_value != value: | ||||
|                 self.dpad_timer.stop() | ||||
|                 self.dpad_timer.setInterval(150 if self.dpad_timer.isActive() else 300)  # Initial slower, then faster repeat | ||||
|                 self.dpad_timer.start() | ||||
|                 self.current_dpad_code = code | ||||
|                 self.current_dpad_value = value | ||||
|  | ||||
|             table = self._get_current_table() | ||||
|             if not table or table.rowCount() == 0: | ||||
|                 return | ||||
|  | ||||
|             current_row = table.currentRow() | ||||
|             if code == ecodes.ABS_HAT0Y:  # Up/Down: Navigate rows | ||||
|                 if value < 0:  # Up | ||||
|                     new_row = max(0, current_row - 1) | ||||
|                 elif value > 0:  # Down | ||||
|                     new_row = min(table.rowCount() - 1, current_row + 1) | ||||
|                 else: | ||||
|                     return | ||||
|                 if new_row != current_row: | ||||
|                     table.setCurrentCell(new_row, 0)  # Focus checkbox column | ||||
|                     table.setFocus(Qt.FocusReason.OtherFocusReason) | ||||
|             elif code == ecodes.ABS_HAT0X:  # Left/Right: Switch tabs | ||||
|                 if value < 0:  # Left: Prev tab | ||||
|                     current_index = self.winetricks_dialog.tab_widget.currentIndex() | ||||
|                     new_index = max(0, current_index - 1) | ||||
|                     self.winetricks_dialog.tab_widget.setCurrentIndex(new_index) | ||||
|                 elif value > 0:  # Right: Next tab | ||||
|                     current_index = self.winetricks_dialog.tab_widget.currentIndex() | ||||
|                     new_index = min(self.winetricks_dialog.tab_widget.count() - 1, current_index + 1) | ||||
|                     self.winetricks_dialog.tab_widget.setCurrentIndex(new_index) | ||||
|                 self._focus_first_row_in_current_table() | ||||
|         except Exception as e: | ||||
|             logger.error(f"Error in handle_winetricks_dpad: {e}") | ||||
|  | ||||
|     def _get_current_table(self): | ||||
|         """Get the current visible table from the tab widget's stacked container.""" | ||||
|         if self.winetricks_dialog is None: | ||||
|             return None | ||||
|         current_container = self.winetricks_dialog.tab_widget.currentWidget() | ||||
|         if current_container and isinstance(current_container, QStackedWidget): | ||||
|             current_table = current_container.widget(1)  # Table is at index 1 (after preloader) | ||||
|             if isinstance(current_table, QTableWidget): | ||||
|                 return current_table | ||||
|         return None | ||||
|  | ||||
|     def _focus_first_row_in_current_table(self): | ||||
|         """Focus the first row in the current table after tab switch.""" | ||||
|         if self.winetricks_dialog is None: | ||||
|             return | ||||
|         table = self._get_current_table() | ||||
|         if table and table.rowCount() > 0: | ||||
|             table.setCurrentCell(0, 0) | ||||
|             table.setFocus(Qt.FocusReason.OtherFocusReason) | ||||
|  | ||||
|     def handle_navigation_repeat(self): | ||||
|         """Плавное повторение движения с переменной скоростью для FileExplorer""" | ||||
|         try: | ||||
| @@ -705,39 +870,6 @@ class InputManager(QObject): | ||||
|                     self._parent.toggleGame(self._parent.current_exec_line, None) | ||||
|                     return | ||||
|  | ||||
|  | ||||
|             if isinstance(active, WinetricksDialog): | ||||
|                 if button_code in BUTTONS['confirm']:  # A button - toggle checkbox | ||||
|                     current_table = active.tab_widget.currentWidget() | ||||
|                     if isinstance(current_table, QTableWidget): | ||||
|                         current_row = current_table.currentRow() | ||||
|                         if current_row >= 0: | ||||
|                             checkbox = current_table.item(current_row, 0) | ||||
|                             if checkbox: | ||||
|                                 checkbox.setCheckState( | ||||
|                                     Qt.CheckState.Unchecked if checkbox.checkState() == Qt.CheckState.Checked else Qt.CheckState.Checked | ||||
|                                 ) | ||||
|                     return | ||||
|                 elif button_code in BUTTONS['add_game']:  # X button - install | ||||
|                     active.install_selected(force=False) | ||||
|                     return | ||||
|                 elif button_code in BUTTONS['prev_dir']:  # Y button - force install | ||||
|                     active.install_selected(force=True) | ||||
|                     return | ||||
|                 elif button_code in BUTTONS['back']:  # B button - close dialog | ||||
|                     active.reject() | ||||
|                     return | ||||
|                 elif button_code in BUTTONS['prev_tab']:  # LB - previous tab | ||||
|                     current_idx = active.tab_widget.currentIndex() | ||||
|                     new_idx = (current_idx - 1) % active.tab_widget.count() | ||||
|                     active.tab_widget.setCurrentIndex(new_idx) | ||||
|                     return | ||||
|                 elif button_code in BUTTONS['next_tab']:  # RB - next tab | ||||
|                     current_idx = active.tab_widget.currentIndex() | ||||
|                     new_idx = (current_idx + 1) % active.tab_widget.count() | ||||
|                     active.tab_widget.setCurrentIndex(new_idx) | ||||
|                     return | ||||
|  | ||||
|             # Standard navigation | ||||
|             if button_code in BUTTONS['confirm']: | ||||
|                 self._parent.activateFocusedWidget() | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -9,7 +9,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-10-15 15:31+0500\n" | ||||
| "POT-Creation-Date: 2025-10-16 10:43+0500\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language: de_DE\n" | ||||
| @@ -252,13 +252,37 @@ msgstr "" | ||||
| msgid "Select All" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Launching {0}" | ||||
| msgid "Open" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Select Dir" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Prev Dir" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Cancel" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Toggle" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Force Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Prev Tab" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Next Tab" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Launching {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "File Explorer" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -326,12 +350,6 @@ msgstr "" | ||||
| msgid "Settings" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Force Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Winetricks not found. Please try again." | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -9,7 +9,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-10-15 15:31+0500\n" | ||||
| "POT-Creation-Date: 2025-10-16 10:43+0500\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language: es_ES\n" | ||||
| @@ -252,13 +252,37 @@ msgstr "" | ||||
| msgid "Select All" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Launching {0}" | ||||
| msgid "Open" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Select Dir" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Prev Dir" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Cancel" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Toggle" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Force Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Prev Tab" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Next Tab" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Launching {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "File Explorer" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -326,12 +350,6 @@ msgstr "" | ||||
| msgid "Settings" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Force Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Winetricks not found. Please try again." | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
| @@ -9,7 +9,7 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PortProtonQt 0.1.1\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-10-15 15:31+0500\n" | ||||
| "POT-Creation-Date: 2025-10-16 10:43+0500\n" | ||||
| "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" | ||||
| "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" | ||||
| "Language-Team: LANGUAGE <LL@li.org>\n" | ||||
| @@ -250,13 +250,37 @@ msgstr "" | ||||
| msgid "Select All" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Launching {0}" | ||||
| msgid "Open" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Select Dir" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Prev Dir" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Cancel" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Toggle" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Force Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Prev Tab" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Next Tab" | ||||
| msgstr "" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Launching {0}" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "File Explorer" | ||||
| msgstr "" | ||||
|  | ||||
| @@ -324,12 +348,6 @@ msgstr "" | ||||
| msgid "Settings" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Force Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Install" | ||||
| msgstr "" | ||||
|  | ||||
| msgid "Winetricks not found. Please try again." | ||||
| msgstr "" | ||||
|  | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -9,8 +9,8 @@ msgid "" | ||||
| msgstr "" | ||||
| "Project-Id-Version: PROJECT VERSION\n" | ||||
| "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" | ||||
| "POT-Creation-Date: 2025-10-15 15:31+0500\n" | ||||
| "PO-Revision-Date: 2025-10-15 15:31+0500\n" | ||||
| "POT-Creation-Date: 2025-10-16 10:43+0500\n" | ||||
| "PO-Revision-Date: 2025-10-16 10:43+0500\n" | ||||
| "Last-Translator: \n" | ||||
| "Language: ru_RU\n" | ||||
| "Language-Team: ru_RU <LL@li.org>\n" | ||||
| @@ -259,13 +259,37 @@ msgstr "Удалить" | ||||
| msgid "Select All" | ||||
| msgstr "Выбрать всё" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Launching {0}" | ||||
| msgstr "Идёт запуск {0}" | ||||
| msgid "Open" | ||||
| msgstr "Открыть" | ||||
|  | ||||
| msgid "Select Dir" | ||||
| msgstr "Выбрать папку" | ||||
|  | ||||
| msgid "Prev Dir" | ||||
| msgstr "Предыдущий каталог" | ||||
|  | ||||
| msgid "Cancel" | ||||
| msgstr "Отмена" | ||||
|  | ||||
| msgid "Toggle" | ||||
| msgstr "Переключить" | ||||
|  | ||||
| msgid "Install" | ||||
| msgstr "Установить" | ||||
|  | ||||
| msgid "Force Install" | ||||
| msgstr "Принудительно установить" | ||||
|  | ||||
| msgid "Prev Tab" | ||||
| msgstr "Предыдущая вкладка" | ||||
|  | ||||
| msgid "Next Tab" | ||||
| msgstr "Следующая вкладка" | ||||
|  | ||||
| #, python-brace-format | ||||
| msgid "Launching {0}" | ||||
| msgstr "Идёт запуск {0}" | ||||
|  | ||||
| msgid "File Explorer" | ||||
| msgstr "Проводник" | ||||
|  | ||||
| @@ -333,12 +357,6 @@ msgstr "Шрифты" | ||||
| msgid "Settings" | ||||
| msgstr "Настройки" | ||||
|  | ||||
| msgid "Force Install" | ||||
| msgstr "Принудительно установить" | ||||
|  | ||||
| msgid "Install" | ||||
| msgstr "Установить" | ||||
|  | ||||
| msgid "Winetricks not found. Please try again." | ||||
| msgstr "Winetricks не найден. Повторите попытку." | ||||
|  | ||||
|   | ||||
| @@ -260,6 +260,10 @@ class MainWindow(QMainWindow): | ||||
|                 GamepadType.XBOX: "xbox_y", | ||||
|                 GamepadType.PLAYSTATION: "ps_square", | ||||
|             }, | ||||
|             'prev_dir': { | ||||
|                 GamepadType.XBOX: "xbox_y", | ||||
|                 GamepadType.PLAYSTATION: "ps_square", | ||||
|             }, | ||||
|         } | ||||
|         return mappings.get(action, {}).get(gtype, "placeholder") | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user