forked from Boria138/PortProtonQt
chore(input_manager): clean code
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -33,3 +33,6 @@ Thumbs.db
|
|||||||
.vscode
|
.vscode
|
||||||
.ropeproject
|
.ropeproject
|
||||||
.zed
|
.zed
|
||||||
|
|
||||||
|
# get_wine debug folder
|
||||||
|
proton_downloads
|
||||||
|
|||||||
@@ -135,6 +135,15 @@ def create_dialog_hints_widget(theme, main_window, input_manager, context='defau
|
|||||||
("prev_tab", _("Prev Tab")), # LB / L1
|
("prev_tab", _("Prev Tab")), # LB / L1
|
||||||
("next_tab", _("Next Tab")), # RB / R1
|
("next_tab", _("Next Tab")), # RB / R1
|
||||||
]
|
]
|
||||||
|
elif context == 'proton_manager':
|
||||||
|
dialog_actions = [
|
||||||
|
("confirm", _("Toggle")), # A / Cross
|
||||||
|
("add_game", _("Download")), # X / Triangle
|
||||||
|
("prev_dir", _("Clear All")), # 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)
|
hints_labels = [] # Store for updates (returned for class storage)
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from portprotonqt.logger import get_logger
|
|||||||
from portprotonqt.theme_manager import ThemeManager
|
from portprotonqt.theme_manager import ThemeManager
|
||||||
from portprotonqt.localization import _
|
from portprotonqt.localization import _
|
||||||
from portprotonqt.version_utils import version_sort_key
|
from portprotonqt.version_utils import version_sort_key
|
||||||
|
from portprotonqt.dialogs import create_dialog_hints_widget, update_dialog_hints
|
||||||
|
|
||||||
logger = get_logger(__name__)
|
logger = get_logger(__name__)
|
||||||
theme_manager = ThemeManager()
|
theme_manager = ThemeManager()
|
||||||
@@ -330,7 +331,7 @@ class ExtractionThread(QThread):
|
|||||||
|
|
||||||
|
|
||||||
class ProtonManager(QDialog):
|
class ProtonManager(QDialog):
|
||||||
def __init__(self, parent=None, portproton_location=None, theme=None):
|
def __init__(self, parent=None, portproton_location=None, theme=None, input_manager=None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self.theme = theme if theme else theme_manager.apply_theme(read_theme_from_config())
|
self.theme = theme if theme else theme_manager.apply_theme(read_theme_from_config())
|
||||||
self.selected_assets = {} # {unique_id: asset_data}
|
self.selected_assets = {} # {unique_id: asset_data}
|
||||||
@@ -340,9 +341,25 @@ class ProtonManager(QDialog):
|
|||||||
self.assets_to_download = []
|
self.assets_to_download = []
|
||||||
self.current_download_index = 0
|
self.current_download_index = 0
|
||||||
self.portproton_location = portproton_location
|
self.portproton_location = portproton_location
|
||||||
|
self.input_manager = input_manager # Input manager for gamepad support
|
||||||
|
self.initial_command_executed = False # Track if --initial command has been executed
|
||||||
|
|
||||||
|
# Find main window
|
||||||
|
self.main_window = None
|
||||||
|
parent_widget = self.parent()
|
||||||
|
while parent_widget:
|
||||||
|
if hasattr(parent_widget, 'input_manager'):
|
||||||
|
self.main_window = parent_widget
|
||||||
|
break
|
||||||
|
parent_widget = parent_widget.parent()
|
||||||
|
|
||||||
self.initUI()
|
self.initUI()
|
||||||
self.load_proton_data_from_json()
|
self.load_proton_data_from_json()
|
||||||
|
|
||||||
|
# Enable gamepad support if input manager is provided
|
||||||
|
if self.input_manager:
|
||||||
|
self.enable_proton_manager_mode()
|
||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setWindowTitle(_('Get other Wine'))
|
self.setWindowTitle(_('Get other Wine'))
|
||||||
self.resize(1100, 720)
|
self.resize(1100, 720)
|
||||||
@@ -403,6 +420,14 @@ class ProtonManager(QDialog):
|
|||||||
|
|
||||||
layout.addWidget(self.download_frame)
|
layout.addWidget(self.download_frame)
|
||||||
|
|
||||||
|
# Create hints widget using common function
|
||||||
|
if self.input_manager and self.main_window:
|
||||||
|
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='proton_manager'
|
||||||
|
)
|
||||||
|
layout.addWidget(self.hints_widget)
|
||||||
|
|
||||||
# Кнопки управления
|
# Кнопки управления
|
||||||
button_layout = QHBoxLayout()
|
button_layout = QHBoxLayout()
|
||||||
self.download_btn = QPushButton(_('Download Selected'))
|
self.download_btn = QPushButton(_('Download Selected'))
|
||||||
@@ -416,6 +441,26 @@ class ProtonManager(QDialog):
|
|||||||
button_layout.addWidget(self.clear_btn)
|
button_layout.addWidget(self.clear_btn)
|
||||||
layout.addLayout(button_layout)
|
layout.addLayout(button_layout)
|
||||||
|
|
||||||
|
# Connect signals for hints updates
|
||||||
|
if self.input_manager and self.main_window:
|
||||||
|
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
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Initial update
|
||||||
|
update_dialog_hints(
|
||||||
|
self.hints_labels, self.main_window, self.input_manager,
|
||||||
|
theme_manager, self.current_theme_name
|
||||||
|
)
|
||||||
|
|
||||||
def load_proton_data_from_json(self):
|
def load_proton_data_from_json(self):
|
||||||
"""Загружаем данные по Протонам из файла JSON"""
|
"""Загружаем данные по Протонам из файла JSON"""
|
||||||
json_url = "https://git.linux-gaming.ru/Boria138/PortProton-Wine-Metadata/raw/branch/main/wine_metadata.json"
|
json_url = "https://git.linux-gaming.ru/Boria138/PortProton-Wine-Metadata/raw/branch/main/wine_metadata.json"
|
||||||
@@ -821,6 +866,26 @@ class ProtonManager(QDialog):
|
|||||||
self.download_btn.setEnabled(True)
|
self.download_btn.setEnabled(True)
|
||||||
self.clear_btn.setEnabled(True)
|
self.clear_btn.setEnabled(True)
|
||||||
self.is_downloading = False
|
self.is_downloading = False
|
||||||
|
|
||||||
|
# Run the initial command after all assets have been processed
|
||||||
|
import subprocess
|
||||||
|
try:
|
||||||
|
# Get the proper PortProton start command
|
||||||
|
start_cmd = get_portproton_start_command()
|
||||||
|
if start_cmd and not self.initial_command_executed:
|
||||||
|
result = subprocess.run(start_cmd + ["cli", "--initial"], timeout=10)
|
||||||
|
if result.returncode != 0:
|
||||||
|
logger.warning(f"Initial PortProton command returned non-zero exit code: {result.returncode}")
|
||||||
|
else:
|
||||||
|
logger.info("Initial PortProton command executed successfully after all assets processed")
|
||||||
|
self.initial_command_executed = True # Mark that command has been executed
|
||||||
|
elif self.initial_command_executed:
|
||||||
|
logger.debug("Initial PortProton command already executed, skipping")
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
logger.warning("Initial PortProton command timed out")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error running initial PortProton command: {e}")
|
||||||
|
|
||||||
QMessageBox.information(self, _("Downloading Complete"), _("All selected archives have been downloaded!"))
|
QMessageBox.information(self, _("Downloading Complete"), _("All selected archives have been downloaded!"))
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -954,22 +1019,6 @@ class ProtonManager(QDialog):
|
|||||||
def extraction_finished(archive_path, success):
|
def extraction_finished(archive_path, success):
|
||||||
if success:
|
if success:
|
||||||
logger.info(f"Successfully extracted: {archive_path}")
|
logger.info(f"Successfully extracted: {archive_path}")
|
||||||
|
|
||||||
# Run the initial command after successful extraction
|
|
||||||
import subprocess
|
|
||||||
try:
|
|
||||||
# Get the proper PortProton start command
|
|
||||||
start_cmd = get_portproton_start_command()
|
|
||||||
if start_cmd:
|
|
||||||
result = subprocess.run(start_cmd + ["cli", "--initial"], timeout=10)
|
|
||||||
if result.returncode != 0:
|
|
||||||
logger.warning(f"Initial PortProton command returned non-zero exit code: {result.returncode}")
|
|
||||||
else:
|
|
||||||
logger.warning("Could not determine PortProton start command, skipping initial command")
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
logger.warning("Initial PortProton command timed out")
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error running initial PortProton command: {e}")
|
|
||||||
else:
|
else:
|
||||||
logger.error(f"Failed to extract: {archive_path}")
|
logger.error(f"Failed to extract: {archive_path}")
|
||||||
QMessageBox.critical(self, _("Extraction Error"), _("Failed to extract archive: {0}").format(archive_path))
|
QMessageBox.critical(self, _("Extraction Error"), _("Failed to extract archive: {0}").format(archive_path))
|
||||||
@@ -1043,6 +1092,15 @@ class ProtonManager(QDialog):
|
|||||||
self.current_download_index += 1
|
self.current_download_index += 1
|
||||||
QTimer.singleShot(100, self.start_next_download)
|
QTimer.singleShot(100, self.start_next_download)
|
||||||
|
|
||||||
|
def has_active_processes(self):
|
||||||
|
"""Check if there are active download or extraction processes"""
|
||||||
|
extraction_active = (self.current_extraction_thread and
|
||||||
|
self.current_extraction_thread.isRunning())
|
||||||
|
download_active = (self.current_download_thread and
|
||||||
|
hasattr(self.current_download_thread, 'isRunning') and
|
||||||
|
self.current_download_thread.isRunning())
|
||||||
|
return extraction_active or download_active
|
||||||
|
|
||||||
def cancel_current_download(self):
|
def cancel_current_download(self):
|
||||||
"""Cancel current download or extraction"""
|
"""Cancel current download or extraction"""
|
||||||
# Stop extraction thread if running
|
# Stop extraction thread if running
|
||||||
@@ -1068,6 +1126,8 @@ class ProtonManager(QDialog):
|
|||||||
self.assets_to_download = []
|
self.assets_to_download = []
|
||||||
self.current_download_index = 0
|
self.current_download_index = 0
|
||||||
self.is_downloading = False
|
self.is_downloading = False
|
||||||
|
# Сбрасываем флаг выполнения команды --initial, так как процесс отменен
|
||||||
|
self.initial_command_executed = False
|
||||||
|
|
||||||
# Сброс/перезапуск UI
|
# Сброс/перезапуск UI
|
||||||
self.download_frame.setVisible(False)
|
self.download_frame.setVisible(False)
|
||||||
@@ -1076,46 +1136,87 @@ class ProtonManager(QDialog):
|
|||||||
|
|
||||||
QMessageBox.information(self, _("Operation Cancelled"), _("Download or extraction has been cancelled."))
|
QMessageBox.information(self, _("Operation Cancelled"), _("Download or extraction has been cancelled."))
|
||||||
|
|
||||||
|
def enable_proton_manager_mode(self):
|
||||||
|
"""Enable gamepad mode for ProtonManager"""
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.enable_proton_manager_mode(self)
|
||||||
|
|
||||||
|
def disable_proton_manager_mode(self):
|
||||||
|
"""Disable gamepad mode for ProtonManager"""
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.disable_proton_manager_mode()
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
"""Проверка, что все потоки останавливаются при закрытии приложения"""
|
"""Проверка, что все потоки останавливаются при закрытии приложения"""
|
||||||
logger.debug("Closing ProtonManager dialog...")
|
logger.debug("Closing ProtonManager dialog...")
|
||||||
|
|
||||||
# Stop extraction thread if running
|
# Disable gamepad mode before closing
|
||||||
if self.current_extraction_thread and self.current_extraction_thread.isRunning():
|
if self.input_manager:
|
||||||
logger.debug("Stopping current extraction thread...")
|
self.disable_proton_manager_mode()
|
||||||
self.current_extraction_thread.stop()
|
|
||||||
if not self.current_extraction_thread.wait(2000):
|
|
||||||
logger.warning("Extraction thread did not stop gracefully during close")
|
|
||||||
|
|
||||||
# Stop download thread if running
|
# Check if there are active processes and cancel them
|
||||||
try:
|
if self.has_active_processes():
|
||||||
if (self.current_download_thread and
|
logger.debug("Active processes detected, cancelling before close...")
|
||||||
hasattr(self.current_download_thread, 'isRunning') and
|
self.cancel_current_download()
|
||||||
self.current_download_thread.isRunning()):
|
else:
|
||||||
logger.debug("Stopping current download thread...")
|
# Stop extraction thread if running
|
||||||
if hasattr(self.current_download_thread, 'stop'):
|
if self.current_extraction_thread and self.current_extraction_thread.isRunning():
|
||||||
self.current_download_thread.stop()
|
logger.debug("Stopping current extraction thread...")
|
||||||
if not self.current_download_thread.wait(2000):
|
self.current_extraction_thread.stop()
|
||||||
logger.warning("Download thread did not stop gracefully during close")
|
if not self.current_extraction_thread.wait(2000):
|
||||||
except RuntimeError:
|
logger.warning("Extraction thread did not stop gracefully during close")
|
||||||
# Object already deleted, which is fine
|
|
||||||
logger.debug("Download thread object already deleted during close")
|
# Stop download thread if running
|
||||||
|
try:
|
||||||
|
if (self.current_download_thread and
|
||||||
|
hasattr(self.current_download_thread, 'isRunning') and
|
||||||
|
self.current_download_thread.isRunning()):
|
||||||
|
logger.debug("Stopping current download thread...")
|
||||||
|
if hasattr(self.current_download_thread, 'stop'):
|
||||||
|
self.current_download_thread.stop()
|
||||||
|
if not self.current_download_thread.wait(2000):
|
||||||
|
logger.warning("Download thread did not stop gracefully during close")
|
||||||
|
except RuntimeError:
|
||||||
|
# Object already deleted, which is fine
|
||||||
|
logger.debug("Download thread object already deleted during close")
|
||||||
|
|
||||||
|
# If we're closing without active processes but haven't completed all downloads,
|
||||||
|
# reset the initial command flag so it can run if the dialog is opened again
|
||||||
|
if self.is_downloading and self.current_download_index < len(self.assets_to_download):
|
||||||
|
self.initial_command_executed = False
|
||||||
|
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
|
def reject(self):
|
||||||
|
"""Override reject to properly cancel active processes before closing"""
|
||||||
|
# Disable gamepad mode before rejecting
|
||||||
|
if self.input_manager:
|
||||||
|
self.disable_proton_manager_mode()
|
||||||
|
|
||||||
|
if self.has_active_processes():
|
||||||
|
logger.debug("Active processes detected, cancelling before reject...")
|
||||||
|
self.cancel_current_download()
|
||||||
|
else:
|
||||||
|
# If we're rejecting without active processes but haven't completed all downloads,
|
||||||
|
# reset the initial command flag so it can run if the dialog is opened again
|
||||||
|
if self.is_downloading and self.current_download_index < len(self.assets_to_download):
|
||||||
|
self.initial_command_executed = False
|
||||||
|
super().reject()
|
||||||
|
|
||||||
|
|
||||||
def show_proton_manager(parent=None, portproton_location=None):
|
|
||||||
|
def show_proton_manager(parent=None, portproton_location=None, input_manager=None):
|
||||||
"""
|
"""
|
||||||
Shows the Proton/WINE archive extractor dialog.
|
Shows the Proton/WINE archive extractor dialog.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
parent: Parent widget for the dialog
|
parent: Parent widget for the dialog
|
||||||
portproton_location: Location of PortProton installation
|
portproton_location: Location of PortProton installation
|
||||||
|
input_manager: Input manager for gamepad support
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
ProtonManager dialog instance
|
ProtonManager dialog instance
|
||||||
"""
|
"""
|
||||||
dialog = ProtonManager(parent, portproton_location)
|
dialog = ProtonManager(parent, portproton_location, input_manager=input_manager)
|
||||||
dialog.exec() # Use exec() for modal dialog
|
dialog.exec() # Use exec() for modal dialog
|
||||||
return dialog
|
return dialog
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from typing import Protocol, cast, Any
|
|||||||
from evdev import InputDevice, InputEvent, UInput, ecodes, list_devices, ff
|
from evdev import InputDevice, InputEvent, UInput, ecodes, list_devices, ff
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pyudev import Context, Monitor, Device, Devices
|
from pyudev import Context, Monitor, Device, Devices
|
||||||
from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView, QMessageBox, QListWidget, QTableWidget, QAbstractItemView, QTableWidgetItem, QSlider
|
from PySide6.QtWidgets import QWidget, QStackedWidget, QApplication, QScrollArea, QLineEdit, QDialog, QMenu, QComboBox, QListView, QMessageBox, QListWidget, QTableWidget, QAbstractItemView, QSlider, QCheckBox
|
||||||
from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot, QTimer
|
from PySide6.QtCore import Qt, QObject, QEvent, QPoint, Signal, Slot, QTimer
|
||||||
from PySide6.QtGui import QKeyEvent, QMouseEvent
|
from PySide6.QtGui import QKeyEvent, QMouseEvent
|
||||||
from portprotonqt.logger import get_logger
|
from portprotonqt.logger import get_logger
|
||||||
@@ -137,6 +137,16 @@ class InputManager(QObject):
|
|||||||
self.deadzone_value = 15 # мёртвая зона из ядра (flat параметр)
|
self.deadzone_value = 15 # мёртвая зона из ядра (flat параметр)
|
||||||
|
|
||||||
self.sensitivity = 8.0
|
self.sensitivity = 8.0
|
||||||
|
|
||||||
|
# Dynamic attributes for different modes (declared here to satisfy type checkers)
|
||||||
|
self.winetricks_dialog = None
|
||||||
|
self.settings_dialog = None
|
||||||
|
self.file_explorer = None
|
||||||
|
self.proton_manager_dialog = None
|
||||||
|
self.original_button_handler = None
|
||||||
|
self.original_dpad_handler = None
|
||||||
|
self.original_gamepad_state = None
|
||||||
|
self._original_handlers_saved = False
|
||||||
self.scroll_accumulator = 0.0
|
self.scroll_accumulator = 0.0
|
||||||
self.scroll_sensitivity = 0.15
|
self.scroll_sensitivity = 0.15
|
||||||
self.scroll_threshold = 0.2
|
self.scroll_threshold = 0.2
|
||||||
@@ -343,15 +353,12 @@ class InputManager(QObject):
|
|||||||
def enable_file_explorer_mode(self, file_explorer):
|
def enable_file_explorer_mode(self, file_explorer):
|
||||||
"""Настройка обработки геймпада для FileExplorer"""
|
"""Настройка обработки геймпада для FileExplorer"""
|
||||||
try:
|
try:
|
||||||
self.file_explorer = file_explorer
|
self._setup_mode_handlers(
|
||||||
self.original_button_handler = self.handle_button_slot
|
file_explorer,
|
||||||
self.original_dpad_handler = self.handle_dpad_slot
|
self.handle_file_explorer_button,
|
||||||
self.original_gamepad_state = self._gamepad_handling_enabled
|
self.handle_file_explorer_dpad,
|
||||||
|
'file_explorer'
|
||||||
self.handle_button_slot = self.handle_file_explorer_button
|
)
|
||||||
self.handle_dpad_slot = self.handle_file_explorer_dpad
|
|
||||||
self._gamepad_handling_enabled = True
|
|
||||||
|
|
||||||
logger.debug("Gamepad handling successfully connected for FileExplorer")
|
logger.debug("Gamepad handling successfully connected for FileExplorer")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error connecting gamepad handlers for FileExplorer: {e}")
|
logger.error(f"Error connecting gamepad handlers for FileExplorer: {e}")
|
||||||
@@ -360,12 +367,9 @@ class InputManager(QObject):
|
|||||||
"""Восстановление оригинальных обработчиков (дефолт возвращаем)"""
|
"""Восстановление оригинальных обработчиков (дефолт возвращаем)"""
|
||||||
try:
|
try:
|
||||||
if self.file_explorer:
|
if self.file_explorer:
|
||||||
self.handle_button_slot = self.original_button_handler
|
# Additional cleanup for file explorer
|
||||||
self.handle_dpad_slot = self.original_dpad_handler
|
|
||||||
self._gamepad_handling_enabled = self.original_gamepad_state
|
|
||||||
|
|
||||||
self.file_explorer = None
|
|
||||||
self.nav_timer.stop()
|
self.nav_timer.stop()
|
||||||
|
self._restore_original_handlers('file_explorer')
|
||||||
logger.debug("Gamepad handling successfully restored")
|
logger.debug("Gamepad handling successfully restored")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error restoring gamepad handlers: {e}")
|
logger.error(f"Error restoring gamepad handlers: {e}")
|
||||||
@@ -557,20 +561,12 @@ class InputManager(QObject):
|
|||||||
def enable_winetricks_mode(self, winetricks_dialog):
|
def enable_winetricks_mode(self, winetricks_dialog):
|
||||||
"""Setup gamepad handling for WinetricksDialog"""
|
"""Setup gamepad handling for WinetricksDialog"""
|
||||||
try:
|
try:
|
||||||
self.winetricks_dialog = winetricks_dialog
|
self._setup_mode_handlers(
|
||||||
self.original_button_handler = self.handle_button_slot
|
winetricks_dialog,
|
||||||
self.original_dpad_handler = self.handle_dpad_slot
|
self.handle_winetricks_button,
|
||||||
self.original_gamepad_state = self._gamepad_handling_enabled
|
self.handle_winetricks_dpad,
|
||||||
|
'winetricks_dialog'
|
||||||
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")
|
logger.debug("Gamepad handling successfully connected for WinetricksDialog")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error connecting gamepad handlers for Winetricks: {e}")
|
logger.error(f"Error connecting gamepad handlers for Winetricks: {e}")
|
||||||
@@ -579,15 +575,7 @@ class InputManager(QObject):
|
|||||||
"""Restore original main window handlers"""
|
"""Restore original main window handlers"""
|
||||||
try:
|
try:
|
||||||
if self.winetricks_dialog:
|
if self.winetricks_dialog:
|
||||||
self.handle_button_slot = self.original_button_handler
|
self._restore_original_handlers('winetricks_dialog')
|
||||||
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")
|
logger.debug("Gamepad handling successfully restored from Winetricks")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error restoring gamepad handlers from Winetricks: {e}")
|
logger.error(f"Error restoring gamepad handlers from Winetricks: {e}")
|
||||||
@@ -606,12 +594,7 @@ class InputManager(QObject):
|
|||||||
|
|
||||||
if button_code in BUTTONS['confirm']: # A: Toggle checkbox
|
if button_code in BUTTONS['confirm']: # A: Toggle checkbox
|
||||||
if isinstance(focused, QTableWidget):
|
if isinstance(focused, QTableWidget):
|
||||||
current_row = focused.currentRow()
|
self.handle_table_confirm(focused)
|
||||||
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
|
return
|
||||||
|
|
||||||
elif button_code in BUTTONS['add_game']: # X: Install
|
elif button_code in BUTTONS['add_game']: # X: Install
|
||||||
@@ -710,23 +693,387 @@ class InputManager(QObject):
|
|||||||
table.setCurrentCell(0, 0)
|
table.setCurrentCell(0, 0)
|
||||||
table.setFocus(Qt.FocusReason.OtherFocusReason)
|
table.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
|
||||||
|
# TABLE NAVIGATION METHODS
|
||||||
|
def handle_table_navigation(self, table: QTableWidget, code: int, value: int):
|
||||||
|
"""
|
||||||
|
Обрабатывает навигацию по таблице
|
||||||
|
|
||||||
|
Args:
|
||||||
|
table: QTableWidget для обработки навигации
|
||||||
|
code: Код события (обычно ABS_HAT0X или ABS_HAT0Y)
|
||||||
|
value: Значение события (направление)
|
||||||
|
"""
|
||||||
|
row_count = table.rowCount()
|
||||||
|
if row_count <= 0:
|
||||||
|
return
|
||||||
|
current_row = table.currentRow()
|
||||||
|
if current_row < 0:
|
||||||
|
current_row = 0
|
||||||
|
table.setCurrentCell(0, 0)
|
||||||
|
|
||||||
|
if code == ecodes.ABS_HAT0Y and value != 0:
|
||||||
|
# Vertical navigation
|
||||||
|
if value > 0: # Down
|
||||||
|
new_row = min(current_row + 1, row_count - 1)
|
||||||
|
elif value < 0: # Up
|
||||||
|
new_row = max(current_row - 1, 0)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
table.setCurrentCell(new_row, table.currentColumn())
|
||||||
|
item = table.item(new_row, table.currentColumn())
|
||||||
|
if item:
|
||||||
|
table.scrollToItem(
|
||||||
|
item,
|
||||||
|
QAbstractItemView.ScrollHint.PositionAtCenter
|
||||||
|
)
|
||||||
|
table.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
return
|
||||||
|
elif code == ecodes.ABS_HAT0X and value != 0:
|
||||||
|
# Horizontal navigation
|
||||||
|
col_count = table.columnCount()
|
||||||
|
current_col = table.currentColumn()
|
||||||
|
if current_col < 0:
|
||||||
|
current_col = 0
|
||||||
|
|
||||||
|
if value < 0: # Left
|
||||||
|
new_col = max(current_col - 1, 0)
|
||||||
|
elif value > 0: # Right
|
||||||
|
new_col = min(current_col + 1, col_count - 1)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
table.setCurrentCell(table.currentRow(), new_col)
|
||||||
|
table.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
return
|
||||||
|
|
||||||
|
def handle_table_confirm(self, table: QTableWidget):
|
||||||
|
"""
|
||||||
|
Обрабатывает подтверждение (например, нажатие A) для таблицы
|
||||||
|
|
||||||
|
Args:
|
||||||
|
table: QTableWidget для обработки подтверждения
|
||||||
|
"""
|
||||||
|
current_row = table.currentRow()
|
||||||
|
current_col = table.currentColumn()
|
||||||
|
if current_row >= 0 and current_col >= 0:
|
||||||
|
# Check if the cell contains a checkbox
|
||||||
|
item = table.item(current_row, current_col)
|
||||||
|
if item and (item.flags() & Qt.ItemFlag.ItemIsUserCheckable):
|
||||||
|
# Toggle the checkbox state
|
||||||
|
new_state = Qt.CheckState.Checked if item.checkState() == Qt.CheckState.Unchecked else Qt.CheckState.Unchecked
|
||||||
|
item.setCheckState(new_state)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# Call custom confirm callback if exists
|
||||||
|
callback = getattr(table, '_on_confirm_callback', None) # type: ignore
|
||||||
|
if callback and callable(callback):
|
||||||
|
callback(table, current_row, current_col)
|
||||||
|
return True
|
||||||
|
|
||||||
|
# WIDGET NAVIGATION METHODS
|
||||||
|
def setup_widget_navigation(self, widget: QWidget, navigation_type: str = "default", **kwargs):
|
||||||
|
"""
|
||||||
|
Устанавливает навигацию для виджета
|
||||||
|
|
||||||
|
Args:
|
||||||
|
widget: QWidget для настройки навигации
|
||||||
|
navigation_type: Тип навигации ('table', 'list', 'combo', 'default')
|
||||||
|
**kwargs: Дополнительные параметры для навигации
|
||||||
|
"""
|
||||||
|
widget.installEventFilter(self)
|
||||||
|
# Use direct assignment for custom navigation properties, with type ignore for pyright
|
||||||
|
widget._navigation_type = navigation_type # type: ignore
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
setattr(widget, f'_{key}', value)
|
||||||
|
|
||||||
|
def handle_widget_navigation(self, widget: QWidget, code: int, value: int):
|
||||||
|
"""
|
||||||
|
Обрабатывает навигацию по виджету
|
||||||
|
|
||||||
|
Args:
|
||||||
|
widget: QWidget для обработки навигации
|
||||||
|
code: Код события (обычно ABS_HAT0X или ABS_HAT0Y)
|
||||||
|
value: Значение события (направление)
|
||||||
|
"""
|
||||||
|
nav_type = getattr(widget, '_navigation_type', 'default') # type: ignore
|
||||||
|
|
||||||
|
if nav_type == 'table' and isinstance(widget, QTableWidget):
|
||||||
|
self.handle_table_navigation(widget, code, value)
|
||||||
|
elif nav_type == 'list' and isinstance(widget, QListWidget):
|
||||||
|
self.handle_list_navigation(widget, code, value)
|
||||||
|
elif nav_type == 'combo' and isinstance(widget, QComboBox):
|
||||||
|
self.handle_combo_navigation(widget, code, value)
|
||||||
|
else:
|
||||||
|
# Default navigation behavior
|
||||||
|
if isinstance(widget, QTableWidget):
|
||||||
|
self.handle_table_navigation(widget, code, value)
|
||||||
|
elif isinstance(widget, QListWidget):
|
||||||
|
self.handle_list_navigation(widget, code, value)
|
||||||
|
elif isinstance(widget, QComboBox):
|
||||||
|
self.handle_combo_navigation(widget, code, value)
|
||||||
|
|
||||||
|
def handle_list_navigation(self, list_widget: QListWidget, code: int, value: int):
|
||||||
|
"""
|
||||||
|
Обрабатывает навигацию по списку
|
||||||
|
|
||||||
|
Args:
|
||||||
|
list_widget: QListWidget для обработки навигации
|
||||||
|
code: Код события (обычно ABS_HAT0X или ABS_HAT0Y)
|
||||||
|
value: Значение события (направление)
|
||||||
|
"""
|
||||||
|
if code == ecodes.ABS_HAT0Y and value != 0:
|
||||||
|
model = list_widget.model()
|
||||||
|
current_index = list_widget.currentIndex()
|
||||||
|
if model and current_index.isValid():
|
||||||
|
row_count = model.rowCount()
|
||||||
|
current_row = current_index.row()
|
||||||
|
if value > 0: # Down
|
||||||
|
next_row = min(current_row + 1, row_count - 1)
|
||||||
|
list_widget.setCurrentIndex(model.index(next_row, current_index.column()))
|
||||||
|
elif value < 0: # Up
|
||||||
|
prev_row = max(current_row - 1, 0)
|
||||||
|
list_widget.setCurrentIndex(model.index(prev_row, current_index.column()))
|
||||||
|
list_widget.scrollTo(list_widget.currentIndex(), QListView.ScrollHint.PositionAtCenter)
|
||||||
|
|
||||||
|
def handle_combo_navigation(self, combo_widget: QComboBox, code: int, value: int):
|
||||||
|
"""
|
||||||
|
Обрабатывает навигацию по комбинированному виджету
|
||||||
|
|
||||||
|
Args:
|
||||||
|
combo_widget: QComboBox для обработки навигации
|
||||||
|
code: Код события (обычно ABS_HAT0X или ABS_HAT0Y)
|
||||||
|
value: Значение события (направление)
|
||||||
|
"""
|
||||||
|
if code == ecodes.ABS_HAT0Y and value != 0:
|
||||||
|
current_index = combo_widget.currentIndex()
|
||||||
|
if value > 0: # Down
|
||||||
|
new_index = min(current_index + 1, combo_widget.count() - 1)
|
||||||
|
elif value < 0: # Up
|
||||||
|
new_index = max(current_index - 1, 0)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
|
||||||
|
if new_index != current_index:
|
||||||
|
combo_widget.setCurrentIndex(new_index)
|
||||||
|
|
||||||
|
def _setup_mode_handlers(self, dialog_instance, button_handler, dpad_handler, dialog_attr_name):
|
||||||
|
"""Common method to setup mode handlers"""
|
||||||
|
# Save original handlers if not already saved
|
||||||
|
if not hasattr(self, '_original_handlers_saved') or not self._original_handlers_saved:
|
||||||
|
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._original_handlers_saved = True
|
||||||
|
|
||||||
|
# Set the dialog instance
|
||||||
|
if dialog_attr_name == 'winetricks_dialog':
|
||||||
|
self.winetricks_dialog = dialog_instance
|
||||||
|
elif dialog_attr_name == 'settings_dialog':
|
||||||
|
self.settings_dialog = dialog_instance
|
||||||
|
elif dialog_attr_name == 'file_explorer':
|
||||||
|
self.file_explorer = dialog_instance
|
||||||
|
elif dialog_attr_name == 'proton_manager_dialog':
|
||||||
|
self.proton_manager_dialog = dialog_instance
|
||||||
|
|
||||||
|
# Set new handlers
|
||||||
|
self.handle_button_slot = button_handler
|
||||||
|
self.handle_dpad_slot = dpad_handler
|
||||||
|
self._gamepad_handling_enabled = True
|
||||||
|
|
||||||
|
# Reset dpad timer
|
||||||
|
self.dpad_timer.stop()
|
||||||
|
self.current_dpad_code = None
|
||||||
|
self.current_dpad_value = 0
|
||||||
|
|
||||||
|
def _restore_original_handlers(self, dialog_attr_name):
|
||||||
|
"""Common method to restore original handlers"""
|
||||||
|
# Restore original handlers
|
||||||
|
self.handle_button_slot = self.original_button_handler
|
||||||
|
self.handle_dpad_slot = self.original_dpad_handler
|
||||||
|
self._gamepad_handling_enabled = self.original_gamepad_state
|
||||||
|
|
||||||
|
# Reset dpad timer
|
||||||
|
self.dpad_timer.stop()
|
||||||
|
self.current_dpad_code = None
|
||||||
|
self.current_dpad_value = 0
|
||||||
|
|
||||||
|
# Clear the dialog reference
|
||||||
|
if dialog_attr_name == 'winetricks_dialog':
|
||||||
|
self.winetricks_dialog = None
|
||||||
|
elif dialog_attr_name == 'settings_dialog':
|
||||||
|
self.settings_dialog = None
|
||||||
|
elif dialog_attr_name == 'file_explorer':
|
||||||
|
self.file_explorer = None
|
||||||
|
elif dialog_attr_name == 'proton_manager_dialog':
|
||||||
|
self.proton_manager_dialog = None
|
||||||
|
|
||||||
|
# Reset the flag so original handlers can be saved again on next enable
|
||||||
|
if hasattr(self, '_original_handlers_saved'):
|
||||||
|
self._original_handlers_saved = False
|
||||||
|
|
||||||
|
# PROTON MANAGER SUPPORT
|
||||||
|
def enable_proton_manager_mode(self, proton_manager_dialog):
|
||||||
|
"""Setup gamepad handling for ProtonManagerDialog"""
|
||||||
|
try:
|
||||||
|
self._setup_mode_handlers(
|
||||||
|
proton_manager_dialog,
|
||||||
|
self.handle_proton_manager_button,
|
||||||
|
self.handle_proton_manager_dpad,
|
||||||
|
'proton_manager_dialog'
|
||||||
|
)
|
||||||
|
logger.debug("Gamepad handling successfully connected for ProtonManager")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error connecting gamepad handlers for ProtonManager: {e}")
|
||||||
|
|
||||||
|
def disable_proton_manager_mode(self):
|
||||||
|
"""Restore original main window handlers"""
|
||||||
|
try:
|
||||||
|
if self.proton_manager_dialog:
|
||||||
|
self._restore_original_handlers('proton_manager_dialog')
|
||||||
|
logger.debug("Gamepad handling successfully restored from ProtonManager")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error restoring gamepad handlers from ProtonManager: {e}")
|
||||||
|
|
||||||
|
def handle_proton_manager_button(self, button_code, value):
|
||||||
|
if self.proton_manager_dialog is None or value == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Handle common UI elements like QMessageBox, QMenu, etc.
|
||||||
|
if self._handle_common_ui_elements(button_code):
|
||||||
|
return
|
||||||
|
|
||||||
|
# ProtonManager-specific button handling
|
||||||
|
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_widget = focused.cellWidget(current_row, 0)
|
||||||
|
if checkbox_widget:
|
||||||
|
checkbox = checkbox_widget.findChild(QCheckBox)
|
||||||
|
if checkbox and checkbox.isEnabled():
|
||||||
|
checkbox.setChecked(not checkbox.isChecked())
|
||||||
|
return
|
||||||
|
|
||||||
|
elif button_code in BUTTONS['add_game']: # X: Download
|
||||||
|
self.proton_manager_dialog.download_selected()
|
||||||
|
|
||||||
|
elif button_code in BUTTONS['prev_dir']: # Y: Clear
|
||||||
|
self.proton_manager_dialog.clear_selection()
|
||||||
|
|
||||||
|
elif button_code in BUTTONS['back']: # B: Cancel/Close
|
||||||
|
# Cancel any active downloads/extractions before closing
|
||||||
|
if (self.proton_manager_dialog.current_extraction_thread and
|
||||||
|
self.proton_manager_dialog.current_extraction_thread.isRunning()) or \
|
||||||
|
(self.proton_manager_dialog.current_download_thread and
|
||||||
|
hasattr(self.proton_manager_dialog.current_download_thread, 'isRunning') and
|
||||||
|
self.proton_manager_dialog.current_download_thread.isRunning()):
|
||||||
|
# If there's an active download/extraction, cancel it
|
||||||
|
self.proton_manager_dialog.cancel_current_download()
|
||||||
|
else:
|
||||||
|
# If no active processes, just close the dialog
|
||||||
|
self.proton_manager_dialog.reject()
|
||||||
|
|
||||||
|
elif button_code in BUTTONS['prev_tab']: # LB: Previous tab
|
||||||
|
new_index = max(0, self.proton_manager_dialog.tab_widget.currentIndex() - 1)
|
||||||
|
self.proton_manager_dialog.tab_widget.setCurrentIndex(new_index)
|
||||||
|
self._focus_first_row_in_current_proton_manager_table()
|
||||||
|
|
||||||
|
elif button_code in BUTTONS['next_tab']: # RB: Next tab
|
||||||
|
new_index = min(self.proton_manager_dialog.tab_widget.count() - 1, self.proton_manager_dialog.tab_widget.currentIndex() + 1)
|
||||||
|
self.proton_manager_dialog.tab_widget.setCurrentIndex(new_index)
|
||||||
|
self._focus_first_row_in_current_proton_manager_table()
|
||||||
|
|
||||||
|
else:
|
||||||
|
self._parent.activateFocusedWidget()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in handle_proton_manager_button: {e}")
|
||||||
|
|
||||||
|
def handle_proton_manager_dpad(self, code, value, now):
|
||||||
|
if self.proton_manager_dialog is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if value == 0: # Release
|
||||||
|
self.dpad_timer.stop()
|
||||||
|
self.current_dpad_code = None
|
||||||
|
self.current_dpad_value = 0
|
||||||
|
return
|
||||||
|
|
||||||
|
# Timer setup
|
||||||
|
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)
|
||||||
|
self.dpad_timer.start()
|
||||||
|
self.current_dpad_code = code
|
||||||
|
self.current_dpad_value = value
|
||||||
|
|
||||||
|
table = self._get_current_proton_manager_table()
|
||||||
|
if not table or table.rowCount() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
current_row = table.currentRow()
|
||||||
|
|
||||||
|
if code == ecodes.ABS_HAT0Y: # Up/Down
|
||||||
|
step = -1 if value < 0 else 1
|
||||||
|
new_row = current_row + step
|
||||||
|
|
||||||
|
# Skip hidden rows
|
||||||
|
while 0 <= new_row < table.rowCount() and table.isRowHidden(new_row):
|
||||||
|
new_row += step
|
||||||
|
|
||||||
|
# Bounds check
|
||||||
|
if new_row < 0:
|
||||||
|
new_row = current_row
|
||||||
|
if new_row >= table.rowCount():
|
||||||
|
new_row = current_row
|
||||||
|
|
||||||
|
if new_row != current_row:
|
||||||
|
table.setCurrentCell(new_row, 0)
|
||||||
|
table.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
|
||||||
|
elif code == ecodes.ABS_HAT0X: # Left/Right (Tabs)
|
||||||
|
current_index = self.proton_manager_dialog.tab_widget.currentIndex()
|
||||||
|
if value < 0: # Left
|
||||||
|
new_index = max(0, current_index - 1)
|
||||||
|
else: # Right
|
||||||
|
new_index = min(self.proton_manager_dialog.tab_widget.count() - 1, current_index + 1)
|
||||||
|
|
||||||
|
if new_index != current_index:
|
||||||
|
self.proton_manager_dialog.tab_widget.setCurrentIndex(new_index)
|
||||||
|
self._focus_first_row_in_current_proton_manager_table()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in handle_proton_manager_dpad: {e}")
|
||||||
|
|
||||||
|
def _get_current_proton_manager_table(self):
|
||||||
|
if self.proton_manager_dialog:
|
||||||
|
current_container = self.proton_manager_dialog.tab_widget.currentWidget()
|
||||||
|
if current_container:
|
||||||
|
table = current_container.findChild(QTableWidget)
|
||||||
|
return table
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _focus_first_row_in_current_proton_manager_table(self):
|
||||||
|
table = self._get_current_proton_manager_table()
|
||||||
|
if table and table.rowCount() > 0:
|
||||||
|
table.setCurrentCell(0, 0)
|
||||||
|
table.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
|
||||||
# SETTINGS MODE
|
# SETTINGS MODE
|
||||||
def enable_settings_mode(self, settings_dialog):
|
def enable_settings_mode(self, settings_dialog):
|
||||||
"""Setup gamepad handling for ExeSettingsDialog"""
|
"""Setup gamepad handling for ExeSettingsDialog"""
|
||||||
try:
|
try:
|
||||||
self.settings_dialog = settings_dialog
|
self._setup_mode_handlers(
|
||||||
self.original_button_handler = self.handle_button_slot
|
settings_dialog,
|
||||||
self.original_dpad_handler = self.handle_dpad_slot
|
self.handle_settings_button,
|
||||||
self.original_gamepad_state = self._gamepad_handling_enabled
|
self.handle_settings_dpad,
|
||||||
|
'settings_dialog'
|
||||||
self.handle_button_slot = self.handle_settings_button
|
)
|
||||||
self.handle_dpad_slot = self.handle_settings_dpad
|
|
||||||
self._gamepad_handling_enabled = True
|
|
||||||
|
|
||||||
self.dpad_timer.stop()
|
|
||||||
self.current_dpad_code = None
|
|
||||||
self.current_dpad_value = 0
|
|
||||||
|
|
||||||
logger.debug("Gamepad handling successfully connected for SettingsDialog")
|
logger.debug("Gamepad handling successfully connected for SettingsDialog")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error connecting gamepad handlers for SettingsDialog: {e}")
|
logger.error(f"Error connecting gamepad handlers for SettingsDialog: {e}")
|
||||||
@@ -735,15 +1082,7 @@ class InputManager(QObject):
|
|||||||
"""Restore original main window handlers"""
|
"""Restore original main window handlers"""
|
||||||
try:
|
try:
|
||||||
if self.settings_dialog:
|
if self.settings_dialog:
|
||||||
self.handle_button_slot = self.original_button_handler
|
self._restore_original_handlers('settings_dialog')
|
||||||
self.handle_dpad_slot = self.original_dpad_handler
|
|
||||||
self._gamepad_handling_enabled = self.original_gamepad_state
|
|
||||||
|
|
||||||
self.settings_dialog = None
|
|
||||||
self.dpad_timer.stop()
|
|
||||||
self.current_dpad_code = None
|
|
||||||
self.current_dpad_value = 0
|
|
||||||
|
|
||||||
logger.debug("Gamepad handling successfully restored from Settings")
|
logger.debug("Gamepad handling successfully restored from Settings")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error restoring gamepad handlers from Settings: {e}")
|
logger.error(f"Error restoring gamepad handlers from Settings: {e}")
|
||||||
@@ -829,18 +1168,13 @@ class InputManager(QObject):
|
|||||||
# Standard interaction
|
# Standard interaction
|
||||||
focused = QApplication.focusWidget()
|
focused = QApplication.focusWidget()
|
||||||
if isinstance(focused, QTableWidget) and table and focused.currentRow() >= 0:
|
if isinstance(focused, QTableWidget) and table and focused.currentRow() >= 0:
|
||||||
row = focused.currentRow()
|
|
||||||
cell = focused.cellWidget(row, 1)
|
|
||||||
|
|
||||||
# Main settings (checkboxes)
|
# Main settings (checkboxes)
|
||||||
if self.settings_dialog and table == self.settings_dialog.settings_table:
|
if self.settings_dialog and table == self.settings_dialog.settings_table:
|
||||||
item = focused.item(row, 1)
|
self.handle_table_confirm(focused)
|
||||||
if item and (item.flags() & Qt.ItemFlag.ItemIsUserCheckable):
|
|
||||||
new_state = Qt.CheckState.Checked if item.checkState() == Qt.CheckState.Unchecked else Qt.CheckState.Unchecked
|
|
||||||
item.setCheckState(new_state)
|
|
||||||
return
|
return
|
||||||
|
|
||||||
# Advanced settings
|
# Advanced settings
|
||||||
|
cell = focused.cellWidget(focused.currentRow(), 1)
|
||||||
if isinstance(cell, QComboBox) and cell.isEnabled():
|
if isinstance(cell, QComboBox) and cell.isEnabled():
|
||||||
cell.showPopup()
|
cell.showPopup()
|
||||||
cell.setFocus()
|
cell.setFocus()
|
||||||
@@ -1635,51 +1969,10 @@ class InputManager(QObject):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
# Table navigation
|
# Table navigation using generalized methods
|
||||||
if isinstance(focused, QTableWidget):
|
if isinstance(focused, QTableWidget):
|
||||||
row_count = focused.rowCount()
|
self.handle_table_navigation(focused, code, value)
|
||||||
if row_count <= 0:
|
return
|
||||||
return
|
|
||||||
current_row = focused.currentRow()
|
|
||||||
if current_row < 0:
|
|
||||||
current_row = 0
|
|
||||||
focused.setCurrentCell(0, 0)
|
|
||||||
|
|
||||||
if code == ecodes.ABS_HAT0Y and value != 0:
|
|
||||||
# Vertical navigation
|
|
||||||
if value > 0: # Down
|
|
||||||
new_row = min(current_row + 1, row_count - 1)
|
|
||||||
elif value < 0: # Up
|
|
||||||
new_row = max(current_row - 1, 0)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
focused.setCurrentCell(new_row, focused.currentColumn())
|
|
||||||
item = focused.item(new_row, focused.currentColumn())
|
|
||||||
if item:
|
|
||||||
focused.scrollToItem(
|
|
||||||
item,
|
|
||||||
QAbstractItemView.ScrollHint.PositionAtCenter
|
|
||||||
)
|
|
||||||
focused.setFocus(Qt.FocusReason.OtherFocusReason)
|
|
||||||
return
|
|
||||||
elif code == ecodes.ABS_HAT0X and value != 0:
|
|
||||||
# Horizontal navigation
|
|
||||||
col_count = focused.columnCount()
|
|
||||||
current_col = focused.currentColumn()
|
|
||||||
if current_col < 0:
|
|
||||||
current_col = 0
|
|
||||||
|
|
||||||
if value < 0: # Left
|
|
||||||
new_col = max(current_col - 1, 0)
|
|
||||||
elif value > 0: # Right
|
|
||||||
new_col = min(current_col + 1, col_count - 1)
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
focused.setCurrentCell(focused.currentRow(), new_col)
|
|
||||||
focused.setFocus(Qt.FocusReason.OtherFocusReason)
|
|
||||||
return
|
|
||||||
|
|
||||||
# Search focus logic for tabs 0 and 1
|
# Search focus logic for tabs 0 and 1
|
||||||
if code == ecodes.ABS_HAT0Y and value < 0:
|
if code == ecodes.ABS_HAT0Y and value < 0:
|
||||||
@@ -2009,18 +2302,10 @@ class InputManager(QObject):
|
|||||||
|
|
||||||
# General actions: Activate, Back, Add
|
# General actions: Activate, Back, Add
|
||||||
if key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
if key in (Qt.Key.Key_Return, Qt.Key.Key_Enter):
|
||||||
# Special handling for table widgets with checkboxes
|
# Special handling for table widgets
|
||||||
if isinstance(focused, QTableWidget):
|
if isinstance(focused, QTableWidget):
|
||||||
current_row = focused.currentRow()
|
self.handle_table_confirm(focused)
|
||||||
current_col = focused.currentColumn()
|
return True
|
||||||
if current_row >= 0 and current_col >= 0:
|
|
||||||
# Check if the cell contains a checkbox
|
|
||||||
item = focused.item(current_row, current_col)
|
|
||||||
if item and (item.flags() & Qt.ItemFlag.ItemIsUserCheckable):
|
|
||||||
# Toggle the checkbox state
|
|
||||||
new_state = Qt.CheckState.Checked if item.checkState() == Qt.CheckState.Unchecked else Qt.CheckState.Unchecked
|
|
||||||
item.setCheckState(new_state)
|
|
||||||
return True
|
|
||||||
self._parent.activateFocusedWidget()
|
self._parent.activateFocusedWidget()
|
||||||
return True
|
return True
|
||||||
elif key in (Qt.Key.Key_Escape, Qt.Key.Key_Backspace):
|
elif key in (Qt.Key.Key_Escape, Qt.Key.Key_Backspace):
|
||||||
|
|||||||
@@ -1889,7 +1889,7 @@ class MainWindow(QMainWindow):
|
|||||||
|
|
||||||
def show_proton_manager(self):
|
def show_proton_manager(self):
|
||||||
"""Shows the Proton/WINE manager for downloading other WINE versions"""
|
"""Shows the Proton/WINE manager for downloading other WINE versions"""
|
||||||
show_proton_manager(self, self.portproton_location)
|
show_proton_manager(self, self.portproton_location, input_manager=self.input_manager)
|
||||||
|
|
||||||
def clear_prefix(self):
|
def clear_prefix(self):
|
||||||
"""Очищает префикс"""
|
"""Очищает префикс"""
|
||||||
|
|||||||
Reference in New Issue
Block a user