8 Commits

Author SHA1 Message Date
6853cfd5f0 docs(changelog): update
All checks were successful
Check Translations / check-translations (push) Successful in 21s
Code and build check / Check code (push) Successful in 1m34s
Code and build check / Build with uv (push) Successful in 52s
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-26 14:58:17 +05:00
6ab3c4d232 docs(localization): update
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-26 14:56:44 +05:00
34ce0a74b0 fix: resolve Pyright type errors
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-26 14:54:53 +05:00
88e9d1d7c5 feat(dialogs): add file extension filters for exe and image selection to File Explorer
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-26 14:35:24 +05:00
4af4d1f0b0 fix(dialogs): fix File Explorer buttons translate
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-26 14:27:09 +05:00
69d2960312 fix(input-manager): centralize gamepad handling for FileExplorer
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-26 14:24:29 +05:00
afad92d967 feat(dialogs): implement FileExplorer for cover image selection
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-26 14:07:21 +05:00
3f0e7487df feat(dialogs): added gamepad support to FileExplorer thanks to @Vector_null
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
2025-06-26 13:51:50 +05:00
14 changed files with 493 additions and 98 deletions

View File

@@ -9,6 +9,7 @@
- Аргумент `--session` для запуска приложения в gamescope с GAMESCOPE_CMD - Аргумент `--session` для запуска приложения в gamescope с GAMESCOPE_CMD
- Начальная поддержка EGS (Без EOS, скачивания игр и запуска игр из сторонних магазинов) - Начальная поддержка EGS (Без EOS, скачивания игр и запуска игр из сторонних магазинов)
- Автодополнение bash для комманды portprotonqt - Автодополнение bash для комманды portprotonqt
- Поддержка геймпадов в диалоге выбора игры
### Changed ### Changed
- Удалены сборки для Fedora 40 - Удалены сборки для Fedora 40
@@ -27,6 +28,7 @@
### Contributors ### Contributors
- @Dervart - @Dervart
- @Vector_null
--- ---

View File

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

View File

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

View File

@@ -1,19 +1,20 @@
import os import os
import tempfile import tempfile
from typing import cast
from PySide6.QtGui import QPixmap from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import ( from PySide6.QtWidgets import (
QDialog, QLineEdit, QFormLayout, QPushButton, QDialog, QLineEdit, QFormLayout, QPushButton,
QHBoxLayout, QDialogButtonBox, QFileDialog, QLabel QHBoxLayout, QDialogButtonBox, QLabel, QVBoxLayout, QListWidget
) )
from PySide6.QtCore import Qt from PySide6.QtCore import Qt, QObject, Signal
from icoextract import IconExtractor, IconExtractorError from icoextract import IconExtractor, IconExtractorError
from PIL import Image from PIL import Image
from portprotonqt.main_window import MainWindow # Import MainWindow for type casting
from portprotonqt.config_utils import get_portproton_location from portprotonqt.config_utils import get_portproton_location
from portprotonqt.localization import _ from portprotonqt.localization import _
from portprotonqt.logger import get_logger from portprotonqt.logger import get_logger
import portprotonqt.themes.standart.styles as default_styles import portprotonqt.themes.standart.styles as default_styles
from portprotonqt.themes.standart.styles import FileExplorerStyles
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -81,6 +82,152 @@ def generate_thumbnail(inputfile, outfile, size=128, force_resize=True):
logger.error(f"Ошибка при сохранении миниатюры: {e}") logger.error(f"Ошибка при сохранении миниатюры: {e}")
return False return False
class FileSelectedSignal(QObject):
file_selected = Signal(str) # Сигнал с путем к выбранному файлу
class FileExplorer(QDialog):
def __init__(self, parent=None, file_filter=None):
super().__init__(parent)
self.file_signal = FileSelectedSignal()
self.file_filter = file_filter # Store the file filter
self.setup_ui()
# Настройки окна
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setWindowFlags(self.windowFlags() & ~Qt.WindowType.WindowContextHelpButtonHint)
self.setStyleSheet(FileExplorerStyles.WINDOW_STYLE)
# Find InputManager from parent
self.input_manager = None
parent = self.parent()
while parent:
if hasattr(parent, 'input_manager'):
self.input_manager = cast(MainWindow, parent).input_manager
break
parent = parent.parent()
if self.input_manager:
self.input_manager.enable_file_explorer_mode(self)
def setup_ui(self):
"""Настройка интерфейса"""
self.setWindowTitle("File Explorer")
self.setGeometry(100, 100, 800, 600)
self.main_layout = QVBoxLayout()
self.main_layout.setContentsMargins(10, 10, 10, 10)
self.main_layout.setSpacing(10)
self.setLayout(self.main_layout)
self.path_label = QLabel()
self.path_label.setStyleSheet(FileExplorerStyles.PATH_LABEL_STYLE)
self.main_layout.addWidget(self.path_label)
# Список файлов
self.file_list = QListWidget()
self.file_list.setStyleSheet(FileExplorerStyles.LIST_STYLE)
self.file_list.itemClicked.connect(self.handle_item_click)
self.main_layout.addWidget(self.file_list)
# Кнопки
self.button_layout = QHBoxLayout()
self.button_layout.setSpacing(10)
self.select_button = QPushButton(_("Select"))
self.cancel_button = QPushButton(_("Cancel"))
self.select_button.setStyleSheet(FileExplorerStyles.BUTTON_STYLE)
self.cancel_button.setStyleSheet(FileExplorerStyles.BUTTON_STYLE)
self.button_layout.addWidget(self.select_button)
self.button_layout.addWidget(self.cancel_button)
self.main_layout.addLayout(self.button_layout)
self.select_button.clicked.connect(self.select_item)
self.cancel_button.clicked.connect(self.reject)
# Начальная папка
self.current_path = os.path.expanduser("~")
self.update_file_list()
def move_selection(self, direction):
"""Перемещение выбора по списку"""
current_row = self.file_list.currentRow()
if direction < 0 and current_row > 0: # Вверх
self.file_list.setCurrentRow(current_row - 1)
elif direction > 0 and current_row < self.file_list.count() - 1: # Вниз
self.file_list.setCurrentRow(current_row + 1)
self.file_list.scrollToItem(self.file_list.currentItem())
def handle_item_click(self, item):
"""Обработка клика мышью"""
self.file_list.setCurrentItem(item)
self.select_item()
def select_item(self):
"""Обработка выбора файла/папки"""
if self.file_list.count() == 0:
return
selected = self.file_list.currentItem().text()
full_path = os.path.join(self.current_path, selected)
if os.path.isdir(full_path):
self.current_path = full_path
self.update_file_list()
else:
self.file_signal.file_selected.emit(full_path)
self.accept()
def update_file_list(self):
"""Обновление списка файлов"""
self.file_list.clear()
try:
if self.current_path != "/":
self.file_list.addItem("../")
items = os.listdir(self.current_path)
dirs = [d for d in items if os.path.isdir(os.path.join(self.current_path, d))]
# Apply file filter if provided
files = [f for f in items if os.path.isfile(os.path.join(self.current_path, f))]
if self.file_filter:
files = [f for f in files if f.lower().endswith(self.file_filter)]
for d in sorted(dirs):
self.file_list.addItem(f"{d}/")
for f in sorted(files):
self.file_list.addItem(f)
self.path_label.setText(f"Path: {self.current_path}")
self.file_list.setCurrentRow(0)
except PermissionError:
self.path_label.setText(f"Access denied: {self.current_path}")
def closeEvent(self, event):
"""Закрытие окна"""
try:
if self.input_manager:
self.input_manager.disable_file_explorer_mode()
if self.parent():
parent = cast(MainWindow, self.parent())
parent.activateWindow()
parent.setFocus()
except Exception as e:
logger.error(f"Error in closeEvent: {e}")
super().closeEvent(event)
def reject(self):
"""Закрытие диалога"""
if self.input_manager:
self.input_manager.disable_file_explorer_mode()
super().reject()
def accept(self):
"""Принятие диалога"""
if self.input_manager:
self.input_manager.disable_file_explorer_mode()
super().accept()
class AddGameDialog(QDialog): class AddGameDialog(QDialog):
def __init__(self, parent=None, theme=None, edit_mode=False, game_name=None, exe_path=None, cover_path=None): def __init__(self, parent=None, theme=None, edit_mode=False, game_name=None, exe_path=None, cover_path=None):
@@ -160,28 +307,52 @@ class AddGameDialog(QDialog):
self.updatePreview() self.updatePreview()
def browseExe(self): def browseExe(self):
fileNameAndFilter = QFileDialog.getOpenFileName( """Открывает файловый менеджер для выбора exe-файла"""
self, try:
_("Select Executable"), file_explorer = FileExplorer(self, file_filter='.exe')
"", file_explorer.file_signal.file_selected.connect(self.onExeSelected)
"Windows Executables (*.exe)"
) if self.parent():
fileName = fileNameAndFilter[0] parent = cast(MainWindow, self.parent())
if fileName: center_point = parent.geometry().center()
self.exeEdit.setText(fileName) file_explorer.move(center_point - file_explorer.rect().center())
if not self.edit_mode:
self.nameEdit.setText(os.path.splitext(os.path.basename(fileName))[0]) file_explorer.show()
except Exception as e:
logger.error(f"Error in browseExe: {e}")
def onExeSelected(self, file_path):
"""Обработчик выбора файла в FileExplorer"""
self.exeEdit.setText(file_path)
if not self.edit_mode:
# Автоматически заполняем имя игры, если не в режиме редактирования
game_name = os.path.splitext(os.path.basename(file_path))[0]
self.nameEdit.setText(game_name)
# Обновляем превью
self.updatePreview()
def browseCover(self): def browseCover(self):
fileNameAndFilter = QFileDialog.getOpenFileName( """Открывает файловый менеджер для выбора изображения обложки"""
self, try:
_("Select Cover Image"), file_explorer = FileExplorer(self, file_filter=('.png', '.jpg', '.jpeg', '.bmp'))
"", file_explorer.file_signal.file_selected.connect(self.onCoverSelected)
"Images (*.png *.jpg *.jpeg *.bmp)"
) if self.parent():
fileName = fileNameAndFilter[0] parent = cast(MainWindow, self.parent())
if fileName: center_point = parent.geometry().center()
self.coverEdit.setText(fileName) file_explorer.move(center_point - file_explorer.rect().center())
file_explorer.show()
except Exception as e:
logger.error(f"Error in browseCover: {e}")
def onCoverSelected(self, file_path):
"""Обработчик выбора файла обложки в FileExplorer"""
if file_path and os.path.splitext(file_path)[1].lower() in ('.png', '.jpg', '.jpeg', '.bmp'):
self.coverEdit.setText(file_path)
else:
logger.warning(f"Selected file is not a valid image: {file_path}")
def updatePreview(self): def updatePreview(self):
"""Update the cover preview image.""" """Update the cover preview image."""
@@ -238,15 +409,15 @@ class AddGameDialog(QDialog):
comment = _('Launch game "{name}" with PortProton').format(name=name) comment = _('Launch game "{name}" with PortProton').format(name=name)
desktop_entry = f"""[Desktop Entry] desktop_entry = f"""[Desktop Entry]
Name={name} Name={name}
Comment={comment} Comment={comment}
Exec={exec_str} Exec={exec_str}
Terminal=false Terminal=false
Type=Application Type=Application
Categories=Game; Categories=Game;
StartupNotify=true StartupNotify=true
Path={working_dir} Path={working_dir}
Icon={icon_path} Icon={icon_path}
""" """
return desktop_entry, desktop_path return desktop_entry, desktop_path

View File

@@ -11,6 +11,7 @@ from portprotonqt.image_utils import FullscreenDialog
from portprotonqt.custom_widgets import NavLabel from portprotonqt.custom_widgets import NavLabel
from portprotonqt.game_card import GameCard from portprotonqt.game_card import GameCard
from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config from portprotonqt.config_utils import read_fullscreen_config, read_window_geometry, save_window_geometry, read_auto_fullscreen_gamepad, read_rumble_config
from portprotonqt.dialogs import AddGameDialog
logger = get_logger(__name__) logger = get_logger(__name__)
@@ -34,7 +35,7 @@ class MainWindowProtocol(Protocol):
gamesListWidget: QWidget gamesListWidget: QWidget
currentDetailPage: QWidget | None currentDetailPage: QWidget | None
current_exec_line: str | None current_exec_line: str | None
current_add_game_dialog: QDialog | None current_add_game_dialog: AddGameDialog | None
# Mapping of actions to evdev button codes, includes Xbox and PlayStation controllers # Mapping of actions to evdev button codes, includes Xbox and PlayStation controllers
# https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c # https://github.com/torvalds/linux/blob/master/drivers/hid/hid-playstation.c
@@ -93,6 +94,21 @@ class InputManager(QObject):
self.last_trigger_time = 0.0 self.last_trigger_time = 0.0
self.trigger_cooldown = 0.2 self.trigger_cooldown = 0.2
# FileExplorer specific attributes
self.file_explorer = None
self.original_button_handler = None
self.original_dpad_handler = None
self.original_gamepad_state = None
self.nav_timer = QTimer(self)
self.nav_timer.timeout.connect(self.handle_navigation_repeat)
self.current_direction = 0
self.last_nav_time = 0
self.initial_nav_delay = 0.1 # Начальная задержка перед первым повторением (сек)
self.repeat_nav_delay = 0.05 # Интервал между повторениями (сек)
self.stick_activated = False
self.stick_value = 0 # Текущее значение стика (для плавности)
self.dead_zone = 8000 # Мертвая зона стика
# Add variables for continuous D-pad movement # Add variables for continuous D-pad movement
self.dpad_timer = QTimer(self) self.dpad_timer = QTimer(self)
self.dpad_timer.timeout.connect(self.handle_dpad_repeat) self.dpad_timer.timeout.connect(self.handle_dpad_repeat)
@@ -112,6 +128,114 @@ class InputManager(QObject):
# Initialize evdev + hotplug # Initialize evdev + hotplug
self.init_gamepad() self.init_gamepad()
def enable_file_explorer_mode(self, file_explorer):
"""Настройка обработки геймпада для FileExplorer"""
try:
self.file_explorer = file_explorer
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_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")
except Exception as e:
logger.error(f"Error connecting gamepad handlers for FileExplorer: {e}")
def disable_file_explorer_mode(self):
"""Восстановление оригинальных обработчиков главного окна программы (дефолт возвращаем)"""
try:
if self.file_explorer:
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.file_explorer = None
self.nav_timer.stop()
logger.debug("Gamepad handling successfully restored")
except Exception as e:
logger.error(f"Error restoring gamepad handlers: {e}")
def handle_file_explorer_button(self, button_code):
"""Обработка кнопок геймпада для FileExplorer"""
try:
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list'):
return
if button_code in BUTTONS['confirm']: # Кнопка A
self.file_explorer.select_item()
elif button_code in BUTTONS['back']: # Кнопка B
self.file_explorer.close()
else:
if self.original_button_handler:
self.original_button_handler(button_code)
except Exception as e:
logger.error(f"Error in FileExplorer button handler: {e}")
def handle_file_explorer_dpad(self, code, value, current_time):
"""Обработка движения D-pad и левого стика для FileExplorer"""
try:
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list') or not self.file_explorer.file_list:
return
if not self.file_explorer.file_list.count():
return
if code in (ecodes.ABS_HAT0Y, ecodes.ABS_Y):
# Для D-pad - реакция с фиксированной скоростью
if code == ecodes.ABS_HAT0Y:
if value != 0:
self.current_direction = value
self.stick_value = 1.0 # Максимальная скорость для D-pad, чтобы скачков не было
if not self.nav_timer.isActive():
self.file_explorer.move_selection(self.current_direction)
self.last_nav_time = current_time
self.nav_timer.start(int(self.initial_nav_delay * 1000))
else:
self.current_direction = 0
self.nav_timer.stop()
# Для стика - плавное управление с учетом степени отклонения
elif code == ecodes.ABS_Y:
if abs(value) < self.dead_zone:
if self.stick_activated:
self.current_direction = 0
self.nav_timer.stop()
self.stick_activated = False
return
# Рассчитываем "силу" отклонения (0.3 - 1.0)
normalized_value = (abs(value) - self.dead_zone) / (32768 - self.dead_zone)
speed_factor = 0.3 + (normalized_value * 0.7) # От 30% до 100% скорости
self.current_direction = -1 if value < 0 else 1
self.stick_value = speed_factor
self.stick_activated = True
if not self.nav_timer.isActive():
self.file_explorer.move_selection(self.current_direction)
self.last_nav_time = current_time
self.nav_timer.start(int(self.initial_nav_delay * 1000))
elif self.original_dpad_handler:
self.original_dpad_handler(code, value, current_time)
except Exception as e:
logger.error(f"Error in FileExplorer dpad handler: {e}")
def handle_navigation_repeat(self):
"""Плавное повторение движения с переменной скоростью для FileExplorer"""
try:
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list') or not self.file_explorer.file_list:
return
if self.current_direction != 0:
now = time.time()
# Динамический интервал в зависимости от stick_value
dynamic_delay = self.repeat_nav_delay / self.stick_value
if now - self.last_nav_time >= dynamic_delay:
self.file_explorer.move_selection(self.current_direction)
self.last_nav_time = now
except Exception as e:
logger.error(f"Error in navigation repeat: {e}")
@Slot(bool) @Slot(bool)
def handle_fullscreen_slot(self, enable: bool) -> None: def handle_fullscreen_slot(self, enable: bool) -> None:
try: try:
@@ -138,6 +262,7 @@ class InputManager(QObject):
self._gamepad_handling_enabled = False self._gamepad_handling_enabled = False
self.stop_rumble() self.stop_rumble()
self.dpad_timer.stop() self.dpad_timer.stop()
self.nav_timer.stop()
def enable_gamepad_handling(self) -> None: def enable_gamepad_handling(self) -> None:
"""Включает обработку событий геймпада.""" """Включает обработку событий геймпада."""
@@ -821,6 +946,7 @@ class InputManager(QObject):
try: try:
self.running = False self.running = False
self.dpad_timer.stop() self.dpad_timer.stop()
self.nav_timer.stop()
self.stop_rumble() self.stop_rumble()
if self.gamepad_thread: if self.gamepad_thread:
self.gamepad_thread.join() self.gamepad_thread.join()

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-06-24 11:17+0500\n" "POT-Creation-Date: 2025-06-26 14:55+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: de_DE\n" "Language: de_DE\n"
@@ -117,6 +117,10 @@ msgstr ""
msgid "start.sh not found at {path}" msgid "start.sh not found at {path}"
msgstr "" msgstr ""
#, python-brace-format
msgid "Launch game \"{name}\" with PortProton"
msgstr ""
#, python-brace-format #, python-brace-format
msgid "Failed to create .desktop file: {error}" msgid "Failed to create .desktop file: {error}"
msgstr "" msgstr ""
@@ -260,6 +264,12 @@ msgstr ""
msgid "Failed to remove game '{game_name}' from Steam: {error}" msgid "Failed to remove game '{game_name}' from Steam: {error}"
msgstr "" msgstr ""
msgid "Select"
msgstr ""
msgid "Cancel"
msgstr ""
msgid "Edit Game" msgid "Edit Game"
msgstr "" msgstr ""
@@ -281,22 +291,12 @@ msgstr ""
msgid "Cover Preview:" msgid "Cover Preview:"
msgstr "" msgstr ""
msgid "Select Executable"
msgstr ""
msgid "Select Cover Image"
msgstr ""
msgid "Invalid image" msgid "Invalid image"
msgstr "" msgstr ""
msgid "No cover selected" msgid "No cover selected"
msgstr "" msgstr ""
#, python-brace-format
msgid "Launch game \"{name}\" with PortProton"
msgstr ""
msgid "Loading Epic Games Store games..." msgid "Loading Epic Games Store games..."
msgstr "" msgstr ""
@@ -616,9 +616,6 @@ msgstr ""
msgid "portprotonqt-session-select file not found at /usr/bin/" msgid "portprotonqt-session-select file not found at /usr/bin/"
msgstr "" msgstr ""
msgid "Cancel"
msgstr ""
msgid "Failed to reboot the system" msgid "Failed to reboot the system"
msgstr "" msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-06-24 11:17+0500\n" "POT-Creation-Date: 2025-06-26 14:55+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language: es_ES\n" "Language: es_ES\n"
@@ -117,6 +117,10 @@ msgstr ""
msgid "start.sh not found at {path}" msgid "start.sh not found at {path}"
msgstr "" msgstr ""
#, python-brace-format
msgid "Launch game \"{name}\" with PortProton"
msgstr ""
#, python-brace-format #, python-brace-format
msgid "Failed to create .desktop file: {error}" msgid "Failed to create .desktop file: {error}"
msgstr "" msgstr ""
@@ -260,6 +264,12 @@ msgstr ""
msgid "Failed to remove game '{game_name}' from Steam: {error}" msgid "Failed to remove game '{game_name}' from Steam: {error}"
msgstr "" msgstr ""
msgid "Select"
msgstr ""
msgid "Cancel"
msgstr ""
msgid "Edit Game" msgid "Edit Game"
msgstr "" msgstr ""
@@ -281,22 +291,12 @@ msgstr ""
msgid "Cover Preview:" msgid "Cover Preview:"
msgstr "" msgstr ""
msgid "Select Executable"
msgstr ""
msgid "Select Cover Image"
msgstr ""
msgid "Invalid image" msgid "Invalid image"
msgstr "" msgstr ""
msgid "No cover selected" msgid "No cover selected"
msgstr "" msgstr ""
#, python-brace-format
msgid "Launch game \"{name}\" with PortProton"
msgstr ""
msgid "Loading Epic Games Store games..." msgid "Loading Epic Games Store games..."
msgstr "" msgstr ""
@@ -616,9 +616,6 @@ msgstr ""
msgid "portprotonqt-session-select file not found at /usr/bin/" msgid "portprotonqt-session-select file not found at /usr/bin/"
msgstr "" msgstr ""
msgid "Cancel"
msgstr ""
msgid "Failed to reboot the system" msgid "Failed to reboot the system"
msgstr "" msgstr ""

View File

@@ -9,7 +9,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PortProtonQt 0.1.1\n" "Project-Id-Version: PortProtonQt 0.1.1\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-06-24 11:17+0500\n" "POT-Creation-Date: 2025-06-26 14:55+0500\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -115,6 +115,10 @@ msgstr ""
msgid "start.sh not found at {path}" msgid "start.sh not found at {path}"
msgstr "" msgstr ""
#, python-brace-format
msgid "Launch game \"{name}\" with PortProton"
msgstr ""
#, python-brace-format #, python-brace-format
msgid "Failed to create .desktop file: {error}" msgid "Failed to create .desktop file: {error}"
msgstr "" msgstr ""
@@ -258,6 +262,12 @@ msgstr ""
msgid "Failed to remove game '{game_name}' from Steam: {error}" msgid "Failed to remove game '{game_name}' from Steam: {error}"
msgstr "" msgstr ""
msgid "Select"
msgstr ""
msgid "Cancel"
msgstr ""
msgid "Edit Game" msgid "Edit Game"
msgstr "" msgstr ""
@@ -279,22 +289,12 @@ msgstr ""
msgid "Cover Preview:" msgid "Cover Preview:"
msgstr "" msgstr ""
msgid "Select Executable"
msgstr ""
msgid "Select Cover Image"
msgstr ""
msgid "Invalid image" msgid "Invalid image"
msgstr "" msgstr ""
msgid "No cover selected" msgid "No cover selected"
msgstr "" msgstr ""
#, python-brace-format
msgid "Launch game \"{name}\" with PortProton"
msgstr ""
msgid "Loading Epic Games Store games..." msgid "Loading Epic Games Store games..."
msgstr "" msgstr ""
@@ -614,9 +614,6 @@ msgstr ""
msgid "portprotonqt-session-select file not found at /usr/bin/" msgid "portprotonqt-session-select file not found at /usr/bin/"
msgstr "" msgstr ""
msgid "Cancel"
msgstr ""
msgid "Failed to reboot the system" msgid "Failed to reboot the system"
msgstr "" msgstr ""

View File

@@ -9,8 +9,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: PROJECT VERSION\n" "Project-Id-Version: PROJECT VERSION\n"
"Report-Msgid-Bugs-To: EMAIL@ADDRESS\n" "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
"POT-Creation-Date: 2025-06-24 11:17+0500\n" "POT-Creation-Date: 2025-06-26 14:55+0500\n"
"PO-Revision-Date: 2025-06-24 11:16+0500\n" "PO-Revision-Date: 2025-06-26 14:55+0500\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language: ru_RU\n" "Language: ru_RU\n"
"Language-Team: ru_RU <LL@li.org>\n" "Language-Team: ru_RU <LL@li.org>\n"
@@ -120,6 +120,10 @@ msgstr "'{game_name}' был(а) удалён(а) из избранного"
msgid "start.sh not found at {path}" msgid "start.sh not found at {path}"
msgstr "start.sh не найден по адресу {path}" msgstr "start.sh не найден по адресу {path}"
#, python-brace-format
msgid "Launch game \"{name}\" with PortProton"
msgstr "Запустить игру \"{name}\" с помощью PortProton"
#, python-brace-format #, python-brace-format
msgid "Failed to create .desktop file: {error}" msgid "Failed to create .desktop file: {error}"
msgstr "Не удалось создать файл .desktop: {error}" msgstr "Не удалось создать файл .desktop: {error}"
@@ -267,6 +271,12 @@ msgstr "Не удалось удалить игру EGS '{game_name}' из Steam
msgid "Failed to remove game '{game_name}' from Steam: {error}" msgid "Failed to remove game '{game_name}' from Steam: {error}"
msgstr "Не удалось удалить игру '{game_name}' из Steam: {error}" msgstr "Не удалось удалить игру '{game_name}' из Steam: {error}"
msgid "Select"
msgstr "Выбрать"
msgid "Cancel"
msgstr "Отмена"
msgid "Edit Game" msgid "Edit Game"
msgstr "Редактировать игру" msgstr "Редактировать игру"
@@ -288,22 +298,12 @@ msgstr "Обложка:"
msgid "Cover Preview:" msgid "Cover Preview:"
msgstr "Предпросмотр обложки:" msgstr "Предпросмотр обложки:"
msgid "Select Executable"
msgstr "Выберите исполняемый файл"
msgid "Select Cover Image"
msgstr "Выберите обложку"
msgid "Invalid image" msgid "Invalid image"
msgstr "Недопустимое изображение" msgstr "Недопустимое изображение"
msgid "No cover selected" msgid "No cover selected"
msgstr "Обложка не выбрана" msgstr "Обложка не выбрана"
#, python-brace-format
msgid "Launch game \"{name}\" with PortProton"
msgstr "Запустить игру \"{name}\" с помощью PortProton"
msgid "Loading Epic Games Store games..." msgid "Loading Epic Games Store games..."
msgstr "Загрузка игр из Epic Games Store..." msgstr "Загрузка игр из Epic Games Store..."
@@ -625,9 +625,6 @@ msgstr "Вернуться на рабочий стол"
msgid "portprotonqt-session-select file not found at /usr/bin/" msgid "portprotonqt-session-select file not found at /usr/bin/"
msgstr "portprotonqt-session-select не найдет" msgstr "portprotonqt-session-select не найдет"
msgid "Cancel"
msgstr "Отмена"
msgid "Failed to reboot the system" msgid "Failed to reboot the system"
msgstr "Не удалось перезагрузить систему" msgstr "Не удалось перезагрузить систему"

View File

@@ -646,3 +646,57 @@ SETTINGS_COMBO_STYLE = f"""
background: rgba(0,122,255,0.25); background: rgba(0,122,255,0.25);
}} }}
""" """
class FileExplorerStyles:
WINDOW_STYLE = """
QDialog {
background-color: #2d2d2d;
color: #ffffff;
font-family: "Arial";
font-size: 14px;
}
"""
PATH_LABEL_STYLE = """
QLabel {
color: #3daee9;
font-size: 16px;
padding: 5px;
}
"""
LIST_STYLE = """
QListWidget {
font-size: 16px;
background-color: #353535;
color: #eee;
border: 1px solid #444;
border-radius: 4px;
}
QListWidget::item {
padding: 8px;
border-bottom: 1px solid #444;
}
QListWidget::item:selected {
background-color: #3daee9;
color: white;
border-radius: 2px;
}
"""
BUTTON_STYLE = """
QPushButton {
background-color: #3daee9;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
}
QPushButton:hover {
background-color: #2c9fd8;
}
QPushButton:pressed {
background-color: #1a8fc7;
}
"""

View File

@@ -866,3 +866,57 @@ def detail_page_style(stops):
border-radius: {border_radius_b}; border-radius: {border_radius_b};
}} }}
""" """
class FileExplorerStyles:
WINDOW_STYLE = """
QDialog {
background-color: #2d2d2d;
color: #ffffff;
font-family: "Arial";
font-size: 14px;
}
"""
PATH_LABEL_STYLE = """
QLabel {
color: #3daee9;
font-size: 16px;
padding: 5px;
}
"""
LIST_STYLE = """
QListWidget {
font-size: 16px;
background-color: #353535;
color: #eee;
border: 1px solid #444;
border-radius: 4px;
}
QListWidget::item {
padding: 8px;
border-bottom: 1px solid #444;
}
QListWidget::item:selected {
background-color: #3daee9;
color: white;
border-radius: 2px;
}
"""
BUTTON_STYLE = """
QPushButton {
background-color: #3daee9;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
font-size: 14px;
}
QPushButton:hover {
background-color: #2c9fd8;
}
QPushButton:pressed {
background-color: #1a8fc7;
}
"""