feat: replace QFileDialog with custom FileExplorer for Legendary import
All checks were successful
Code and build check / Check code (push) Successful in 1m25s
Code and build check / Build with uv (push) Successful in 48s

Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
2025-06-30 00:25:28 +05:00
parent 7c617eef78
commit 48048a3f50
3 changed files with 110 additions and 57 deletions

View File

@ -6,14 +6,14 @@ import subprocess
import threading
import logging
import orjson
from PySide6.QtWidgets import QMessageBox, QDialog, QMenu, QFileDialog
from PySide6.QtWidgets import QMessageBox, QDialog, QMenu
from PySide6.QtCore import QUrl, QPoint, QObject, Signal, Qt
from PySide6.QtGui import QDesktopServices
from portprotonqt.localization import _
from portprotonqt.config_utils import parse_desktop_entry, read_favorites, save_favorites
from portprotonqt.steam_api import is_game_in_steam, add_to_steam, remove_from_steam
from portprotonqt.egs_api import add_egs_to_steam, get_egs_executable, remove_egs_from_steam
from portprotonqt.dialogs import AddGameDialog, generate_thumbnail
from portprotonqt.dialogs import AddGameDialog, FileExplorer, generate_thumbnail
logger = logging.getLogger(__name__)
@ -282,35 +282,56 @@ class ContextMenuManager:
"""
if not self._check_portproton():
return
folder_path = QFileDialog.getExistingDirectory(
self.parent, _("Select Game Installation Folder"), os.path.expanduser("~")
)
if not folder_path:
self._show_status_message(_("No folder selected"))
return
if not os.path.exists(self.legendary_path):
self.signals.show_warning_dialog.emit(
_("Error"),
_("Legendary executable not found at {path}").format(path=self.legendary_path)
)
return
def run_import():
cmd = [self.legendary_path, "import", app_name, folder_path]
try:
subprocess.run(cmd, capture_output=True, text=True, check=True)
self.signals.show_info_dialog.emit(
_("Success"),
_("Imported '{game_name}' to Legendary").format(game_name=game_name)
)
except subprocess.CalledProcessError as e:
self.signals.show_warning_dialog.emit(
_("Error"),
_("Failed to import '{game_name}' to Legendary: {error}").format(
game_name=game_name, error=e.stderr
# Используем FileExplorer с directory_only=True
file_explorer = FileExplorer(
parent=self.parent,
theme=self.theme,
initial_path=os.path.expanduser("~"),
directory_only=True
)
def on_folder_selected(folder_path):
if not folder_path:
self._show_status_message(_("No folder selected"))
return
def run_import():
cmd = [self.legendary_path, "import", app_name, folder_path]
try:
subprocess.run(cmd, capture_output=True, text=True, check=True)
self.signals.show_info_dialog.emit(
_("Success"),
_("Imported '{game_name}' to Legendary").format(game_name=game_name)
)
)
self._show_status_message(_("Importing '{game_name}' to Legendary...").format(game_name=game_name))
threading.Thread(target=run_import, daemon=True).start()
except subprocess.CalledProcessError as e:
self.signals.show_warning_dialog.emit(
_("Error"),
_("Failed to import '{game_name}' to Legendary: {error}").format(
game_name=game_name, error=e.stderr
)
)
self._show_status_message(_("Importing '{game_name}' to Legendary...").format(game_name=game_name))
threading.Thread(target=run_import, daemon=True).start()
# Подключаем сигнал выбора файла/папки
file_explorer.file_signal.file_selected.connect(on_folder_selected)
# Центрируем FileExplorer относительно родительского виджета
parent_widget = self.parent
if parent_widget:
parent_geometry = parent_widget.geometry()
center_point = parent_geometry.center()
file_explorer_geometry = file_explorer.geometry()
file_explorer_geometry.moveCenter(center_point)
file_explorer.setGeometry(file_explorer_geometry)
file_explorer.show()
def toggle_favorite(self, game_card, add: bool):
"""

View File

