diff --git a/portprotonqt/context_menu_manager.py b/portprotonqt/context_menu_manager.py index 680c2e4..4d0db90 100644 --- a/portprotonqt/context_menu_manager.py +++ b/portprotonqt/context_menu_manager.py @@ -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): """ diff --git a/portprotonqt/dialogs.py b/portprotonqt/dialogs.py index f461f57..71da51e 100644 --- a/portprotonqt/dialogs.py +++ b/portprotonqt/dialogs.py @@ -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) diff --git a/portprotonqt/input_manager.py b/portprotonqt/input_manager.py index 7892aa1..2624b5b 100644 --- a/portprotonqt/input_manager.py +++ b/portprotonqt/input_manager.py @@ -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']: