9 Commits

Author SHA1 Message Date
0f74a47aed chore(localization): update
All checks were successful
Check Translations (disabled until yaspeller is fixed) / check-translations (push) Has been skipped
Code check / Check code (push) Successful in 1m31s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-11-24 23:49:29 +05:00
666ec654a0 fix(ui): prevent text truncation in show_gamepad_tooltip
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-11-24 23:34:57 +05:00
0c25cc9fd2 chore(settings): rework tabble
All checks were successful
Code check / Check code (push) Successful in 1m19s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-11-24 23:23:56 +05:00
5de83dbf49 fix(settings): drop .ppdb from show-ppdb
All checks were successful
Code check / Check code (push) Successful in 1m22s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-11-24 23:03:31 +05:00
1821faadf6 styles for virtual keyboard
All checks were successful
Code check / Check code (push) Successful in 2m56s
2025-11-24 16:47:41 +00:00
17f0a6b0ea fix(ui): prevent segfault by validating widget existence in async callbacks
All checks were successful
Code check / Check code (push) Successful in 1m26s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-11-24 16:27:02 +05:00
e9c75b998f chore(localization): update
All checks were successful
Check Translations (disabled until yaspeller is fixed) / check-translations (push) Has been skipped
Code check / Check code (push) Successful in 1m17s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-11-23 15:43:59 +05:00
bbfbc00c11 fix(settings): fix virtual keyboard
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-11-23 15:28:30 +05:00
b7804fdd01 fix(ui): unify handling of QMessageBox and QMenu in controller
All checks were successful
Code check / Check code (push) Successful in 1m14s
- Added _handle_common_ui_elements() for QMessageBox, QMenu, etc.
- Fixed A/B behavior for single- and multi-button QMessageBox dialogs
- Improved D-pad navigation and focused-button selection
- Removed duplicated logic in specialized handlers

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-11-23 15:13:21 +05:00
15 changed files with 621 additions and 333 deletions

View File

@@ -20,3 +20,33 @@ Stop Game
Fullscreen
Fulscreen
\t
Горячая
vkbasalt
dgVoodoo2
Zink
Vulkan
VKD3D
DirectX12
Prev Dir
Forced
GOverlay
Glide
all
futex
DLSS
fullscreen
ProtonGE
window
compositing
Zink
Use
bundled
dxvk
older games
versions
DLL Overrides
COMP
VKD3D
Select needed
CPUs
cores

View File

@@ -21,9 +21,9 @@ Current translation status:
| Locale | Progress | Translated |
| :----- | -------: | ---------: |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 323 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 323 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 323 of 323 |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 of 338 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 of 338 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 338 of 338 |
---

View File

@@ -21,9 +21,9 @@
| Локаль | Прогресс | Переведено |
| :----- | -------: | ---------: |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 323 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 323 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 323 из 323 |
| [de_DE](./de_DE/LC_MESSAGES/messages.po) | 0% | 0 из 338 |
| [es_ES](./es_ES/LC_MESSAGES/messages.po) | 0% | 0 из 338 |
| [ru_RU](./ru_RU/LC_MESSAGES/messages.po) | 100% | 338 из 338 |
---

View File

@@ -1818,9 +1818,7 @@ class ExeSettingsDialog(QDialog):
self.settings_table = QTableWidget()
self.settings_table.setAlternatingRowColors(True)
self.settings_table.setFocusPolicy(Qt.FocusPolicy.StrongFocus)
# self.settings_table.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
self.settings_table.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows)
self.settings_table.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
self.settings_table.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
self.settings_table.setColumnCount(3)
self.settings_table.setHorizontalHeaderLabels([_("Setting"), _("Value"), _("Description")])
self.settings_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.ResizeToContents)
@@ -1939,7 +1937,7 @@ class ExeSettingsDialog(QDialog):
# Load current settings
process = QProcess(self)
process.finished.connect(self.on_show_ppdb_finished)
process.start(self.start_sh[0], ["cli", "--show-ppdb", f"{self.exe_path}.ppdb"])
process.start(self.start_sh[0], ["cli", "--show-ppdb", f"{self.exe_path}"])
def on_show_ppdb_finished(self, exit_code, exit_status):
"""Handle --show-ppdb output."""
@@ -2001,10 +1999,8 @@ class ExeSettingsDialog(QDialog):
current_val = self.current_settings.get(toggle, '0')
is_blocked = toggle in self.blocked_keys
checkbox = QTableWidgetItem()
checkbox.setFlags(checkbox.flags() | Qt.ItemFlag.ItemIsUserCheckable)
check_state = Qt.CheckState.Checked if current_val == '1' and not is_blocked else Qt.CheckState.Unchecked
checkbox.setCheckState(check_state)
checkbox.setTextAlignment(Qt.AlignmentFlag.AlignCenter)
if is_blocked:
checkbox.setFlags(checkbox.flags() & ~Qt.ItemFlag.ItemIsUserCheckable)
checkbox.setBackground(QColor(240, 240, 240))
@@ -2330,43 +2326,28 @@ class ExeSettingsDialog(QDialog):
def show_gamepad_tooltip(self, show=True, text=""):
"""Show or hide the gamepad tooltip with the provided text."""
if show and text:
# First set the text to measure the required size
# Set the text to the tooltip
self.gamepad_tooltip.setText(text)
# Calculate appropriate size based on text content
# Temporarily set a large size to allow proper text wrapping measurement
self.gamepad_tooltip.setFixedSize(500, 300)
# Use font metrics to calculate the proper size with Qt's wrapping
font_metrics = self.gamepad_tooltip.fontMetrics()
# Calculate text dimensions - wrap at max width
max_width = 500 # Maximum width in pixels
text_lines = text.split('\n') # Handle multiline text
# If text is longer than can fit in a single line at max width, wrap it
wrapped_lines = []
for line in text_lines:
if font_metrics.horizontalAdvance(line) <= max_width:
wrapped_lines.append(line)
else:
# Word wrap the line to fit within max width
words = line.split(' ')
current_line = ''
for word in words:
test_line = current_line + ' ' + word if current_line else word
if font_metrics.horizontalAdvance(test_line) <= max_width:
current_line = test_line
else:
if current_line:
wrapped_lines.append(current_line)
current_line = word
if current_line:
wrapped_lines.append(current_line)
# Calculate the required size using Qt's text wrapping functionality
# We'll allow Qt to do the wrapping and measure accordingly
# Using the boundingRect with TextWordWrap flag for accurate measurement
text_rect = font_metrics.boundingRect(
0, 0, max_width - 20, 1000, # Leave space for padding
Qt.TextFlag.TextWordWrap | Qt.TextFlag.TextExpandTabs,
text
)
# Set the final wrapped text
wrapped_text = '\n'.join(wrapped_lines)
self.gamepad_tooltip.setText(wrapped_text)
# Calculate the required size
rect = font_metrics.boundingRect(0, 0, max_width, 1000, Qt.TextFlag.TextWordWrap, wrapped_text)
required_width = min(max_width, rect.width() + 20) # Add padding
required_height = min(300, rect.height() + 16) # Add padding, max height 300
# Calculate final dimensions with sufficient padding
required_width = min(max_width, text_rect.width() + 25) # Add padding
required_height = min(300, text_rect.height() + 25) # Add padding
# Position the tooltip near the currently focused cell
current_table = self.advanced_table if self.tab_widget.currentIndex() == 1 else self.settings_table