@ -88,12 +88,13 @@ class FileSelectedSignal(QObject):
file_selected = Signal(str) # Сигнал с путем к выбранному файлу
class FileExplorer(QDialog):
def __init__(self, parent=None, theme=None, file_filter=None, initial_path=None):
def __init__(self, parent=None, theme=None, file_filter=None, initial_path=None, directory_only=False):
super().__init__(parent)
self.theme = theme if theme else default_styles
self.theme_manager = ThemeManager()
self.file_signal = FileSelectedSignal()
self.file_filter = file_filter # Store the file filter
self.directory_only = directory_only # Store the directory_only flag
self.mime_db = QMimeDatabase() # Initialize QMimeDatabase for mimetype detection
self.path_history = {} # Dictionary to store last selected item per directory
self.initial_path = initial_path # Store initial path if provided
@ -216,13 +217,21 @@ class FileExplorer(QDialog):
full_path = os.path.join(self.current_path, selected)
if os.path.isdir(full_path):
# Если выбрана директория, нормализуем путь
self.current_path = os.path.normpath(full_path)
self.update_file_list()
if self.directory_only:
# Подтверждаем выбор директории
self.file_signal.file_selected.emit(os.path.normpath(full_path))
self.accept()
else:
# Открываем директорию
self.current_path = os.path.normpath(full_path)
self.update_file_list()
else:
# Для файла отправляем нормализованный путь
self.file_signal.file_selected.emit(os.path.normpath(full_path))
self.accept()
if not self.directory_only:
# Для файла отправляем нормализованный путь
self.file_signal.file_selected.emit(os.path.normpath(full_path))
self.accept()
else:
logger.debug("Selected item is not a directory, ignoring: %s", full_path)
def previous_dir(self):
"""Возврат к родительской директории"""
@ -288,14 +297,7 @@ class FileExplorer(QDialog):
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:
if isinstance(self.file_filter, str):
files = [f for f in files if f.lower().endswith(self.file_filter)]
elif isinstance(self.file_filter, tuple):
files = [f for f in files if any(f.lower().endswith(ext) for ext in self.file_filter)]
# Добавляем директории
for d in sorted(dirs):
item = QListWidgetItem(f"{d}/")
folder_icon = self.theme_manager.get_icon("folder")
@ -307,25 +309,34 @@ class FileExplorer(QDialog):
item.setIcon(folder_icon)
self.file_list.addItem(item)
for f in sorted(files):
item = QListWidgetItem(f)
file_path = os.path.join(self.current_path, f)
mime_type = self.mime_db.mimeTypeForFile(file_path).name()
# Добавляем файлы только если directory_only=False
if not self.directory_only:
files = [f for f in items if os.path.isfile(os.path.join(self.current_path, f))]
if self.file_filter:
if isinstance(self.file_filter, str):
files = [f for f in files if f.lower().endswith(self.file_filter)]
elif isinstance(self.file_filter, tuple):
files = [f for f in files if any(f.lower().endswith(ext) for ext in self.file_filter)]
if mime_type.startswith("image/"):
pixmap = QPixmap(file_path)
if not pixmap.isNull():
item.setIcon(QIcon(pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio)))
elif file_path.lower().endswith(".exe"):
tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
tmp.close()
if generate_thumbnail(file_path, tmp.name, size=64):
pixmap = QPixmap(tmp.name)
for f in sorted(files):
item = QListWidgetItem(f)
file_path = os.path.join(self.current_path, f)
mime_type = self.mime_db.mimeTypeForFile(file_path).name()
if mime_type.startswith("image/"):
pixmap = QPixmap(file_path)
if not pixmap.isNull():
item.setIcon(QIcon(pixmap))
os.unlink(tmp.name)
item.setIcon(QIcon(pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio)))
elif file_path.lower().endswith(".exe"):
tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
tmp.close()
if generate_thumbnail(file_path, tmp.name, size=64):
pixmap = QPixmap(tmp.name)
if not pixmap.isNull():
item.setIcon(QIcon(pixmap))
os.unlink(tmp.name)
self.file_list.addItem(item)
self.file_list.addItem(item)
self.path_label.setText(_("Path: ") + self.current_path)

View File

@ -1,5 +1,6 @@
import time
import threading
import os
from typing import Protocol, cast
from evdev import InputDevice, InputEvent, ecodes, list_devices, ff
from pyudev import Context, Monitor, MonitorObserver, Device
@ -162,8 +163,28 @@ class InputManager(QObject):
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list'):
return
if button_code in BUTTONS['confirm']:
self.file_explorer.select_item()
if button_code in BUTTONS['add_game']:
if self.file_explorer.file_list.count() == 0:
return
selected = self.file_explorer.file_list.currentItem().text()
full_path = os.path.join(self.file_explorer.current_path, selected)
if os.path.isdir(full_path):
# Подтверждаем выбор директории
self.file_explorer.file_signal.file_selected.emit(os.path.normpath(full_path))
self.file_explorer.accept()
else:
logger.debug("Selected item is not a directory: %s", full_path)
elif button_code in BUTTONS['confirm']:
if self.file_explorer.file_list.count() == 0:
return
selected = self.file_explorer.file_list.currentItem().text()
full_path = os.path.join(self.file_explorer.current_path, selected)
if os.path.isdir(full_path):
# Открываем директорию
self.file_explorer.current_path = os.path.normpath(full_path)
self.file_explorer.update_file_list()
else:
logger.debug("Selected item is not a directory, cannot open: %s", full_path)
elif button_code in BUTTONS['back']:
self.file_explorer.close()
elif button_code in BUTTONS['prev_dir']: