feat: replace QFileDialog with custom FileExplorer for Legendary import
Signed-off-by: Boris Yumankulov <boria138@altlinux.org>
This commit is contained in:
@ -6,14 +6,14 @@ import subprocess
|
|||||||
import threading
|
import threading
|
||||||
import logging
|
import logging
|
||||||
import orjson
|
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.QtCore import QUrl, QPoint, QObject, Signal, Qt
|
||||||
from PySide6.QtGui import QDesktopServices
|
from PySide6.QtGui import QDesktopServices
|
||||||
from portprotonqt.localization import _
|
from portprotonqt.localization import _
|
||||||
from portprotonqt.config_utils import parse_desktop_entry, read_favorites, save_favorites
|
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.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.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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -282,35 +282,56 @@ class ContextMenuManager:
|
|||||||
"""
|
"""
|
||||||
if not self._check_portproton():
|
if not self._check_portproton():
|
||||||
return
|
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):
|
if not os.path.exists(self.legendary_path):
|
||||||
self.signals.show_warning_dialog.emit(
|
self.signals.show_warning_dialog.emit(
|
||||||
_("Error"),
|
_("Error"),
|
||||||
_("Legendary executable not found at {path}").format(path=self.legendary_path)
|
_("Legendary executable not found at {path}").format(path=self.legendary_path)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
def run_import():
|
|
||||||
cmd = [self.legendary_path, "import", app_name, folder_path]
|
# Используем FileExplorer с directory_only=True
|
||||||
try:
|
file_explorer = FileExplorer(
|
||||||
subprocess.run(cmd, capture_output=True, text=True, check=True)
|
parent=self.parent,
|
||||||
self.signals.show_info_dialog.emit(
|
theme=self.theme,
|
||||||
_("Success"),
|
initial_path=os.path.expanduser("~"),
|
||||||
_("Imported '{game_name}' to Legendary").format(game_name=game_name)
|
directory_only=True
|
||||||
)
|
)
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
self.signals.show_warning_dialog.emit(
|
def on_folder_selected(folder_path):
|
||||||
_("Error"),
|
if not folder_path:
|
||||||
_("Failed to import '{game_name}' to Legendary: {error}").format(
|
self._show_status_message(_("No folder selected"))
|
||||||
game_name=game_name, error=e.stderr
|
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._show_status_message(_("Importing '{game_name}' to Legendary...").format(game_name=game_name))
|
self.signals.show_warning_dialog.emit(
|
||||||
threading.Thread(target=run_import, daemon=True).start()
|
_("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):
|
def toggle_favorite(self, game_card, add: bool):
|
||||||
"""
|
"""
|
||||||
|
@ -88,12 +88,13 @@ class FileSelectedSignal(QObject):
|
|||||||
file_selected = Signal(str) # Сигнал с путем к выбранному файлу
|
file_selected = Signal(str) # Сигнал с путем к выбранному файлу
|
||||||
|
|
||||||
class FileExplorer(QDialog):
|
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)
|
super().__init__(parent)
|
||||||
self.theme = theme if theme else default_styles
|
self.theme = theme if theme else default_styles
|
||||||
self.theme_manager = ThemeManager()
|
self.theme_manager = ThemeManager()
|
||||||
self.file_signal = FileSelectedSignal()
|
self.file_signal = FileSelectedSignal()
|
||||||
self.file_filter = file_filter # Store the file filter
|
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.mime_db = QMimeDatabase() # Initialize QMimeDatabase for mimetype detection
|
||||||
self.path_history = {} # Dictionary to store last selected item per directory
|
self.path_history = {} # Dictionary to store last selected item per directory
|
||||||
self.initial_path = initial_path # Store initial path if provided
|
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)
|
full_path = os.path.join(self.current_path, selected)
|
||||||
|
|
||||||
if os.path.isdir(full_path):
|
if os.path.isdir(full_path):
|
||||||
# Если выбрана директория, нормализуем путь
|
if self.directory_only:
|
||||||
self.current_path = os.path.normpath(full_path)
|
# Подтверждаем выбор директории
|
||||||
self.update_file_list()
|
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:
|
else:
|
||||||
# Для файла отправляем нормализованный путь
|
if not self.directory_only:
|
||||||
self.file_signal.file_selected.emit(os.path.normpath(full_path))
|
# Для файла отправляем нормализованный путь
|
||||||
self.accept()
|
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):
|
def previous_dir(self):
|
||||||
"""Возврат к родительской директории"""
|
"""Возврат к родительской директории"""
|
||||||
@ -288,14 +297,7 @@ class FileExplorer(QDialog):
|
|||||||
items = os.listdir(self.current_path)
|
items = os.listdir(self.current_path)
|
||||||
dirs = [d for d in items if os.path.isdir(os.path.join(self.current_path, d))]
|
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):
|
for d in sorted(dirs):
|
||||||
item = QListWidgetItem(f"{d}/")
|
item = QListWidgetItem(f"{d}/")
|
||||||
folder_icon = self.theme_manager.get_icon("folder")
|
folder_icon = self.theme_manager.get_icon("folder")
|
||||||
@ -307,25 +309,34 @@ class FileExplorer(QDialog):
|
|||||||
item.setIcon(folder_icon)
|
item.setIcon(folder_icon)
|
||||||
self.file_list.addItem(item)
|
self.file_list.addItem(item)
|
||||||
|
|
||||||
for f in sorted(files):
|
# Добавляем файлы только если directory_only=False
|
||||||
item = QListWidgetItem(f)
|
if not self.directory_only:
|
||||||
file_path = os.path.join(self.current_path, f)
|
files = [f for f in items if os.path.isfile(os.path.join(self.current_path, f))]
|
||||||
mime_type = self.mime_db.mimeTypeForFile(file_path).name()
|
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/"):
|
for f in sorted(files):
|
||||||
pixmap = QPixmap(file_path)
|
item = QListWidgetItem(f)
|
||||||
if not pixmap.isNull():
|
file_path = os.path.join(self.current_path, f)
|
||||||
item.setIcon(QIcon(pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio)))
|
mime_type = self.mime_db.mimeTypeForFile(file_path).name()
|
||||||
elif file_path.lower().endswith(".exe"):
|
|
||||||
tmp = tempfile.NamedTemporaryFile(suffix='.png', delete=False)
|
if mime_type.startswith("image/"):
|
||||||
tmp.close()
|
pixmap = QPixmap(file_path)
|
||||||
if generate_thumbnail(file_path, tmp.name, size=64):
|
|
||||||
pixmap = QPixmap(tmp.name)
|
|
||||||
if not pixmap.isNull():
|
if not pixmap.isNull():
|
||||||
item.setIcon(QIcon(pixmap))
|
item.setIcon(QIcon(pixmap.scaled(64, 64, Qt.AspectRatioMode.KeepAspectRatio)))
|
||||||
os.unlink(tmp.name)
|
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)
|
self.path_label.setText(_("Path: ") + self.current_path)
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import time
|
import time
|
||||||
import threading
|
import threading
|
||||||
|
import os
|
||||||
from typing import Protocol, cast
|
from typing import Protocol, cast
|
||||||
from evdev import InputDevice, InputEvent, ecodes, list_devices, ff
|
from evdev import InputDevice, InputEvent, ecodes, list_devices, ff
|
||||||
from pyudev import Context, Monitor, MonitorObserver, Device
|
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'):
|
if not self.file_explorer or not hasattr(self.file_explorer, 'file_list'):
|
||||||
return
|
return
|
||||||
|
|
||||||
if button_code in BUTTONS['confirm']:
|
if button_code in BUTTONS['add_game']:
|
||||||
self.file_explorer.select_item()
|
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']:
|
elif button_code in BUTTONS['back']:
|
||||||
self.file_explorer.close()
|
self.file_explorer.close()
|
||||||
elif button_code in BUTTONS['prev_dir']:
|
elif button_code in BUTTONS['prev_dir']:
|
||||||
|
Reference in New Issue
Block a user