feat(settings): added initial gamepad navigation
All checks were successful
Code check / Check code (push) Successful in 1m20s
All checks were successful
Code check / Check code (push) Successful in 1m20s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@@ -125,6 +125,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 == 'settings':
|
||||||
|
dialog_actions = [
|
||||||
|
("confirm", _("Toggle")), # A / Cross
|
||||||
|
("add_game", _("Save")), # X / Triangle
|
||||||
|
("prev_dir", _("Search")), # 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)
|
||||||
|
|
||||||
@@ -1723,6 +1732,8 @@ class ExeSettingsDialog(QDialog):
|
|||||||
self.setModal(True)
|
self.setModal(True)
|
||||||
self.resize(1100, 720)
|
self.resize(1100, 720)
|
||||||
self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE + self.theme.MESSAGE_BOX_STYLE)
|
self.setStyleSheet(self.theme.MAIN_WINDOW_STYLE + self.theme.MESSAGE_BOX_STYLE)
|
||||||
|
# Set focus policy to handle focus changes properly
|
||||||
|
self.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||||
|
|
||||||
# Load toggle settings from config module
|
# Load toggle settings from config module
|
||||||
self.toggle_settings = get_toggle_settings()
|
self.toggle_settings = get_toggle_settings()
|
||||||
@@ -1741,6 +1752,27 @@ class ExeSettingsDialog(QDialog):
|
|||||||
|
|
||||||
self.current_theme_name = read_theme_from_config()
|
self.current_theme_name = read_theme_from_config()
|
||||||
|
|
||||||
|
# Enable settings dialog-specific mode
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.enable_settings_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='settings')
|
||||||
|
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)
|
||||||
|
|
||||||
|
# Initialize virtual keyboard
|
||||||
|
self.init_virtual_keyboard()
|
||||||
|
|
||||||
# Load current settings (includes list-db)
|
# Load current settings (includes list-db)
|
||||||
self.load_current_settings()
|
self.load_current_settings()
|
||||||
|
|
||||||
@@ -1757,6 +1789,19 @@ class ExeSettingsDialog(QDialog):
|
|||||||
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
self.main_layout.setContentsMargins(10, 10, 10, 10)
|
||||||
self.main_layout.setSpacing(10)
|
self.main_layout.setSpacing(10)
|
||||||
|
|
||||||
|
# Search bar
|
||||||
|
search_layout = QHBoxLayout()
|
||||||
|
self.search_label = QLabel(_("Search:"))
|
||||||
|
self.search_label.setStyleSheet(self.theme.PARAMS_TITLE_STYLE)
|
||||||
|
self.search_edit = QLineEdit()
|
||||||
|
self.search_edit.setStyleSheet(self.theme.ADDGAME_INPUT_STYLE)
|
||||||
|
self.search_edit.setPlaceholderText(_("Search settings..."))
|
||||||
|
self.search_edit.textChanged.connect(self.filter_settings)
|
||||||
|
self.search_edit.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||||
|
search_layout.addWidget(self.search_label)
|
||||||
|
search_layout.addWidget(self.search_edit)
|
||||||
|
self.main_layout.addLayout(search_layout)
|
||||||
|
|
||||||
# Tab widget
|
# Tab widget
|
||||||
self.tab_widget = QTabWidget()
|
self.tab_widget = QTabWidget()
|
||||||
self.tab_widget.setStyleSheet(self.theme.WINETRICKS_TAB_STYLE)
|
self.tab_widget.setStyleSheet(self.theme.WINETRICKS_TAB_STYLE)
|
||||||
@@ -2009,6 +2054,9 @@ class ExeSettingsDialog(QDialog):
|
|||||||
if len(setting['options']) == 1:
|
if len(setting['options']) == 1:
|
||||||
combo.setEnabled(False)
|
combo.setEnabled(False)
|
||||||
|
|
||||||
|
# Set focus policy for combo box
|
||||||
|
combo.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
|
||||||
|
|
||||||
self.advanced_table.setCellWidget(row, 1, combo)
|
self.advanced_table.setCellWidget(row, 1, combo)
|
||||||
self.advanced_widgets[setting['key']] = combo
|
self.advanced_widgets[setting['key']] = combo
|
||||||
self.original_display_values[setting['key']] = current_val
|
self.original_display_values[setting['key']] = current_val
|
||||||
@@ -2029,6 +2077,75 @@ class ExeSettingsDialog(QDialog):
|
|||||||
desc_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
desc_item.setTextAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter)
|
||||||
self.advanced_table.setItem(row, 2, desc_item)
|
self.advanced_table.setItem(row, 2, desc_item)
|
||||||
|
|
||||||
|
def init_virtual_keyboard(self):
|
||||||
|
"""Initialize virtual keyboard"""
|
||||||
|
self.keyboard = VirtualKeyboard(self, theme=self.theme, button_width=40)
|
||||||
|
self.keyboard.hide()
|
||||||
|
self.keyboard.current_input_widget = None
|
||||||
|
|
||||||
|
def show_virtual_keyboard(self, widget=None):
|
||||||
|
"""Show virtual keyboard for search or text input"""
|
||||||
|
if not widget:
|
||||||
|
# Default to search edit
|
||||||
|
widget = self.search_edit
|
||||||
|
|
||||||
|
if not widget or not widget.isVisible():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Set the current input widget
|
||||||
|
self.keyboard.current_input_widget = widget
|
||||||
|
|
||||||
|
# Position the keyboard
|
||||||
|
keyboard_height = 220
|
||||||
|
self.keyboard.setFixedWidth(self.width())
|
||||||
|
self.keyboard.setFixedHeight(keyboard_height)
|
||||||
|
self.keyboard.move(0, self.height() - keyboard_height)
|
||||||
|
|
||||||
|
# Show and raise keyboard
|
||||||
|
self.keyboard.setParent(self)
|
||||||
|
self.keyboard.show()
|
||||||
|
self.keyboard.raise_()
|
||||||
|
|
||||||
|
# Focus on first button of keyboard
|
||||||
|
first_button = self.keyboard.findFirstFocusableButton()
|
||||||
|
if first_button:
|
||||||
|
# First hide the current focus to prevent conflicts
|
||||||
|
focused_widget = QApplication.focusWidget()
|
||||||
|
if focused_widget and focused_widget != self.keyboard:
|
||||||
|
focused_widget.clearFocus()
|
||||||
|
|
||||||
|
# Then focus the keyboard button
|
||||||
|
QTimer.singleShot(50, lambda: first_button.setFocus())
|
||||||
|
|
||||||
|
def filter_settings(self, text):
|
||||||
|
"""Filter settings based on search text."""
|
||||||
|
# Filter main settings table
|
||||||
|
search_text = text.lower()
|
||||||
|
for row in range(self.settings_table.rowCount()):
|
||||||
|
name_item = self.settings_table.item(row, 0) # Setting name
|
||||||
|
desc_item = self.settings_table.item(row, 2) # Description
|
||||||
|
should_show = False
|
||||||
|
|
||||||
|
if name_item and search_text in name_item.text().lower():
|
||||||
|
should_show = True
|
||||||
|
elif desc_item and search_text in desc_item.text().lower():
|
||||||
|
should_show = True
|
||||||
|
|
||||||
|
self.settings_table.setRowHidden(row, not should_show)
|
||||||
|
|
||||||
|
# Filter advanced settings table
|
||||||
|
for row in range(self.advanced_table.rowCount()):
|
||||||
|
name_item = self.advanced_table.item(row, 0) # Setting name
|
||||||
|
desc_item = self.advanced_table.item(row, 2) # Description
|
||||||
|
should_show = False
|
||||||
|
|
||||||
|
if name_item and search_text in name_item.text().lower():
|
||||||
|
should_show = True
|
||||||
|
elif desc_item and search_text in desc_item.text().lower():
|
||||||
|
should_show = True
|
||||||
|
|
||||||
|
self.advanced_table.setRowHidden(row, not should_show)
|
||||||
|
|
||||||
def apply_changes(self):
|
def apply_changes(self):
|
||||||
"""Apply changes by collecting diffs from both main and advanced tabs."""
|
"""Apply changes by collecting diffs from both main and advanced tabs."""
|
||||||
changes = []
|
changes = []
|
||||||
@@ -2089,8 +2206,40 @@ class ExeSettingsDialog(QDialog):
|
|||||||
self.load_current_settings()
|
self.load_current_settings()
|
||||||
QMessageBox.information(self, _("Success"), _("Settings updated successfully."))
|
QMessageBox.information(self, _("Success"), _("Settings updated successfully."))
|
||||||
|
|
||||||
|
def keyPressEvent(self, event):
|
||||||
|
"""Override key press event to handle combo box interaction properly."""
|
||||||
|
# If a combo box in the advanced table is active and has an open dropdown,
|
||||||
|
# we need to handle Escape key specially to prevent dialog closure
|
||||||
|
focused_widget = QApplication.focusWidget()
|
||||||
|
if (event.key() == Qt.Key.Key_Escape and
|
||||||
|
isinstance(focused_widget, QComboBox) and
|
||||||
|
focused_widget.view().isVisible()):
|
||||||
|
# If a combo box dropdown is open, just close the dropdown instead of the dialog
|
||||||
|
focused_widget.hidePopup()
|
||||||
|
self.advanced_table.setFocus()
|
||||||
|
return
|
||||||
|
super().keyPressEvent(event)
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
|
# Hide virtual keyboard if visible
|
||||||
|
if hasattr(self, 'keyboard') and self.keyboard.isVisible():
|
||||||
|
self.keyboard.hide()
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.disable_settings_mode()
|
||||||
super().closeEvent(event)
|
super().closeEvent(event)
|
||||||
|
|
||||||
def reject(self):
|
def reject(self):
|
||||||
|
# Hide virtual keyboard if visible
|
||||||
|
if hasattr(self, 'keyboard') and self.keyboard.isVisible():
|
||||||
|
self.keyboard.hide()
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.disable_settings_mode()
|
||||||
super().reject()
|
super().reject()
|
||||||
|
|
||||||
|
def accept(self):
|
||||||
|
# Hide virtual keyboard if visible
|
||||||
|
if hasattr(self, 'keyboard') and self.keyboard.isVisible():
|
||||||
|
self.keyboard.hide()
|
||||||
|
if self.input_manager:
|
||||||
|
self.input_manager.disable_settings_mode()
|
||||||
|
super().accept()
|
||||||
|
|||||||
@@ -526,6 +526,39 @@ class InputManager(QObject):
|
|||||||
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}")
|
||||||
|
|
||||||
|
def enable_settings_mode(self, settings_dialog):
|
||||||
|
"""Setup gamepad handling for ExeSettingsDialog"""
|
||||||
|
try:
|
||||||
|
self.settings_dialog = settings_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_settings_button
|
||||||
|
self.handle_dpad_slot = self.handle_settings_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 SettingsDialog")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error connecting gamepad handlers for SettingsDialog: {e}")
|
||||||
|
|
||||||
|
def disable_settings_mode(self):
|
||||||
|
"""Restore original main window handlers"""
|
||||||
|
try:
|
||||||
|
if self.settings_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.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")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error restoring gamepad handlers from Settings: {e}")
|
||||||
|
|
||||||
def handle_winetricks_button(self, button_code, value):
|
def handle_winetricks_button(self, button_code, value):
|
||||||
if self.winetricks_dialog is None:
|
if self.winetricks_dialog is None:
|
||||||
return
|
return
|
||||||
@@ -592,6 +625,143 @@ class InputManager(QObject):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error in handle_winetricks_button: {e}")
|
logger.error(f"Error in handle_winetricks_button: {e}")
|
||||||
|
|
||||||
|
def handle_settings_button(self, button_code, value):
|
||||||
|
if self.settings_dialog is None or value == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
kb = getattr(self.settings_dialog, 'keyboard', None)
|
||||||
|
|
||||||
|
# Virtual keyboard
|
||||||
|
if kb and kb.isVisible():
|
||||||
|
if button_code in BUTTONS['back']:
|
||||||
|
kb.hide()
|
||||||
|
if kb.current_input_widget:
|
||||||
|
kb.current_input_widget.setFocus()
|
||||||
|
return
|
||||||
|
if button_code in BUTTONS['confirm'] or button_code in BUTTONS['context_menu']:
|
||||||
|
kb.activateFocusedKey()
|
||||||
|
return
|
||||||
|
if button_code in BUTTONS['prev_tab']:
|
||||||
|
kb.on_lang_click()
|
||||||
|
return
|
||||||
|
if button_code in BUTTONS['next_tab']:
|
||||||
|
kb.on_shift_click(not kb.shift_pressed)
|
||||||
|
return
|
||||||
|
if button_code in BUTTONS['add_game']:
|
||||||
|
kb.on_backspace_pressed()
|
||||||
|
return
|
||||||
|
return
|
||||||
|
|
||||||
|
# Pop-ups
|
||||||
|
popup = QApplication.activePopupWidget()
|
||||||
|
if popup:
|
||||||
|
if isinstance(popup, (QMessageBox, QDialog)):
|
||||||
|
if button_code in BUTTONS['confirm'] | BUTTONS['back']:
|
||||||
|
popup.accept()
|
||||||
|
return
|
||||||
|
if isinstance(popup, QMenu):
|
||||||
|
if button_code in BUTTONS['confirm'] and popup.activeAction():
|
||||||
|
popup.activeAction().trigger()
|
||||||
|
return
|
||||||
|
if button_code in BUTTONS['back']:
|
||||||
|
popup.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
table = self._get_current_settings_table()
|
||||||
|
|
||||||
|
# Ищем любой открытый комбобокс в Advanced-вкладке
|
||||||
|
open_combo = None
|
||||||
|
if table is not None and table == self.settings_dialog.advanced_table:
|
||||||
|
for r in range(table.rowCount()):
|
||||||
|
w = table.cellWidget(r, 1)
|
||||||
|
if isinstance(w, QComboBox) and w.view().isVisible():
|
||||||
|
open_combo = w
|
||||||
|
break
|
||||||
|
|
||||||
|
# B — закрываем открытый комбобокс или весь диалог
|
||||||
|
if button_code in BUTTONS['back']:
|
||||||
|
if open_combo:
|
||||||
|
open_combo.hidePopup()
|
||||||
|
if table is not None:
|
||||||
|
table.setFocus()
|
||||||
|
else:
|
||||||
|
self.settings_dialog.reject()
|
||||||
|
return
|
||||||
|
|
||||||
|
# A — главное действие
|
||||||
|
if button_code in BUTTONS['confirm']:
|
||||||
|
# Если есть открытый комбобокс — подтверждаем выбор в нём
|
||||||
|
if open_combo:
|
||||||
|
view = open_combo.view()
|
||||||
|
model_index = view.currentIndex()
|
||||||
|
if model_index.isValid():
|
||||||
|
open_combo.setCurrentIndex(model_index.row())
|
||||||
|
open_combo.hidePopup()
|
||||||
|
if table is not None:
|
||||||
|
table.setFocus()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Обычная логика: чекбоксы, открытие комбо, ввод текста
|
||||||
|
focused = QApplication.focusWidget()
|
||||||
|
if isinstance(focused, QTableWidget) and table and focused.currentRow() >= 0:
|
||||||
|
row = focused.currentRow()
|
||||||
|
cell = focused.cellWidget(row, 1)
|
||||||
|
|
||||||
|
# Main tab — чекбоксы
|
||||||
|
if table == self.settings_dialog.settings_table:
|
||||||
|
item = focused.item(row, 1)
|
||||||
|
# Only allow toggling if the item is user checkable (enabled)
|
||||||
|
if item and item.flags() & Qt.ItemFlag.ItemIsUserCheckable:
|
||||||
|
item.setCheckState(
|
||||||
|
Qt.CheckState.Checked
|
||||||
|
if item.checkState() == Qt.CheckState.Unchecked
|
||||||
|
else Qt.CheckState.Unchecked
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
# Advanced tab
|
||||||
|
if isinstance(cell, QComboBox):
|
||||||
|
# Only allow opening combo box if it's enabled
|
||||||
|
if cell.isEnabled():
|
||||||
|
cell.showPopup() # открываем, если закрыт
|
||||||
|
cell.setFocus()
|
||||||
|
return
|
||||||
|
if isinstance(cell, QLineEdit):
|
||||||
|
cell.setFocus()
|
||||||
|
self.settings_dialog.show_virtual_keyboard(cell)
|
||||||
|
return
|
||||||
|
|
||||||
|
if isinstance(focused, QLineEdit):
|
||||||
|
self.settings_dialog.show_virtual_keyboard(focused)
|
||||||
|
return
|
||||||
|
|
||||||
|
# X — Apply
|
||||||
|
if button_code in BUTTONS['add_game']:
|
||||||
|
self.settings_dialog.apply_changes()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Y — поиск + клавиатура
|
||||||
|
if button_code in BUTTONS['prev_dir']:
|
||||||
|
self.settings_dialog.search_edit.setFocus()
|
||||||
|
self.settings_dialog.show_virtual_keyboard(self.settings_dialog.search_edit)
|
||||||
|
return
|
||||||
|
|
||||||
|
# LB / RB — переключение вкладок
|
||||||
|
if button_code in BUTTONS['prev_tab']:
|
||||||
|
idx = max(0, self.settings_dialog.tab_widget.currentIndex() - 1)
|
||||||
|
self.settings_dialog.tab_widget.setCurrentIndex(idx)
|
||||||
|
self._focus_first_row_in_current_settings_table()
|
||||||
|
elif button_code in BUTTONS['next_tab']:
|
||||||
|
idx = min(self.settings_dialog.tab_widget.count() - 1, self.settings_dialog.tab_widget.currentIndex() + 1)
|
||||||
|
self.settings_dialog.tab_widget.setCurrentIndex(idx)
|
||||||
|
self._focus_first_row_in_current_settings_table()
|
||||||
|
else:
|
||||||
|
self._parent.activateFocusedWidget()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in handle_settings_button: {e}")
|
||||||
|
|
||||||
def handle_winetricks_dpad(self, code, value, now):
|
def handle_winetricks_dpad(self, code, value, now):
|
||||||
if self.winetricks_dialog is None:
|
if self.winetricks_dialog is None:
|
||||||
return
|
return
|
||||||
@@ -617,9 +787,21 @@ class InputManager(QObject):
|
|||||||
current_row = table.currentRow()
|
current_row = table.currentRow()
|
||||||
if code == ecodes.ABS_HAT0Y: # Up/Down: Navigate rows
|
if code == ecodes.ABS_HAT0Y: # Up/Down: Navigate rows
|
||||||
if value < 0: # Up
|
if value < 0: # Up
|
||||||
new_row = max(0, current_row - 1)
|
# Find the next visible row above the current row
|
||||||
|
new_row = current_row - 1
|
||||||
|
while new_row >= 0 and table.isRowHidden(new_row):
|
||||||
|
new_row -= 1
|
||||||
|
if new_row < 0:
|
||||||
|
# If no visible row above, stay at current position
|
||||||
|
new_row = current_row
|
||||||
elif value > 0: # Down
|
elif value > 0: # Down
|
||||||
new_row = min(table.rowCount() - 1, current_row + 1)
|
# Find the next visible row below the current row
|
||||||
|
new_row = current_row + 1
|
||||||
|
while new_row < table.rowCount() and table.isRowHidden(new_row):
|
||||||
|
new_row += 1
|
||||||
|
if new_row >= table.rowCount():
|
||||||
|
# If no visible row below, stay at current position
|
||||||
|
new_row = current_row
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
if new_row != current_row:
|
if new_row != current_row:
|
||||||
@@ -658,6 +840,175 @@ class InputManager(QObject):
|
|||||||
table.setCurrentCell(0, 0)
|
table.setCurrentCell(0, 0)
|
||||||
table.setFocus(Qt.FocusReason.OtherFocusReason)
|
table.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
|
||||||
|
def _focus_first_row_in_current_settings_table(self):
|
||||||
|
"""Focus the first row in the current settings table after tab switch."""
|
||||||
|
if self.settings_dialog is None:
|
||||||
|
return
|
||||||
|
current_table = self._get_current_settings_table()
|
||||||
|
if current_table and current_table.rowCount() > 0:
|
||||||
|
# For the advanced settings table, focus on column 1 (value column) which contains the widgets
|
||||||
|
# For the main settings table, focus on column 0 (name column) which contains checkboxes
|
||||||
|
focus_column = 1 if current_table == self.settings_dialog.advanced_table else 0
|
||||||
|
current_table.setCurrentCell(0, focus_column)
|
||||||
|
current_table.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
|
||||||
|
def _get_current_settings_table(self):
|
||||||
|
"""Get the current visible table from the settings dialog's tab widget."""
|
||||||
|
if self.settings_dialog is None:
|
||||||
|
return None
|
||||||
|
current_index = self.settings_dialog.tab_widget.currentIndex()
|
||||||
|
if current_index == 0:
|
||||||
|
return self.settings_dialog.settings_table
|
||||||
|
elif current_index == 1:
|
||||||
|
return self.settings_dialog.advanced_table
|
||||||
|
return None
|
||||||
|
|
||||||
|
def handle_settings_dpad(self, code, value, now):
|
||||||
|
if self.settings_dialog is None:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
# Check if virtual keyboard is visible - if so, handle keyboard navigation instead
|
||||||
|
if (hasattr(self.settings_dialog, 'keyboard') and
|
||||||
|
self.settings_dialog.keyboard.isVisible()):
|
||||||
|
# Handle keyboard navigation with D-pad
|
||||||
|
if code in (ecodes.ABS_HAT0X, ecodes.ABS_X):
|
||||||
|
normalized_value = 0
|
||||||
|
if code == ecodes.ABS_X: # Left stick
|
||||||
|
# Apply deadzone
|
||||||
|
if abs(value) < self.dead_zone:
|
||||||
|
self.current_dpad_code = None
|
||||||
|
self.current_dpad_value = 0
|
||||||
|
self.axis_moving = False
|
||||||
|
self.dpad_timer.stop()
|
||||||
|
return
|
||||||
|
normalized_value = 1 if value > self.dead_zone else -1
|
||||||
|
else: # D-pad
|
||||||
|
normalized_value = value # D-pad already gives -1, 0, 1
|
||||||
|
|
||||||
|
if normalized_value != 0:
|
||||||
|
if normalized_value > 0: # Right
|
||||||
|
self.settings_dialog.keyboard.move_focus_right()
|
||||||
|
elif normalized_value < 0: # Left
|
||||||
|
self.settings_dialog.keyboard.move_focus_left()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Handle vertical navigation for keyboard
|
||||||
|
elif code in (ecodes.ABS_HAT0Y, ecodes.ABS_Y):
|
||||||
|
normalized_value = 0
|
||||||
|
if code == ecodes.ABS_Y: # Left stick
|
||||||
|
# Apply deadzone
|
||||||
|
if abs(value) < self.dead_zone:
|
||||||
|
self.current_dpad_code = None
|
||||||
|
self.current_dpad_value = 0
|
||||||
|
self.axis_moving = False
|
||||||
|
self.dpad_timer.stop()
|
||||||
|
return
|
||||||
|
normalized_value = 1 if value > self.dead_zone else -1
|
||||||
|
else: # D-pad
|
||||||
|
normalized_value = value # D-pad already gives -1, 0, 1
|
||||||
|
|
||||||
|
if normalized_value != 0:
|
||||||
|
if normalized_value > 0: # Down
|
||||||
|
self.settings_dialog.keyboard.move_focus_down()
|
||||||
|
elif normalized_value < 0: # Up
|
||||||
|
self.settings_dialog.keyboard.move_focus_up()
|
||||||
|
return
|
||||||
|
return # Don't continue with table navigation if keyboard is visible
|
||||||
|
|
||||||
|
# Get the current settings table first
|
||||||
|
table = self._get_current_settings_table()
|
||||||
|
if not table or table.rowCount() == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check if a combo box in advanced settings has an open dropdown - if so, handle combo box navigation
|
||||||
|
if (table == self.settings_dialog.advanced_table and
|
||||||
|
table.currentRow() >= 0 and
|
||||||
|
table.currentColumn() == 1): # Value column
|
||||||
|
cell_widget = table.cellWidget(table.currentRow(), 1)
|
||||||
|
if isinstance(cell_widget, QComboBox) and cell_widget.view().isVisible():
|
||||||
|
# Only handle combo box dropdown navigation for vertical movements
|
||||||
|
if code == ecodes.ABS_HAT0Y and value != 0:
|
||||||
|
current_index = cell_widget.currentIndex()
|
||||||
|
if value < 0: # Up: move to previous item
|
||||||
|
new_index = max(0, current_index - 1)
|
||||||
|
elif value > 0: # Down: move to next item
|
||||||
|
new_index = min(cell_widget.count() - 1, current_index + 1)
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
if new_index != current_index:
|
||||||
|
cell_widget.setCurrentIndex(new_index)
|
||||||
|
cell_widget.setCurrentText(cell_widget.itemText(new_index)) # Ensure text is updated
|
||||||
|
# If combo box is active, don't continue with table navigation (for any direction)
|
||||||
|
return # Don't continue with table navigation if combo box is active
|
||||||
|
# If not a combo box or dropdown not visible, continue with regular table navigation below
|
||||||
|
# Continue with regular table navigation
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
current_row = table.currentRow()
|
||||||
|
if code == ecodes.ABS_HAT0Y: # Up/Down: Navigate rows
|
||||||
|
if value < 0: # Up
|
||||||
|
# Find the next visible row above the current row
|
||||||
|
new_row = current_row - 1
|
||||||
|
while new_row >= 0 and table.isRowHidden(new_row):
|
||||||
|
new_row -= 1
|
||||||
|
if new_row < 0:
|
||||||
|
# If no visible row above, stay at current position
|
||||||
|
new_row = current_row
|
||||||
|
elif value > 0: # Down
|
||||||
|
# Find the next visible row below the current row
|
||||||
|
new_row = current_row + 1
|
||||||
|
while new_row < table.rowCount() and table.isRowHidden(new_row):
|
||||||
|
new_row += 1
|
||||||
|
if new_row >= table.rowCount():
|
||||||
|
# If no visible row below, stay at current position
|
||||||
|
new_row = current_row
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
if new_row != current_row:
|
||||||
|
# For the advanced settings table, focus on column 1 (value column) which contains the widgets
|
||||||
|
# For the main settings table, focus on column 0 (name column) which contains checkboxes
|
||||||
|
focus_column = 1 if table == self.settings_dialog.advanced_table else 0
|
||||||
|
table.setCurrentCell(new_row, focus_column)
|
||||||
|
table.setFocus(Qt.FocusReason.OtherFocusReason)
|
||||||
|
elif code == ecodes.ABS_HAT0X: # Left/Right: Switch tabs or navigate in cells
|
||||||
|
if value < 0: # Left
|
||||||
|
current_col = table.currentColumn()
|
||||||
|
if current_col > 0:
|
||||||
|
# Move to previous column in the same row
|
||||||
|
table.setCurrentCell(current_row, max(0, current_col - 1))
|
||||||
|
else:
|
||||||
|
# Switch to previous tab if at first column
|
||||||
|
current_index = self.settings_dialog.tab_widget.currentIndex()
|
||||||
|
new_index = max(0, current_index - 1)
|
||||||
|
self.settings_dialog.tab_widget.setCurrentIndex(new_index)
|
||||||
|
self._focus_first_row_in_current_settings_table()
|
||||||
|
elif value > 0: # Right
|
||||||
|
current_col = table.currentColumn()
|
||||||
|
if current_col < table.columnCount() - 1:
|
||||||
|
# Move to next column in the same row
|
||||||
|
table.setCurrentCell(current_row, min(table.columnCount() - 1, current_col + 1))
|
||||||
|
else:
|
||||||
|
# Switch to next tab if at last column
|
||||||
|
current_index = self.settings_dialog.tab_widget.currentIndex()
|
||||||
|
new_index = min(self.settings_dialog.tab_widget.count() - 1, current_index + 1)
|
||||||
|
self.settings_dialog.tab_widget.setCurrentIndex(new_index)
|
||||||
|
self._focus_first_row_in_current_settings_table()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in handle_settings_dpad: {e}")
|
||||||
|
|
||||||
def handle_navigation_repeat(self):
|
def handle_navigation_repeat(self):
|
||||||
"""Плавное повторение движения с переменной скоростью для FileExplorer"""
|
"""Плавное повторение движения с переменной скоростью для FileExplorer"""
|
||||||
try:
|
try:
|
||||||
|
|||||||
Reference in New Issue
Block a user