View File

@@ -572,29 +572,11 @@ class InputManager(QObject):
return
try:
# 1. Check active Popups (QMessageBox, QMenu)
popup = QApplication.activePopupWidget()
if popup:
if isinstance(popup, QMessageBox):
if button_code in (BUTTONS['confirm'] | BUTTONS['back']):
popup.accept()
return
elif isinstance(popup, QMenu):
if button_code in BUTTONS['confirm']:
if popup.activeAction():
popup.activeAction().trigger()
elif button_code in BUTTONS['back']:
popup.close()
return
# Handle common UI elements like QMessageBox, QMenu, etc.
if self._handle_common_ui_elements(button_code):
return
# 2. Check Top-Level Message Boxes
for widget in QApplication.topLevelWidgets():
if isinstance(widget, QMessageBox) and widget.isVisible():
if button_code in (BUTTONS['confirm'] | BUTTONS['back']):
widget.accept()
return
# 3. Main Logic
# Winetricks-specific button handling
focused = QApplication.focusWidget()
if button_code in BUTTONS['confirm']: # A: Toggle checkbox
@@ -742,7 +724,7 @@ class InputManager(QObject):
logger.error(f"Error restoring gamepad handlers from Settings: {e}")
def handle_settings_button(self, button_code, value):
if self.settings_dialog is None or value == 0:
if self.settings_dialog is None:
return
try:
@@ -750,112 +732,123 @@ class InputManager(QObject):
kb = getattr(self.settings_dialog, 'keyboard', None)
if kb and kb.isVisible():
if button_code in BUTTONS['back']:
kb.hide()
if kb.current_input_widget:
kb.current_input_widget.setFocus()
if value != 0: # Only handle press, not release
kb.hide()
if kb.current_input_widget:
kb.current_input_widget.setFocus()
return # Return early to avoid dialog closing logic
elif button_code in (BUTTONS['confirm'] | BUTTONS['context_menu']):
kb.activateFocusedKey()
if value != 0: # Only handle press, not release
kb.activateFocusedKey()
return
elif button_code in BUTTONS['prev_tab']:
kb.on_lang_click()
if value != 0: # Only handle press, not release
kb.on_lang_click()
return
elif button_code in BUTTONS['next_tab']:
kb.on_shift_click(not kb.shift_pressed)
if value != 0: # Only handle press, not release
kb.on_shift_click(not kb.shift_pressed)
return
elif button_code in BUTTONS['add_game']:
kb.on_backspace_pressed()
if value != 0: # Press event
kb.on_backspace_pressed()
else: # Release event
kb.stop_backspace_repeat()
return
# Handle common UI elements like QMessageBox, QMenu, etc.
if self._handle_common_ui_elements(button_code):
return
# 2. Popup Handling
popup = QApplication.activePopupWidget()
if popup:
if isinstance(popup, (QMessageBox, QDialog)):
if button_code in (BUTTONS['confirm'] | BUTTONS['back']):
# Handle other QDialogs
if value != 0: # Only handle press events, not releases
popup = QApplication.activePopupWidget()
if isinstance(popup, QDialog):
if button_code in BUTTONS['confirm']:
popup.accept()
return
if isinstance(popup, QMenu):
if button_code in BUTTONS['confirm'] and popup.activeAction():
popup.activeAction().trigger()
elif button_code in BUTTONS['back']:
popup.close()
popup.reject()
return
# 3. Advanced Tab Combo Box Logic
table = self._get_current_settings_table()
open_combo = None
if table 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
# 3. Advanced Tab Combo Box Logic
table = self._get_current_settings_table()
open_combo = None
if table 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 Button - Close combo or dialog
if button_code in BUTTONS['back']:
if open_combo:
open_combo.hidePopup()
if table:
table.setFocus()
else:
self.settings_dialog.reject()
return
# A Button - Confirm
if button_code in BUTTONS['confirm']:
if open_combo:
view = open_combo.view()
if view.currentIndex().isValid():
open_combo.setCurrentIndex(view.currentIndex().row())
open_combo.hidePopup()
if table:
table.setFocus()
return
# Standard interaction
focused = QApplication.focusWidget()
if isinstance(focused, QTableWidget) and table and focused.currentRow() >= 0:
row = focused.currentRow()
cell = focused.cellWidget(row, 1)
# Main settings (checkboxes)
if self.settings_dialog and table == self.settings_dialog.settings_table:
item = focused.item(row, 1)
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
# Advanced settings
if isinstance(cell, QComboBox) and 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
# 4. Global Shortcuts
if button_code in BUTTONS['add_game']: # X: Apply
self.settings_dialog.apply_changes()
elif button_code in BUTTONS['prev_dir']: # Y: Search + Keyboard
self.settings_dialog.search_edit.setFocus()
self.settings_dialog.show_virtual_keyboard(self.settings_dialog.search_edit)
elif button_code in BUTTONS['prev_tab']: # LB
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']: # RB
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()
# B Button - Close combo or dialog
if button_code in BUTTONS['back']:
if open_combo:
open_combo.hidePopup()
if table:
table.setFocus()
else:
self.settings_dialog.reject()
return
# A Button - Confirm
if button_code in BUTTONS['confirm']:
if open_combo:
view = open_combo.view()
if view.currentIndex().isValid():
open_combo.setCurrentIndex(view.currentIndex().row())
open_combo.hidePopup()
if table:
table.setFocus()
return
# Standard interaction
focused = QApplication.focusWidget()
if isinstance(focused, QTableWidget) and table and focused.currentRow() >= 0:
row = focused.currentRow()
cell = focused.cellWidget(row, 1)
# Main settings (checkboxes)
if self.settings_dialog and table == self.settings_dialog.settings_table:
item = focused.item(row, 1)
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
# Advanced settings
if isinstance(cell, QComboBox) and 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
# 4. Global Shortcuts
if button_code in BUTTONS['add_game']: # X: Apply
self.settings_dialog.apply_changes()
elif button_code in BUTTONS['prev_dir']: # Y: Search + Keyboard
self.settings_dialog.search_edit.setFocus()
self.settings_dialog.show_virtual_keyboard(self.settings_dialog.search_edit)
elif button_code in BUTTONS['prev_tab']: # LB
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']: # RB
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()
self._parent.activateFocusedWidget()
except Exception as e:
logger.error(f"Error in handle_settings_button: {e}")
@@ -1243,26 +1236,8 @@ class InputManager(QObject):
self._parent.openSystemOverlay()
return
# Handle QMenu (context menu)
if isinstance(popup, QMenu):
if button_code in BUTTONS['confirm']:
if popup.activeAction():
popup.activeAction().trigger()
popup.close()
return
elif button_code in BUTTONS['back']:
popup.close()
return
return
# Handle QMessageBox
if isinstance(active, QMessageBox):
if button_code in BUTTONS['confirm']:
active.accept() # Close QMessageBox with the default button
return
elif button_code in BUTTONS['back']:
active.reject() # Close QMessageBox on back button
return
# Handle common UI elements like QMenu and QMessageBox
if self._handle_common_ui_elements(button_code):
return
# Handle QComboBox
@@ -1463,21 +1438,40 @@ class InputManager(QObject):
if not app or not active:
return
# Handle SystemOverlay, AddGameDialog, or QMessageBox navigation with D-pad
if isinstance(active, QDialog) and code == ecodes.ABS_HAT0X and value != 0:
if isinstance(active, QMessageBox): # Specific handling for QMessageBox
if not focused or not active.focusWidget():
# If no widget is focused, focus the first focusable widget
focusables = active.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively)
focusables = [w for w in focusables if w.focusPolicy() & Qt.FocusPolicy.StrongFocus]
if focusables:
focusables[0].setFocus(Qt.FocusReason.OtherFocusReason)
return
# Handle QMessageBox navigation with D-pad (for multiple buttons)
if isinstance(active, QMessageBox) and not isinstance(focused, QTableWidget):
if not focused or not active.focusWidget():
# If no widget is focused, focus the first focusable widget
focusables = active.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively)
focusables = [w for w in focusables if w.focusPolicy() & Qt.FocusPolicy.StrongFocus]
if focusables:
focusables[0].setFocus(Qt.FocusReason.OtherFocusReason)
return
if code == ecodes.ABS_HAT0X and value != 0: # Horizontal navigation
if value > 0: # Right
active.focusNextChild()
elif value < 0: # Left
active.focusPreviousChild()
elif code == ecodes.ABS_HAT0Y and value != 0: # Vertical navigation
if value > 0: # Down
active.focusNextChild()
elif value < 0: # Up
active.focusPreviousChild()
return
# Handle SystemOverlay, AddGameDialog, or other QDialog navigation with D-pad
elif isinstance(active, QDialog) and code == ecodes.ABS_HAT0X and value != 0:
if not focused or not active.focusWidget():
# If no widget is focused, focus the first focusable widget
focusables = active.findChildren(QWidget, options=Qt.FindChildOption.FindChildrenRecursively)
focusables = [w for w in focusables if w.focusPolicy() & Qt.FocusPolicy.StrongFocus]
if focusables:
focusables[0].setFocus(Qt.FocusReason.OtherFocusReason)
return
if value > 0: # Right
active.focusNextChild()
elif value < 0: # Left
active.focusPreviousChild()
return
elif isinstance(active, QDialog) and code == ecodes.ABS_HAT0Y and value != 0 and not isinstance(focused, QTableWidget): # Keep up/down for other dialogs
if not focused or not active.focusWidget():
# If no widget is focused, focus the first focusable widget
@@ -2312,3 +2306,56 @@ class InputManager(QObject):
except Exception as e:
logger.error(f"Error during cleanup: {e}", exc_info=True)
def _handle_common_ui_elements(self, button_code):
"""
Common handler for common UI elements like QMessageBox, QMenu, etc.
Returns True if the event was handled, False otherwise.
"""
# Check for popup widgets first
popup = QApplication.activePopupWidget()
if popup:
if isinstance(popup, QMessageBox):
self._handle_qmessagebox_button(popup, button_code)
return True
elif isinstance(popup, QMenu):
if button_code in BUTTONS['confirm']:
if popup.activeAction():
popup.activeAction().trigger()
popup.close()
elif button_code in BUTTONS['back']:
popup.close()
return True
# Check for top-level QMessageBox specifically
active = QApplication.activeWindow()
if isinstance(active, QMessageBox):
self._handle_qmessagebox_button(active, button_code)
return True
# Check for top-level message boxes (additional check)
for widget in QApplication.topLevelWidgets():
if isinstance(widget, QMessageBox) and widget.isVisible() and widget != active:
self._handle_qmessagebox_button(widget, button_code)
return True
return False # Not handled by common handler
def _handle_qmessagebox_button(self, msg_box, button_code):
"""
Unified handler for QMessageBox across all modes.
For single button dialogs, A button accepts the dialog.
For multiple button dialogs, navigate between buttons and allow selection.
"""
if button_code in BUTTONS['confirm']:
# Check if there's a focused button in the message box
focused_widget = msg_box.focusWidget()
if focused_widget:
# If a specific button is focused, click/activate it
focused_widget.click()
else:
# If no button is focused, accept with default behavior
msg_box.accept()
elif button_code in BUTTONS['back']:
# For back button, reject the dialog (typically cancels or selects cancel button)
msg_box.reject()

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-11-11 17:00+0500\n"
"POT-Creation-Date: 2025-11-24 23:48+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de_DE\n"
@@ -279,6 +279,12 @@ msgstr ""
msgid "Next Tab"
msgstr ""
msgid "Save"
msgstr ""
msgid "Search"
msgstr ""
#, python-brace-format
msgid "Launching {0}"
msgstr ""
@@ -368,6 +374,12 @@ msgstr ""
msgid "Exe Settings"
msgstr ""
msgid "Search:"
msgstr ""
msgid "Search settings..."
msgstr ""
msgid "Main"
msgstr ""
@@ -461,9 +473,6 @@ msgstr ""
msgid "Fullscreen"
msgstr ""
msgid "Search"
msgstr ""
msgid "Installation already in progress."
msgstr ""
@@ -483,6 +492,12 @@ msgstr ""
msgid "Installation error."
msgstr ""
msgid "Refresh Grid"
msgstr ""
msgid "Game library refreshed"
msgstr ""
msgid "Loading Steam games..."
msgstr ""
@@ -495,6 +510,15 @@ msgstr ""
msgid "Find Games ..."
msgstr ""
msgid "A refresh is already in progress..."
msgstr ""
msgid "Refreshing..."
msgstr ""
msgid "Refreshing game library..."
msgstr ""
#, python-brace-format
msgid "Added '{name}'"
msgstr ""
@@ -872,9 +896,6 @@ msgstr ""
msgid "Run the application in a terminal"
msgstr ""
msgid "Disable startup mode and WINE version selector window"
msgstr ""
msgid "Use system GameMode for performance optimization"
msgstr ""
@@ -938,6 +959,39 @@ msgstr ""
msgid "Use async dxvk-sarek (experimental)"
msgstr ""
msgid "Wine Version"
msgstr ""
msgid "Select the Wine or Proton version to use for this executable."
msgstr ""
msgid "Prefix Name"
msgstr ""
msgid "Specify the Wine prefix to run this game with"
msgstr ""
msgid "Newest"
msgstr ""
msgid "Stable"
msgstr ""
msgid "Vulkan Backend"
msgstr ""
msgid ""
"Select the DirectX → Vulkan/OpenGL backend:\n"
"\n"
"• Newest latest DXVK + VKD3D (best compatibility/performance, requires "
"modern drivers: AMD Mesa 25+, NVIDIA 550.54.14+, Intel Mesa 24.2+)\n"
"• Stable older, well-tested DXVK + VKD3D (works on any Vulkan 1.3+ "
"driver)\n"
"• Sarek experimental DXVK-Sarek + VKD3D-Sarek (supports older drivers, "
"Vulkan 1.1+)\n"
"• WINED3D OpenGL fallback (lowest performance, use only if others fail)"
msgstr ""
msgid "Windows version"
msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-11-11 17:00+0500\n"
"POT-Creation-Date: 2025-11-24 23:48+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: es_ES\n"
@@ -279,6 +279,12 @@ msgstr ""
msgid "Next Tab"
msgstr ""
msgid "Save"
msgstr ""
msgid "Search"
msgstr ""
#, python-brace-format
msgid "Launching {0}"
msgstr ""
@@ -368,6 +374,12 @@ msgstr ""
msgid "Exe Settings"
msgstr ""
msgid "Search:"
msgstr ""
msgid "Search settings..."
msgstr ""
msgid "Main"
msgstr ""
@@ -461,9 +473,6 @@ msgstr ""
msgid "Fullscreen"
msgstr ""
msgid "Search"
msgstr ""
msgid "Installation already in progress."
msgstr ""
@@ -483,6 +492,12 @@ msgstr ""
msgid "Installation error."
msgstr ""
msgid "Refresh Grid"
msgstr ""
msgid "Game library refreshed"
msgstr ""
msgid "Loading Steam games..."
msgstr ""
@@ -495,6 +510,15 @@ msgstr ""
msgid "Find Games ..."
msgstr ""
msgid "A refresh is already in progress..."
msgstr ""
msgid "Refreshing..."
msgstr ""
msgid "Refreshing game library..."
msgstr ""
#, python-brace-format
msgid "Added '{name}'"
msgstr ""
@@ -872,9 +896,6 @@ msgstr ""
msgid "Run the application in a terminal"
msgstr ""
msgid "Disable startup mode and WINE version selector window"
msgstr ""
msgid "Use system GameMode for performance optimization"
msgstr ""
@@ -938,6 +959,39 @@ msgstr ""
msgid "Use async dxvk-sarek (experimental)"
msgstr ""
msgid "Wine Version"
msgstr ""
msgid "Select the Wine or Proton version to use for this executable."
msgstr ""
msgid "Prefix Name"
msgstr ""
msgid "Specify the Wine prefix to run this game with"
msgstr ""
msgid "Newest"
msgstr ""
msgid "Stable"
msgstr ""
msgid "Vulkan Backend"
msgstr ""
msgid ""
"Select the DirectX → Vulkan/OpenGL backend:\n"
"\n"
"• Newest latest DXVK + VKD3D (best compatibility/performance, requires "
"modern drivers: AMD Mesa 25+, NVIDIA 550.54.14+, Intel Mesa 24.2+)\n"
"• Stable older, well-tested DXVK + VKD3D (works on any Vulkan 1.3+ "
"driver)\n"
"• Sarek experimental DXVK-Sarek + VKD3D-Sarek (supports older drivers, "
"Vulkan 1.1+)\n"
"• WINED3D OpenGL fallback (lowest performance, use only if others fail)"
msgstr ""
msgid "Windows version"
msgstr ""

View File

@@ -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-11-11 17:00+0500\n"
"POT-Creation-Date: 2025-11-24 23:48+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"
@@ -277,6 +277,12 @@ msgstr ""
msgid "Next Tab"
msgstr ""
msgid "Save"
msgstr ""
msgid "Search"
msgstr ""
#, python-brace-format
msgid "Launching {0}"
msgstr ""
@@ -366,6 +372,12 @@ msgstr ""
msgid "Exe Settings"
msgstr ""
msgid "Search:"
msgstr ""
msgid "Search settings..."
msgstr ""
msgid "Main"
msgstr ""
@@ -459,9 +471,6 @@ msgstr ""
msgid "Fullscreen"
msgstr ""
msgid "Search"
msgstr ""
msgid "Installation already in progress."
msgstr ""
@@ -481,6 +490,12 @@ msgstr ""
msgid "Installation error."
msgstr ""
msgid "Refresh Grid"
msgstr ""
msgid "Game library refreshed"
msgstr ""
msgid "Loading Steam games..."
msgstr ""
@@ -493,6 +508,15 @@ msgstr ""
msgid "Find Games ..."
msgstr ""
msgid "A refresh is already in progress..."
msgstr ""
msgid "Refreshing..."
msgstr ""
msgid "Refreshing game library..."
msgstr ""
#, python-brace-format
msgid "Added '{name}'"
msgstr ""
@@ -870,9 +894,6 @@ msgstr ""
msgid "Run the application in a terminal"
msgstr ""
msgid "Disable startup mode and WINE version selector window"
msgstr ""
msgid "Use system GameMode for performance optimization"
msgstr ""
@@ -936,6 +957,39 @@ msgstr ""
msgid "Use async dxvk-sarek (experimental)"
msgstr ""
msgid "Wine Version"
msgstr ""
msgid "Select the Wine or Proton version to use for this executable."
msgstr ""
msgid "Prefix Name"
msgstr ""
msgid "Specify the Wine prefix to run this game with"
msgstr ""
msgid "Newest"
msgstr ""
msgid "Stable"
msgstr ""
msgid "Vulkan Backend"
msgstr ""
msgid ""
"Select the DirectX → Vulkan/OpenGL backend:\n"
"\n"
"• Newest latest DXVK + VKD3D (best compatibility/performance, requires "
"modern drivers: AMD Mesa 25+, NVIDIA 550.54.14+, Intel Mesa 24.2+)\n"
"• Stable older, well-tested DXVK + VKD3D (works on any Vulkan 1.3+ "
"driver)\n"
"• Sarek experimental DXVK-Sarek + VKD3D-Sarek (supports older drivers, "
"Vulkan 1.1+)\n"
"• WINED3D OpenGL fallback (lowest performance, use only if others fail)"
msgstr ""
msgid "Windows version"
msgstr ""

View File

@@ -9,8 +9,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-11-11 17:00+0500\n"
"PO-Revision-Date: 2025-11-11 17:00+0500\n"
"POT-Creation-Date: 2025-11-24 23:48+0500\n"
"PO-Revision-Date: 2025-11-24 23:47+0500\n"
"Last-Translator: \n"
"Language: ru_RU\n"
"Language-Team: ru_RU <LL@li.org>\n"
@@ -286,6 +286,12 @@ msgstr "Предыдущая вкладка"
msgid "Next Tab"
msgstr "Следующая вкладка"
msgid "Save"
msgstr "Сохранить"
msgid "Search"
msgstr "Поиск"
#, python-brace-format
msgid "Launching {0}"
msgstr "Идёт запуск {0}"
@@ -375,6 +381,12 @@ msgstr "Компоненты успешно установлены."
msgid "Exe Settings"
msgstr "Настройки EXE"
msgid "Search:"
msgstr "Поиск:"
msgid "Search settings..."
msgstr "Поиск настроек..."
msgid "Main"
msgstr "Основные"
@@ -468,9 +480,6 @@ msgstr "Назад"
msgid "Fullscreen"
msgstr "Полный экран"
msgid "Search"
msgstr "Поиск"
msgid "Installation already in progress."
msgstr "Установка уже выполняется."
@@ -490,6 +499,12 @@ msgstr "Установка не удалась."
msgid "Installation error."
msgstr "Ошибка установки."
msgid "Refresh Grid"
msgstr "Обновить"
msgid "Game library refreshed"
msgstr "Игровая библиотека обновлена"
msgid "Loading Steam games..."
msgstr "Загрузка игр из Steam..."
@@ -502,6 +517,15 @@ msgstr "Игровая библиотека"
msgid "Find Games ..."
msgstr "Найти игры..."
msgid "A refresh is already in progress..."
msgstr "Обновление уже выполняется..."
msgid "Refreshing..."
msgstr "Обновление..."
msgid "Refreshing game library..."
msgstr "Обновление игровой библиотеки..."
#, python-brace-format
msgid "Added '{name}'"
msgstr "'{name}' добавлен(а)"
@@ -897,9 +921,6 @@ msgstr "Запускать приложение в виртуальном раб
msgid "Run the application in a terminal"
msgstr "Запускать приложение в терминале"
msgid "Disable startup mode and WINE version selector window"
msgstr "Отключить окно выбора режима запуска и версии WINE"
msgid "Use system GameMode for performance optimization"
msgstr "Использовать системный GameMode для оптимизации производительности"
@@ -963,6 +984,50 @@ msgstr "Использовать встроенные dxvk/vkd3d из Wine/Proto
msgid "Use async dxvk-sarek (experimental)"
msgstr "Использовать асинхронный dxvk-sarek (экспериментально)"
msgid "Wine Version"
msgstr "Версия Wine"
msgid "Select the Wine or Proton version to use for this executable."
msgstr "Выбор версии Wine или Proton для использования с этим исполняемым файлом."
msgid "Prefix Name"
msgstr "Имя префикса"
msgid "Specify the Wine prefix to run this game with"
msgstr "Укажите префикс Wine для запуска этой игры"
msgid "Newest"
msgstr "Новейший"
msgid "Stable"
msgstr "Стабильный"
msgid "Vulkan Backend"
msgstr "Vulkan рендеринг"
msgid ""
"Select the DirectX → Vulkan/OpenGL backend:\n"
"\n"
"• Newest latest DXVK + VKD3D (best compatibility/performance, requires "
"modern drivers: AMD Mesa 25+, NVIDIA 550.54.14+, Intel Mesa 24.2+)\n"
"• Stable older, well-tested DXVK + VKD3D (works on any Vulkan 1.3+ "
"driver)\n"
"• Sarek experimental DXVK-Sarek + VKD3D-Sarek (supports older drivers, "
"Vulkan 1.1+)\n"
"• WINED3D OpenGL fallback (lowest performance, use only if others fail)"
msgstr ""
"Выберите бэкэнд DirectX → Vulkan/OpenGL:\n"
"\n"
"• Новейший — последние версии DXVK + VKD3D (наилучшая "
"совместимость/производительность, требует современных драйверов: AMD Mesa"
" 25+, NVIDIA 550.54.14+, Intel Mesa 24.2+)\n"
"• Стабильный — более старая, хорошо протестированная версия DXVK + VKD3D "
"(работает с любыми драйверами Vulkan 1.3+)\n"
"• Sarek — экспериментальная версия DXVK-Sarek + VKD3D-Sarek (поддерживает"
" более старые драйверы, Vulkan 1.1+)\n"
"• WINED3D — резервный вариант OpenGL (наименьшая производительность, "
"используйте только в случае сбоя других вариантов)"
msgid "Windows version"
msgstr "Версия Windows"

View File

@@ -2314,7 +2314,8 @@ class MainWindow(QMainWindow):
def openGameDetailPage(self, name, description, cover_path=None, appid="", exec_line="", controller_support="", last_launch="", formatted_playtime="", protondb_tier="", game_source="", anticheat_status=""):
detailPage = QWidget()
self._animations = {}
if not hasattr(self, '_animations'):
self._animations = {}
imageLabel = QLabel()
imageLabel.setFixedSize(300, 450)
self._detail_page_active = True
@@ -2322,38 +2323,53 @@ class MainWindow(QMainWindow):
# Функция загрузки изображения и обновления стилей
def load_image_and_restore_effect():
if not detailPage or detailPage.isHidden():
logger.warning("Detail page is None or hidden, skipping image load")
# Check if detail page still exists and is valid
if not detailPage or detailPage.isHidden() or detailPage.parent() is None:
logger.warning("Detail page is None, hidden, or no longer valid, skipping image load")
return
detailPage.setWindowOpacity(1.0)
try:
detailPage.setWindowOpacity(1.0)
except RuntimeError:
logger.warning("Detail page already deleted, skipping opacity set")
return
if cover_path:
def on_pixmap_ready(pixmap):
if not detailPage or detailPage.isHidden():
logger.warning("Detail page is None or hidden, skipping pixmap update")
# Check if detail page still exists and is valid
if not detailPage or detailPage.isHidden() or detailPage.parent() is None:
logger.warning("Detail page is None, hidden, or no longer valid, skipping pixmap update")
return
rounded = round_corners(pixmap, 10)
imageLabel.setPixmap(rounded)
logger.debug("Pixmap set for imageLabel")
try:
rounded = round_corners(pixmap, 10)
imageLabel.setPixmap(rounded)
logger.debug("Pixmap set for imageLabel")
def on_palette_ready(palette):
if not detailPage or detailPage.isHidden():
logger.warning("Detail page is None or hidden, skipping palette update")
return
dark_palette = [self.darkenColor(color, factor=200) for color in palette]
stops = ",\n".join(
[f"stop:{i/(len(dark_palette)-1):.2f} {dark_palette[i].name()}" for i in range(len(dark_palette))]
)
detailPage.setStyleSheet(self.theme.detail_page_style(stops))
detailPage.update()
logger.debug("Stylesheet updated with palette")
self.getColorPalette_async(cover_path, num_colors=5, callback=on_palette_ready)
def on_palette_ready(palette):
# Check if detail page still exists and is valid
if not detailPage or detailPage.isHidden() or detailPage.parent() is None:
logger.warning("Detail page is None, hidden, or no longer valid, skipping palette update")
return
try:
dark_palette = [self.darkenColor(color, factor=200) for color in palette]
stops = ",\n".join(
[f"stop:{i/(len(dark_palette)-1):.2f} {dark_palette[i].name()}" for i in range(len(dark_palette))]
)
detailPage.setStyleSheet(self.theme.detail_page_style(stops))
detailPage.update()
logger.debug("Stylesheet updated with palette")
except RuntimeError:
logger.warning("Detail page already deleted, skipping palette stylesheet update")
self.getColorPalette_async(cover_path, num_colors=5, callback=on_palette_ready)
except RuntimeError:
logger.warning("Detail page already deleted, skipping pixmap update")
load_pixmap_async(cover_path, 300, 450, on_pixmap_ready)
else:
detailPage.setStyleSheet(self.theme.DETAIL_PAGE_NO_COVER_STYLE)
detailPage.update()
try:
detailPage.setStyleSheet(self.theme.DETAIL_PAGE_NO_COVER_STYLE)
detailPage.update()
except RuntimeError:
logger.warning("Detail page already deleted, skipping no-cover stylesheet update")
def cleanup_animation():
if detailPage in self._animations:
@@ -2594,6 +2610,9 @@ class MainWindow(QMainWindow):
return
if not self._current_detail_page or self._current_detail_page.isHidden() or not self._current_detail_page.parent():
return
# Additional check: make sure the detail page in the stacked widget is still our current detail page
if self.stackedWidget.currentWidget() != self._current_detail_page and self._current_detail_page not in [self.stackedWidget.widget(i) for i in range(self.stackedWidget.count())]:
return
if results:
game = results[0] # Берем первый результат
@@ -2617,33 +2636,42 @@ class MainWindow(QMainWindow):
has_data = False
if main_story_time is not None:
mainStoryTitle = QLabel(_("MAIN STORY"))
mainStoryTitle.setStyleSheet(self.theme.LAST_LAUNCH_TITLE_STYLE)
mainStoryValue = QLabel(main_story_time)
mainStoryValue.setStyleSheet(self.theme.LAST_LAUNCH_VALUE_STYLE)
hltbLayout.addWidget(mainStoryTitle)
hltbLayout.addWidget(mainStoryValue)
hltbLayout.addSpacing(30)
has_data = True
try:
mainStoryTitle = QLabel(_("MAIN STORY"))
mainStoryTitle.setStyleSheet(self.theme.LAST_LAUNCH_TITLE_STYLE)
mainStoryValue = QLabel(main_story_time)
mainStoryValue.setStyleSheet(self.theme.LAST_LAUNCH_VALUE_STYLE)
hltbLayout.addWidget(mainStoryTitle)
hltbLayout.addWidget(mainStoryValue)
hltbLayout.addSpacing(30)
has_data = True
except RuntimeError:
logger.warning("Detail page already deleted, skipping main story time update")
if main_extra_time is not None:
mainExtraTitle = QLabel(_("MAIN + SIDES"))
mainExtraTitle.setStyleSheet(self.theme.PLAY_TIME_TITLE_STYLE)
mainExtraValue = QLabel(main_extra_time)
mainExtraValue.setStyleSheet(self.theme.PLAY_TIME_VALUE_STYLE)
hltbLayout.addWidget(mainExtraTitle)
hltbLayout.addWidget(mainExtraValue)
hltbLayout.addSpacing(30)
has_data = True
try:
mainExtraTitle = QLabel(_("MAIN + SIDES"))
mainExtraTitle.setStyleSheet(self.theme.PLAY_TIME_TITLE_STYLE)
mainExtraValue = QLabel(main_extra_time)
mainExtraValue.setStyleSheet(self.theme.PLAY_TIME_VALUE_STYLE)
hltbLayout.addWidget(mainExtraTitle)
hltbLayout.addWidget(mainExtraValue)
hltbLayout.addSpacing(30)
has_data = True
except RuntimeError:
logger.warning("Detail page already deleted, skipping main extra time update")
if completionist_time is not None:
completionistTitle = QLabel(_("COMPLETIONIST"))
completionistTitle.setStyleSheet(self.theme.LAST_LAUNCH_TITLE_STYLE)
completionistValue = QLabel(completionist_time)
completionistValue.setStyleSheet(self.theme.LAST_LAUNCH_VALUE_STYLE)
hltbLayout.addWidget(completionistTitle)
hltbLayout.addWidget(completionistValue)
has_data = True
try:
completionistTitle = QLabel(_("COMPLETIONIST"))
completionistTitle.setStyleSheet(self.theme.LAST_LAUNCH_TITLE_STYLE)
completionistValue = QLabel(completionist_time)
completionistValue.setStyleSheet(self.theme.LAST_LAUNCH_VALUE_STYLE)
hltbLayout.addWidget(completionistTitle)
hltbLayout.addWidget(completionistValue)
has_data = True
except RuntimeError:
logger.warning("Detail page already deleted, skipping completionist time update")
# Если есть данные, добавляем layout во вторую строку
if has_data:

View File

@@ -20,7 +20,6 @@ def get_toggle_settings():
'PW_HIDE_NVIDIA_GPU': _("Disguise all NVIDIA GPU features"),
'PW_VIRTUAL_DESKTOP': _("Run the application in WINE virtual desktop"),
'PW_USE_TERMINAL': _("Run the application in a terminal"),
'PW_GUI_DISABLED_CS': _("Disable startup mode and WINE version selector window"),
'PW_USE_GAMEMODE': _("Use system GameMode for performance optimization"),
'PW_USE_D3D_EXTRAS': _("Enable forced use of third-party DirectX libraries"),
'PW_FIX_VIDEO_IN_GAME': _("Fix pink-tinted video playback in some games"),
@@ -70,7 +69,7 @@ def get_advanced_settings(disabled_text, logical_core_options, locale_options,
advanced_settings.append({
'key': 'PW_PREFIX_NAME',
'name': _("Prefix Name"),
'description': _("Select the Wine prefix to use."),
'description': _("Specify the Wine prefix to run this game with"),
'type': 'combo',
'options': prefix_options,
'default': 'DEFAULT'
@@ -78,10 +77,10 @@ def get_advanced_settings(disabled_text, logical_core_options, locale_options,
# 3. Vulkan Backend
vulkan_options = [
_("Auto latest DXVK + VKD3D (recommended)"), # → 6
_("Stable proven DXVK + VKD3D"), # → 2
_("Sarek experimental DXVK-Sarek + VKD3D-Sarek"), # → 1
_("WINED3D OpenGL (fallback only)") # → 0
_("Newest"), # → 6
_("Stable"), # → 2
("Sarek"), # → 1
("WINED3D OpenGL") # → 0
]
# Маппинг: отображаемый текст → реальное значение в ppdb
@@ -96,22 +95,11 @@ def get_advanced_settings(disabled_text, logical_core_options, locale_options,
'key': 'PW_VULKAN_USE',
'name': _("Vulkan Backend"),
'description': _(
"Select the rendering backend for translating DirectX → Vulkan/OpenGL:\n\n"
"Auto latest DXVK + VKD3D (recommended)\n"
" The newest versions from the developers. Give the best compatibility and performance in modern games.\n"
" Require up-to-date drivers:\n"
" AMD: Mesa 25.0+ or proprietary AMDVLK 2024.Q4+\n"
" NVIDIA: driver 550.54.14 or newer\n"
" Intel: Mesa 24.2+\n\n"
"• Stable proven DXVK + VKD3D\n"
" Older but extremely well-tested versions. Work on any drivers that support Vulkan 1.3+.\n"
" The best choice if you have problems with the newest versions.\n\n"
"• Sarek experimental DXVK-Sarek + VKD3D-Sarek\n"
" Work even on older drivers and video cards that support at least Vulkan 1.1.\n\n"
"• WINED3D OpenGL translation (fallback)\n"
" No DXVK/VKD3D used. DirectX is translated to OpenGL via built-in WineD3D.\n"
" Works on absolutely any hardware, but performance is significantly lower.\n"
" Use only as a last resort when nothing else starts."
"Select the DirectX → Vulkan/OpenGL backend:\n\n"
"Newest latest DXVK + VKD3D (best compatibility/performance, requires modern drivers: AMD Mesa 25+, NVIDIA 550.54.14+, Intel Mesa 24.2+)\n"
"• Stable older, well-tested DXVK + VKD3D (works on any Vulkan 1.3+ driver)\n"
"• Sarek experimental DXVK-Sarek + VKD3D-Sarek (supports older drivers, Vulkan 1.1+)\n"
"• WINED3D OpenGL fallback (lowest performance, use only if others fail)"
),
'type': 'combo',
'options': vulkan_options,

View File

@@ -25,6 +25,7 @@ color_e = "#404554"
color_f = "#ffffff"
color_g = "rgba(0, 0, 0, 0)"
color_h = "transparent"
color_i = "rgba(40, 42, 51, 0.9)"
GAME_CARD_ANIMATION = {
# Тип анимации при входе и выходе на детальную страницу
@@ -217,54 +218,40 @@ CONTEXT_MENU_STYLE = f"""
}}
"""
VIRTUAL_KEYBOARD_STYLE = """
VirtualKeyboard {
background-color: rgba(30, 30, 30, 200);
border-radius: 0px;
border: none;
}
QPushButton {
VIRTUAL_KEYBOARD_STYLE = f"""
QWidget {{
background: {color_i};
}}
QPushButton {{
font-size: 14px;
border: 1px solid #555;
border-top-color: #666;
border-left-color: #666;
border-radius: 3px;
border: {border_a} {color_h};
border-radius: {border_radius_a};
min-width: 30px;
min-height: 30px;
padding: 4px;
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #505050, stop:1 #404040);
color: #e0e0e0;
}
QPushButton:hover {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #606060, stop:1 #505050);
border: 1px solid #666;
border-top-color: #777;
border-left-color: #777;
}
QPushButton:focus {
border: 2px solid #4a90e2;
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #5a5a5a, stop:1 #454545);
}
QPushButton:pressed {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #3a3a3a, stop:1 #303030);
border: 1px solid #444;
border-bottom-color: #555;
border-right-color: #555;
padding-top: 5px;
padding-bottom: 3px;
padding-left: 5px;
padding-right: 3px;
}
QPushButton[checked="true"] {
background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #4a90e2, stop:1 #3a7ad2);
color: white;
border: 1px solid #2a6ac2;
border-top-color: #5aa0f2;
border-left-color: #5aa0f2;
}
QPushButton[checked="true"]:focus {
border: 2px solid #6aa3f5;
}
padding: 5px;
background-color: {color_c};
color: {color_f};
}}
QPushButton:hover {{
background-color: {color_a};
border: {border_b} {color_a};
}}
QPushButton:focus {{
border: {border_b} {color_a};
background-color: {color_a};
}}
QPushButton:pressed {{
background-color: {color_c};
border: {border_a} {color_h};
}}
QPushButton[checked="true"] {{
background-color: {color_a};
color: {color_f};
border: {border_a} {color_h};
}}
QPushButton[checked="true"]:focus {{
border: {border_b} {color_f};
}}
"""
# ГЛОБАЛЬНЫЙ СТИЛЬ ДЛЯ ОКНА (ФОН), ЛЭЙБЛОВ, КНОПОК