diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index dc20b8e1..eb6cb1e3 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -20,7 +20,7 @@ AppDir: id: io.github.dummerle.rare name: Rare icon: Rare - version: 1.10.7 + version: 1.10.9 exec: usr/bin/python3 exec_args: $APPDIR/usr/src/rare/main.py $@ apt: diff --git a/pyproject.toml b/pyproject.toml index 5d51e960..779fe56c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -12,7 +12,7 @@ force-exclude = ''' [tool.poetry] name = "rare" -version = "1.10.7" +version = "1.10.9" description = "A GUI for Legendary" authors = ["Dummerle"] license = "GPL3" diff --git a/rare/__init__.py b/rare/__init__.py index a1e30886..19e12745 100644 --- a/rare/__init__.py +++ b/rare/__init__.py @@ -1,4 +1,4 @@ -__version__ = "1.10.7" +__version__ = "1.10.9" __codename__ = "Garlic Crab" # For PyCharm profiler diff --git a/rare/components/__init__.py b/rare/components/__init__.py index 8d0e4fd4..684a1133 100644 --- a/rare/components/__init__.py +++ b/rare/components/__init__.py @@ -45,8 +45,6 @@ class Rare(RareApp): self.signals = RareCore.instance().signals() self.core = RareCore.instance().core() - config_helper.init_config_handler(self.core) - lang = self.settings.value("language", self.core.language_code, type=str) self.load_translator(lang) diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py index 52e0aab2..a3042281 100644 --- a/rare/components/dialogs/install_dialog.py +++ b/rare/components/dialogs/install_dialog.py @@ -73,13 +73,15 @@ class InstallDialog(QDialog): self.ui.install_dialog_label.setText(f'

{header} "{self.rgame.app_title}"

') self.setWindowTitle(f'{header} "{self.rgame.app_title}" - {QCoreApplication.instance().applicationName()}') - if not self.options.base_path: - self.options.base_path = self.core.lgd.config.get( - "Legendary", "install_dir", fallback=os.path.expanduser("~/legendary") - ) + if options.base_path: + base_path = options.base_path + elif rgame.is_installed: + base_path = rgame.install_path + else: + base_path = self.core.get_default_install_dir(rgame.default_platform) self.install_dir_edit = PathEdit( - path=self.options.base_path, + path=base_path, file_mode=QFileDialog.DirectoryOnly, edit_func=self.option_changed, save_func=self.save_install_edit, @@ -90,36 +92,24 @@ class InstallDialog(QDialog): QFormLayout.FieldRole, self.install_dir_edit ) - if self.options.update: - self.ui.install_dir_label.setEnabled(False) - self.install_dir_edit.setEnabled(False) - self.ui.shortcut_label.setEnabled(False) - self.ui.shortcut_check.setEnabled(False) - else: - self.ui.shortcut_check.setChecked(QSettings().value("create_shortcut", True, bool)) + self.install_dir_edit.setDisabled(rgame.is_installed) + self.ui.install_dir_label.setDisabled(rgame.is_installed) + self.ui.shortcut_label.setDisabled(rgame.is_installed) + self.ui.shortcut_check.setDisabled(rgame.is_installed) + self.ui.shortcut_check.setChecked(not rgame.is_installed and QSettings().value("create_shortcut", True, bool)) self.error_box() - platforms = self.rgame.platforms - self.ui.platform_combo.addItems(reversed(platforms)) - self.ui.platform_combo.currentIndexChanged.connect(lambda: self.option_changed(None)) - self.ui.platform_combo.currentIndexChanged.connect(lambda: self.error_box()) - self.ui.platform_combo.currentIndexChanged.connect( - lambda i: self.error_box( - self.tr("Warning"), - self.tr("You will not be able to run the game if you select {} as platform").format( - self.ui.platform_combo.itemText(i) - ), - ) - if (self.ui.platform_combo.currentText() == "Mac" and pf.system() != "Darwin") - else None - ) - self.ui.platform_combo.setCurrentIndex( - self.ui.platform_combo.findText( - "Mac" if (pf.system() == "Darwin" and "Mac" in platforms) else "Windows" - ) - ) - self.ui.platform_combo.currentTextChanged.connect(self.setup_sdl_list) + self.ui.platform_combo.addItems(reversed(rgame.platforms)) + combo_text = rgame.igame.platform if rgame.is_installed else rgame.default_platform + self.ui.platform_combo.setCurrentIndex(self.ui.platform_combo.findText(combo_text)) + self.ui.platform_combo.currentIndexChanged.connect(lambda i: self.option_changed(None)) + self.ui.platform_combo.currentIndexChanged.connect(self.check_incompatible_platform) + self.ui.platform_combo.currentIndexChanged.connect(self.reset_install_dir) + self.ui.platform_combo.currentIndexChanged.connect(self.reset_sdl_list) + + self.ui.platform_label.setDisabled(rgame.is_installed) + self.ui.platform_combo.setDisabled(rgame.is_installed) self.advanced.ui.max_workers_spin.setValue(self.core.lgd.config.getint("Legendary", "max_workers", fallback=0)) self.advanced.ui.max_workers_spin.valueChanged.connect(self.option_changed) @@ -139,7 +129,10 @@ class InstallDialog(QDialog): self.selectable_checks: List[TagCheckBox] = [] self.config_tags: Optional[List[str]] = None - self.setup_sdl_list(self.ui.platform_combo.currentText()) + + self.reset_install_dir(self.ui.platform_combo.currentIndex()) + self.reset_sdl_list(self.ui.platform_combo.currentIndex()) + self.check_incompatible_platform(self.ui.platform_combo.currentIndex()) self.ui.install_button.setEnabled(False) @@ -155,9 +148,10 @@ class InstallDialog(QDialog): self.selectable.setEnabled(False) if pf.system() == "Darwin": + self.ui.shortcut_label.setDisabled(True) self.ui.shortcut_check.setDisabled(True) self.ui.shortcut_check.setChecked(False) - self.ui.shortcut_check.setToolTip(self.tr("Creating a shortcut is not supported on MacOS")) + self.ui.shortcut_check.setToolTip(self.tr("Creating a shortcut is not supported on macOS")) self.advanced.ui.install_prereqs_label.setEnabled(False) self.advanced.ui.install_prereqs_check.setEnabled(False) @@ -190,8 +184,16 @@ class InstallDialog(QDialog): self.__on_verify() self.show() - @pyqtSlot(str) - def setup_sdl_list(self, platform="Windows"): + @pyqtSlot(int) + def reset_install_dir(self, index: int): + if not self.rgame.is_installed: + platform = self.ui.platform_combo.itemText(index) + default_dir = self.core.get_default_install_dir(platform) + self.install_dir_edit.setText(default_dir) + + @pyqtSlot(int) + def reset_sdl_list(self, index: int): + platform = self.ui.platform_combo.itemText(index) for cb in self.selectable_checks: cb.disconnect() cb.deleteLater() @@ -223,8 +225,19 @@ class InstallDialog(QDialog): else: self.selectable.setDisabled(True) + @pyqtSlot(int) + def check_incompatible_platform(self, index: int): + platform = self.ui.platform_combo.itemText(index) + if platform == "Mac" and pf.system() != "Darwin": + self.error_box( + self.tr("Warning"), + self.tr("You will not be able to run the game if you select {} as platform").format(platform) + ) + else: + self.error_box() + def get_options(self): - self.options.base_path = self.install_dir_edit.text() if not self.options.update else None + self.options.base_path = "" if self.rgame.is_installed else self.install_dir_edit.text() self.options.max_workers = self.advanced.ui.max_workers_spin.value() self.options.shared_memory = self.advanced.ui.max_memory_spin.value() self.options.order_opt = self.advanced.ui.dl_optimizations_check.isChecked() diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py index 7017ebc3..dfff5865 100644 --- a/rare/components/tabs/downloads/__init__.py +++ b/rare/components/tabs/downloads/__init__.py @@ -346,5 +346,5 @@ class DownloadsTab(QWidget): @pyqtSlot(RareGame, bool, str) def __on_uninstall_worker_result(self, rgame: RareGame, success: bool, message: str): if not success: - QMessageBox.warning(None, self.tr("Uninstall - {}").format(rgame.title), message, QMessageBox.Close) + QMessageBox.warning(None, self.tr("Uninstall - {}").format(rgame.app_title), message, QMessageBox.Close) rgame.state = RareGame.State.IDLE diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index c206b4e3..2cb3876d 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -1,6 +1,7 @@ from logging import getLogger from PyQt5.QtCore import QSettings, Qt, pyqtSlot +from PyQt5.QtGui import QShowEvent from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame from rare.models.game import RareGame @@ -33,8 +34,6 @@ class GamesTab(QStackedWidget): self.image_manager = ImageManagerSingleton() self.settings = QSettings() - self.active_filter: int = 0 - self.games_page = QWidget(parent=self) games_page_layout = QVBoxLayout(self.games_page) self.addWidget(self.games_page) @@ -69,6 +68,7 @@ class GamesTab(QStackedWidget): self.icon_view = QWidget(self.icon_view_scroll) icon_view_layout = LibraryLayout(self.icon_view) + icon_view_layout.setSpacing(9) icon_view_layout.setContentsMargins(0, 13, 0, 13) icon_view_layout.setAlignment(Qt.AlignTop) @@ -97,16 +97,20 @@ class GamesTab(QStackedWidget): self.head_bar.refresh_list.clicked.connect(self.library_controller.update_list) self.head_bar.view.toggled.connect(self.toggle_view) - f = self.settings.value("filter", 0, int) - if f >= len(self.head_bar.available_filters): - f = 0 - self.active_filter = self.head_bar.available_filters[f] + self.active_filter: str = self.head_bar.filter.currentData(Qt.UserRole) # signals self.signals.game.installed.connect(self.update_count_games_label) self.signals.game.uninstalled.connect(self.update_count_games_label) + self.init = True + + def showEvent(self, a0: QShowEvent): + if a0.spontaneous() or not self.init: + return super().showEvent(a0) self.setup_game_list() + self.init = False + return super().showEvent(a0) @pyqtSlot() def scroll_to_top(self): @@ -149,7 +153,7 @@ class GamesTab(QStackedWidget): for rgame in self.rcore.games: icon_widget, list_widget = self.add_library_widget(rgame) if not icon_widget or not list_widget: - logger.warning(f"Excluding {rgame.app_name} from the game list") + logger.warning("Excluding %s from the game list", rgame.app_title) continue self.icon_view.layout().addWidget(icon_widget) self.list_view.layout().addWidget(list_widget) @@ -160,8 +164,7 @@ class GamesTab(QStackedWidget): try: icon_widget, list_widget = self.library_controller.add_game(rgame) except Exception as e: - raise e - logger.error(f"{rgame.app_name} is broken. Don't add it to game list: {e}") + logger.error("Could not add widget for %s to library: %s", rgame.app_name, e) return None, None icon_widget.show_info.connect(self.show_game_info) list_widget.show_info.connect(self.show_game_info) diff --git a/rare/components/tabs/games/game_info/cloud_saves.py b/rare/components/tabs/games/game_info/cloud_saves.py index a39d492b..d9cd9f23 100644 --- a/rare/components/tabs/games/game_info/cloud_saves.py +++ b/rare/components/tabs/games/game_info/cloud_saves.py @@ -144,10 +144,10 @@ class CloudSaves(QWidget, SideTabContents): self.cloud_save_path_edit.setText("") QMessageBox.warning( self, - self.tr("Error - {}").format(self.rgame.title), + self.tr("Error - {}").format(self.rgame.app_title), self.tr( "Error while calculating path for {}. Insufficient permissions to create {}" - ).format(self.rgame.title, path), + ).format(self.rgame.app_title, path), ) return if not path: @@ -217,7 +217,7 @@ class CloudSaves(QWidget, SideTabContents): self.rgame = rgame - self.set_title.emit(rgame.title) + self.set_title.emit(rgame.app_title) rgame.signals.widget.update.connect(self.__update_widget) self.__update_widget() diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index a6735235..f0507021 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -121,7 +121,7 @@ class GameInfo(QWidget, SideTabContents): if not os.path.exists(repair_file): QMessageBox.warning( self, - self.tr("Error - {}").format(self.rgame.title), + self.tr("Error - {}").format(self.rgame.app_title), self.tr( "Repair file does not exist or game does not need a repair. Please verify game first" ), @@ -135,11 +135,11 @@ class GameInfo(QWidget, SideTabContents): if rgame.has_update: ans = QMessageBox.question( self, - self.tr("Repair and update? - {}").format(self.rgame.title), + self.tr("Repair and update? - {}").format(self.rgame.app_title), self.tr( "There is an update for {} from {} to {}. " "Do you want to update the game while repairing it?" - ).format(rgame.title, rgame.version, rgame.remote_version), + ).format(rgame.app_title, rgame.version, rgame.remote_version), ) == QMessageBox.Yes rgame.repair(repair_and_update=ans) @@ -147,7 +147,7 @@ class GameInfo(QWidget, SideTabContents): def __on_worker_error(self, rgame: RareGame, message: str): QMessageBox.warning( self, - self.tr("Error - {}").format(rgame.title), + self.tr("Error - {}").format(rgame.app_title), message ) @@ -155,11 +155,11 @@ class GameInfo(QWidget, SideTabContents): def __on_verify(self): """ This method is to be called from the button only """ if not os.path.exists(self.rgame.igame.install_path): - logger.error(f"Installation path {self.rgame.igame.install_path} for {self.rgame.title} does not exist") + logger.error(f"Installation path {self.rgame.igame.install_path} for {self.rgame.app_title} does not exist") QMessageBox.warning( self, - self.tr("Error - {}").format(self.rgame.title), - self.tr("Installation path for {} does not exist. Cannot continue.").format(self.rgame.title), + self.tr("Error - {}").format(self.rgame.app_title), + self.tr("Installation path for {} does not exist. Cannot continue.").format(self.rgame.app_title), ) return self.verify_game(self.rgame) @@ -184,18 +184,18 @@ class GameInfo(QWidget, SideTabContents): if success: QMessageBox.information( self, - self.tr("Summary - {}").format(rgame.title), + self.tr("Summary - {}").format(rgame.app_title), self.tr("{} has been verified successfully. " - "No missing or corrupt files found").format(rgame.title), + "No missing or corrupt files found").format(rgame.app_title), ) else: ans = QMessageBox.question( self, - self.tr("Summary - {}").format(rgame.title), + self.tr("Summary - {}").format(rgame.app_title), self.tr( "{} failed verification, {} file(s) corrupted, {} file(s) are missing. " "Do you want to repair them?" - ).format(rgame.title, failed, missing), + ).format(rgame.app_title, failed, missing), QMessageBox.Yes | QMessageBox.No, QMessageBox.Yes, ) @@ -216,7 +216,7 @@ class GameInfo(QWidget, SideTabContents): if os.path.basename(self.rgame.install_path) in os.path.basename(item): ans = QMessageBox.question( self, - self.tr("Move game? - {}").format(self.rgame.title), + self.tr("Move game? - {}").format(self.rgame.app_title), self.tr( "Destination {} already exists. " "Are you sure you want to overwrite it?" @@ -255,8 +255,8 @@ class GameInfo(QWidget, SideTabContents): def __on_move_result(self, rgame: RareGame, dst_path: str): QMessageBox.information( self, - self.tr("Summary - {}").format(rgame.title), - self.tr("{} successfully moved to {}.").format(rgame.title, dst_path), + self.tr("Summary - {}").format(rgame.app_title), + self.tr("{} successfully moved to {}.").format(rgame.app_title, dst_path), ) @pyqtSlot() @@ -284,7 +284,9 @@ class GameInfo(QWidget, SideTabContents): ) self.ui.platform.setText( - self.rgame.igame.platform if self.rgame.is_installed and not self.rgame.is_non_asset else "Windows" + self.rgame.igame.platform + if self.rgame.is_installed and not self.rgame.is_non_asset + else self.rgame.default_platform ) self.ui.lbl_grade.setDisabled( diff --git a/rare/components/tabs/games/game_widgets/__init__.py b/rare/components/tabs/games/game_widgets/__init__.py index b1df4b3d..a5d5b58b 100644 --- a/rare/components/tabs/games/game_widgets/__init__.py +++ b/rare/components/tabs/games/game_widgets/__init__.py @@ -38,15 +38,15 @@ class LibraryWidgetController(QObject): elif "hidden" in widget.rgame.metadata.tags: visible = False elif filter_name == "installed": - visible = widget.rgame.is_installed + visible = widget.rgame.is_installed and not widget.rgame.is_unreal elif filter_name == "offline": - visible = widget.rgame.can_run_offline + visible = widget.rgame.can_run_offline and not widget.rgame.is_unreal elif filter_name == "32bit": - visible = widget.rgame.is_win32 + visible = widget.rgame.is_win32 and not widget.rgame.is_unreal elif filter_name == "mac": - visible = widget.rgame.is_mac + visible = widget.rgame.is_mac and not widget.rgame.is_unreal elif filter_name == "installable": - visible = not widget.rgame.is_non_asset + visible = not widget.rgame.is_non_asset and not widget.rgame.is_unreal elif filter_name == "include_ue": visible = True elif filter_name == "all": diff --git a/rare/components/tabs/games/game_widgets/icon_game_widget.py b/rare/components/tabs/games/game_widgets/icon_game_widget.py index bf75c4e2..0bdaf07e 100644 --- a/rare/components/tabs/games/game_widgets/icon_game_widget.py +++ b/rare/components/tabs/games/game_widgets/icon_game_widget.py @@ -15,7 +15,7 @@ class IconGameWidget(GameWidget): def __init__(self, rgame: RareGame, parent=None): super().__init__(rgame, parent) self.setObjectName(f"{rgame.app_name}") - self.setFixedSize(ImageSize.Display) + self.setFixedSize(ImageSize.Library) self.ui = IconWidget() self.ui.setupUi(self) diff --git a/rare/components/tabs/games/head_bar.py b/rare/components/tabs/games/head_bar.py index 1178108c..2311d97c 100644 --- a/rare/components/tabs/games/head_bar.py +++ b/rare/components/tabs/games/head_bar.py @@ -1,4 +1,6 @@ -from PyQt5.QtCore import QSettings, pyqtSignal, pyqtSlot +import platform as pf + +from PyQt5.QtCore import QSettings, pyqtSignal, pyqtSlot, Qt from PyQt5.QtWidgets import ( QLabel, QPushButton, @@ -22,45 +24,28 @@ class GameListHeadBar(QWidget): def __init__(self, parent=None): super(GameListHeadBar, self).__init__(parent=parent) self.rcore = RareCore.instance() - self.settings = QSettings() + self.settings = QSettings(self) - self.filter = QComboBox() - self.filter.addItems( - [ - self.tr("All games"), - self.tr("Installed only"), - self.tr("Offline Games"), - # self.tr("Hidden") - ] - ) - - self.available_filters = [ - "all", - "installed", - "offline", - # "hidden" - ] + self.filter = QComboBox(self) + self.filter.addItem(self.tr("All games"), "all") + self.filter.addItem(self.tr("Installed"), "installed") + self.filter.addItem(self.tr("Offline"), "offline") + # self.filter.addItem(self.tr("Hidden"), "hidden") if self.rcore.bit32_games: - self.filter.addItem(self.tr("32 Bit Games")) - self.available_filters.append("32bit") - + self.filter.addItem(self.tr("32bit games"), "32bit") if self.rcore.mac_games: - self.filter.addItem(self.tr("Mac games")) - self.available_filters.append("mac") - + self.filter.addItem(self.tr("macOS games"), "mac") if self.rcore.origin_games: - self.filter.addItem(self.tr("Exclude Origin")) - self.available_filters.append("installable") - - self.filter.addItem(self.tr("Include Unreal Engine")) - self.available_filters.append("include_ue") + self.filter.addItem(self.tr("Exclude Origin"), "installable") + self.filter.addItem(self.tr("Include Unreal"), "include_ue") + filter_default = "mac" if pf.system() == "Darwin" else "all" + filter_index = i if (i := self.filter.findData(filter_default, Qt.UserRole)) >= 0 else 0 try: - self.filter.setCurrentIndex(self.settings.value("filter", 0, int)) + self.filter.setCurrentIndex(self.settings.value("library_filter", filter_index, int)) except TypeError: - self.settings.setValue("filter", 0) - self.filter.setCurrentIndex(0) - + self.settings.setValue("library_filter", filter_index) + self.filter.setCurrentIndex(filter_index) self.filter.currentIndexChanged.connect(self.filter_changed) integrations_menu = QMenu(self) @@ -139,6 +124,6 @@ class GameListHeadBar(QWidget): self.rcore.fetch() @pyqtSlot(int) - def filter_changed(self, i: int): - self.filterChanged.emit(self.available_filters[i]) - self.settings.setValue("filter", i) + def filter_changed(self, index: int): + self.filterChanged.emit(self.filter.itemData(index, Qt.UserRole)) + self.settings.setValue("library_filter", index) diff --git a/rare/components/tabs/games/integrations/eos_group.py b/rare/components/tabs/games/integrations/eos_group.py index 784d4ae4..174f34aa 100644 --- a/rare/components/tabs/games/integrations/eos_group.py +++ b/rare/components/tabs/games/integrations/eos_group.py @@ -213,9 +213,7 @@ class EOSGroup(QGroupBox, Ui_EosWidget): self.enabled_cb.setChecked(enabled) def install_overlay(self, update=False): - base_path = os.path.join( - self.core.lgd.config.get("Legendary", "install_dir", fallback=os.path.expanduser("~/legendary")),".overlay" - ) + base_path = os.path.join(self.core.get_default_install_dir(), ".overlay") if update: if not self.overlay: self.overlay_stack.setCurrentIndex(1) diff --git a/rare/components/tabs/games/integrations/import_group.py b/rare/components/tabs/games/integrations/import_group.py index 24e05dff..f61f96d3 100644 --- a/rare/components/tabs/games/integrations/import_group.py +++ b/rare/components/tabs/games/integrations/import_group.py @@ -72,8 +72,10 @@ class ImportWorker(QRunnable): def __init__( self, - core: LegendaryCore, path: str, + core: LegendaryCore, + path: str, app_name: str = None, + platform: Optional[str] = None, import_folder: bool = False, import_dlcs: bool = False, import_force: bool = False @@ -86,6 +88,7 @@ class ImportWorker(QRunnable): self.path = Path(path) self.app_name = app_name self.import_folder = import_folder + self.platform = platform if platform is not None else self.core.default_platform self.import_dlcs = import_dlcs self.import_force = import_force @@ -110,9 +113,13 @@ class ImportWorker(QRunnable): result = ImportedGame(ImportResult.ERROR) result.path = str(path) if app_name or (app_name := find_app_name(str(path), self.core)): + game = self.core.get_game(app_name) result.app_name = app_name - result.app_title = app_title = self.core.get_game(app_name).app_title - success, message = self.__import_game(path, app_name, app_title) + result.app_title = game.app_title + platform = self.platform + if platform not in self.core.get_game(app_name, update_meta=False).asset_infos: + platform = "Windows" + success, message = self.__import_game(path, app_name, platform) if not success: result.result = ImportResult.FAILED result.message = message @@ -120,12 +127,13 @@ class ImportWorker(QRunnable): result.result = ImportResult.SUCCESS return result - def __import_game(self, path: Path, app_name: str, app_title: str): + def __import_game(self, path: Path, app_name: str, platform: str): cli = LegendaryCLI(self.core) status = LgndrIndirectStatus() args = LgndrImportGameArgs( app_path=str(path), app_name=app_name, + platform=platform, disable_check=self.import_force, skip_dlcs=not self.import_dlcs, with_dlcs=self.import_dlcs, @@ -192,7 +200,7 @@ class ImportGroup(QGroupBox): self.__install_dirs: Set[str] = set() self.path_edit = PathEdit( - self.core.get_default_install_dir(), + self.core.get_default_install_dir(self.core.default_platform), QFileDialog.DirectoryOnly, edit_func=self.path_edit_callback, parent=self, @@ -206,6 +214,7 @@ class ImportGroup(QGroupBox): self.app_name_edit = IndicatorLineEdit( placeholder=self.tr("Use in case the app name was not found automatically"), edit_func=self.app_name_edit_callback, + save_func=self.app_name_save_callback, parent=self, ) self.app_name_edit.textChanged.connect(self.app_name_changed) @@ -247,8 +256,10 @@ class ImportGroup(QGroupBox): def set_game(self, app_name: str): if app_name: - folder = self.rcore.get_game(app_name).folder_name - self.path_edit.setText(os.path.join(self.core.get_default_install_dir(), folder)) + rgame = self.rcore.get_game(app_name) + self.path_edit.setText( + os.path.join(self.core.get_default_install_dir(rgame.default_platform), rgame.folder_name) + ) self.app_name_edit.setText(app_name) def path_edit_callback(self, path) -> Tuple[bool, str, int]: @@ -279,6 +290,12 @@ class ImportGroup(QGroupBox): else: return False, text, IndicatorReasonsCommon.NOT_INSTALLED + def app_name_save_callback(self, text) -> None: + rgame = self.rcore.get_game(text) + self.ui.platform_combo.clear() + self.ui.platform_combo.addItems(rgame.platforms) + self.ui.platform_combo.setCurrentText(rgame.default_platform) + @pyqtSlot(str) def app_name_changed(self, app_name: str): self.info_label.setText("") @@ -294,6 +311,14 @@ class ImportGroup(QGroupBox): @pyqtSlot(int) def import_folder_changed(self, state: Qt.CheckState): self.app_name_edit.setEnabled(not state) + self.ui.platform_combo.setEnabled(not state) + self.ui.platform_combo.setToolTip( + self.tr( + "When importing multiple games, the current OS will be used at the" + " platform for the games that support it, otherwise the Windows version" + " will be imported." + ) if state else "" + ) self.ui.import_dlcs_check.setCheckState(Qt.Unchecked) self.ui.import_force_check.setCheckState(Qt.Unchecked) self.ui.import_dlcs_check.setEnabled( @@ -322,10 +347,11 @@ class ImportGroup(QGroupBox): self.worker = ImportWorker( self.core, path, - self.app_name_edit.text(), - self.ui.import_folder_check.isChecked(), - self.ui.import_dlcs_check.isChecked(), - self.ui.import_force_check.isChecked() + app_name=self.app_name_edit.text(), + platform=self.ui.platform_combo.currentText() if not self.ui.import_folder_check.isChecked() else None, + import_folder=self.ui.import_folder_check.isChecked(), + import_dlcs=self.ui.import_dlcs_check.isChecked(), + import_force=self.ui.import_force_check.isChecked() ) self.worker.signals.progress.connect(self.__on_import_progress) self.worker.signals.result.connect(self.__on_import_result) diff --git a/rare/components/tabs/games/integrations/ubisoft_group.py b/rare/components/tabs/games/integrations/ubisoft_group.py index eddd303d..008912be 100644 --- a/rare/components/tabs/games/integrations/ubisoft_group.py +++ b/rare/components/tabs/games/integrations/ubisoft_group.py @@ -74,7 +74,6 @@ class UbiConnectWorker(Worker): def __init__(self, core: LegendaryCore, ubi_account_id, partner_link_id): super(UbiConnectWorker, self).__init__() self.signals = UbiConnectWorker.Signals() - self.setAutoDelete(True) self.core = core self.ubi_account_id = ubi_account_id self.partner_link_id = partner_link_id diff --git a/rare/components/tabs/settings/game_settings.py b/rare/components/tabs/settings/game_settings.py index 201f5fee..d52dd79f 100644 --- a/rare/components/tabs/settings/game_settings.py +++ b/rare/components/tabs/settings/game_settings.py @@ -40,8 +40,10 @@ class DefaultGameSettings(QWidget): if platform.system() != "Windows": self.linux_settings = LinuxAppSettings() - self.proton_settings = ProtonSettings(self.linux_settings, self.wrapper_settings) - self.ui.proton_layout.addWidget(self.proton_settings) + if platform.system() != "Darwin": + self.proton_settings = ProtonSettings(self.linux_settings, self.wrapper_settings) + self.ui.proton_layout.addWidget(self.proton_settings) + self.proton_settings.environ_changed.connect(self.env_vars.reset_model) # FIXME: Remove the spacerItem and margins from the linux settings # FIXME: This should be handled differently at soem point in the future @@ -57,8 +59,6 @@ class DefaultGameSettings(QWidget): lambda active: self.wrapper_settings.add_wrapper("mangohud") if active else self.wrapper_settings.delete_wrapper("mangohud")) self.linux_settings.environ_changed.connect(self.env_vars.reset_model) - self.proton_settings.environ_changed.connect(self.env_vars.reset_model) - else: self.ui.linux_settings_widget.setVisible(False) @@ -77,7 +77,10 @@ class DefaultGameSettings(QWidget): proton = self.wrapper_settings.wrappers.get("proton", "") if proton: proton = proton.text - self.proton_settings.load_settings(app_name, proton) + if platform.system() != "Darwin": + self.proton_settings.load_settings(app_name, proton) + else: + proton = "" if proton: self.linux_settings.ui.wine_groupbox.setEnabled(False) else: diff --git a/rare/components/tabs/settings/legendary.py b/rare/components/tabs/settings/legendary.py index ec132120..c4254608 100644 --- a/rare/components/tabs/settings/legendary.py +++ b/rare/components/tabs/settings/legendary.py @@ -1,7 +1,7 @@ -import platform +import platform as pf import re from logging import getLogger -from typing import Tuple +from typing import Tuple, List from PyQt5.QtCore import QObject, pyqtSignal, QThreadPool, QSettings from PyQt5.QtWidgets import QSizePolicy, QWidget, QFileDialog, QMessageBox @@ -19,14 +19,21 @@ class RefreshGameMetaWorker(Worker): class Signals(QObject): finished = pyqtSignal() - def __init__(self): + def __init__(self, platforms: List[str], include_unreal: bool): super(RefreshGameMetaWorker, self).__init__() self.signals = RefreshGameMetaWorker.Signals() - self.setAutoDelete(True) self.core = LegendaryCoreSingleton() + if platforms: + self.platforms = platforms + else: + self.platforms = ["Windows"] + self.skip_ue = not include_unreal def run_real(self) -> None: - self.core.get_game_and_dlc_list(True, force_refresh=True) + for platform in self.platforms: + self.core.get_game_and_dlc_list( + True, platform=platform, force_refresh=True, skip_ue=self.skip_ue + ) self.signals.finished.emit() @@ -34,15 +41,26 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): def __init__(self, parent=None): super(LegendarySettings, self).__init__(parent=parent) self.setupUi(self) - self.settings = QSettings() + self.settings = QSettings(self) self.core = LegendaryCoreSingleton() - # Default installation directory + # Platform specific installation directory for macOS games + if pf.system() == "Darwin": + self.mac_install_dir = PathEdit( + self.core.get_default_install_dir("Mac"), + placeholder=self.tr("Default installation folder for macOS games"), + file_mode=QFileDialog.DirectoryOnly, + save_func=self.__mac_path_save, + ) + self.install_dir_layout.addWidget(self.mac_install_dir) + + # Platform-independent installation directory self.install_dir = PathEdit( self.core.get_default_install_dir(), + placeholder=self.tr("Default installation folder for Windows games"), file_mode=QFileDialog.DirectoryOnly, - save_func=self.path_save, + save_func=self.__win_path_save, ) self.install_dir_layout.addWidget(self.install_dir) @@ -82,20 +100,36 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): ) self.locale_layout.addWidget(self.locale_edit) - self.win32_cb.setChecked(self.settings.value("win32_meta", False, bool)) - self.win32_cb.stateChanged.connect(lambda: self.settings.setValue("win32_meta", self.win32_cb.isChecked())) + self.fetch_win32_check.setChecked(self.settings.value("win32_meta", False, bool)) + self.fetch_win32_check.stateChanged.connect( + lambda: self.settings.setValue("win32_meta", self.fetch_win32_check.isChecked()) + ) - self.mac_cb.setChecked(self.settings.value("mac_meta", platform.system() == "Darwin", bool)) - self.mac_cb.stateChanged.connect(lambda: self.settings.setValue("mac_meta", self.mac_cb.isChecked())) + self.fetch_macos_check.setChecked(self.settings.value("macos_meta", pf.system() == "Darwin", bool)) + self.fetch_macos_check.stateChanged.connect( + lambda: self.settings.setValue("macos_meta", self.fetch_macos_check.isChecked()) + ) + self.fetch_macos_check.setDisabled(pf.system() == "Darwin") - self.refresh_game_meta_btn.clicked.connect(self.refresh_game_meta) + self.fetch_unreal_check.setChecked(self.settings.value("unreal_meta", False, bool)) + self.fetch_unreal_check.stateChanged.connect( + lambda: self.settings.setValue("unreal_meta", self.fetch_unreal_check.isChecked()) + ) - def refresh_game_meta(self): - self.refresh_game_meta_btn.setDisabled(True) - self.refresh_game_meta_btn.setText(self.tr("Loading")) - worker = RefreshGameMetaWorker() - worker.signals.finished.connect(lambda: self.refresh_game_meta_btn.setDisabled(False)) - worker.signals.finished.connect(lambda: self.refresh_game_meta_btn.setText(self.tr("Refresh game meta"))) + self.refresh_metadata_button.clicked.connect(self.refresh_metadata) + # FIXME: Disable the button for now because it interferes with RareCore + self.refresh_metadata_button.setEnabled(False) + self.refresh_metadata_button.setVisible(False) + + def refresh_metadata(self): + self.refresh_metadata_button.setDisabled(True) + platforms = [] + if self.fetch_win32_check.isChecked(): + platforms.append("Win32") + if self.fetch_macos_check.isChecked(): + platforms.append("Mac") + worker = RefreshGameMetaWorker(platforms, self.fetch_unreal_check.isChecked()) + worker.signals.finished.connect(lambda: self.refresh_metadata_button.setDisabled(False)) QThreadPool.globalInstance().start(worker) @staticmethod @@ -120,12 +154,20 @@ class LegendarySettings(QWidget, Ui_LegendarySettings): self.core.lgd.config.remove_option("Legendary", "locale") self.core.lgd.save_config() - def path_save(self, text: str): - self.core.lgd.config["Legendary"]["install_dir"] = text - if not text and "install_dir" in self.core.lgd.config["Legendary"].keys(): - self.core.lgd.config["Legendary"].pop("install_dir") + def __mac_path_save(self, text: str) -> None: + self.__path_save(text, "mac_install_dir") + + def __win_path_save(self, text: str) -> None: + self.__path_save(text, "install_dir") + if pf.system() != "Darwin": + self.__mac_path_save(text) + + def __path_save(self, text: str, option: str = "Windows"): + self.core.lgd.config["Legendary"][option] = text + if not text and option in self.core.lgd.config["Legendary"].keys(): + self.core.lgd.config["Legendary"].pop(option) else: - logger.debug(f"Set config install_dir to {text}") + logger.debug(f"Set %s option in config to %s", option, text) self.core.lgd.save_config() def max_worker_save(self, workers: str): diff --git a/rare/components/tabs/settings/rare.py b/rare/components/tabs/settings/rare.py index 25b8c447..1c42040a 100644 --- a/rare/components/tabs/settings/rare.py +++ b/rare/components/tabs/settings/rare.py @@ -82,8 +82,6 @@ class RareSettings(QWidget, Ui_RareSettings): self.style_select.setCurrentIndex(0) self.style_select.currentIndexChanged.connect(self.on_style_select_changed) - self.interface_info.setVisible(False) - self.rpc = RPCSettings(self) self.right_layout.insertWidget(1, self.rpc, alignment=Qt.AlignTop) @@ -203,7 +201,6 @@ class RareSettings(QWidget, Ui_RareSettings): self.settings.setValue("color_scheme", "") self.style_select.setDisabled(False) set_color_pallete("") - self.interface_info.setVisible(True) def on_style_select_changed(self, style): if style: @@ -215,7 +212,6 @@ class RareSettings(QWidget, Ui_RareSettings): self.settings.setValue("style_sheet", "") self.color_select.setDisabled(False) set_style_sheet("") - self.interface_info.setVisible(True) def open_dir(self): if platform.system() == "Windows": @@ -230,7 +226,6 @@ class RareSettings(QWidget, Ui_RareSettings): def update_lang(self, i: int): self.settings.setValue("language", languages[i][0]) - self.interface_info.setVisible(True) def init_checkboxes(self, checkboxes): for cb in checkboxes: diff --git a/rare/lgndr/cli.py b/rare/lgndr/cli.py index 9261bcb8..d0a4e8d2 100644 --- a/rare/lgndr/cli.py +++ b/rare/lgndr/cli.py @@ -43,9 +43,9 @@ class LegendaryCLI(LegendaryCLIReal): def unlock_installed(func): @functools.wraps(func) def unlock(self, *args, **kwargs): - self.logger.debug("Using unlock decorator") + self.logger.debug("%s: Using unlock decorator", func.__name__) if not self.core.lgd.lock_installed(): - self.logger.info("Data is locked, trying to forcufully release it") + self.logger.info("Data is locked, trying to forcefully release it") # self.core.lgd._installed_lock.release(force=True) try: ret = func(self, *args, **kwargs) diff --git a/rare/lgndr/core.py b/rare/lgndr/core.py index aee89a78..15ac8f53 100644 --- a/rare/lgndr/core.py +++ b/rare/lgndr/core.py @@ -3,6 +3,7 @@ import json import logging import os from multiprocessing import Queue +from sys import platform as sys_platform from uuid import uuid4 # On Windows the monkeypatching of `run_real` below doesn't work like on Linux @@ -17,8 +18,8 @@ from legendary.models.game import Game, InstalledGame from legendary.models.manifest import ManifestMeta from rare.lgndr.downloader.mp.manager import DLManager -from rare.lgndr.lfs.lgndry import LGDLFS from rare.lgndr.glue.exception import LgndrException, LgndrLogHandler +from rare.lgndr.lfs.lgndry import LGDLFS legendary.core.DLManager = DLManager legendary.core.LGDLFS = LGDLFS @@ -30,6 +31,7 @@ class LegendaryCore(LegendaryCoreReal): def __init__(self, *args, **kwargs): super(LegendaryCore, self).__init__(*args, **kwargs) self.log.info("Using Rare's LegendaryCore monkey") + self.log.info("Using config in %s", self.lgd.path.replace(os.getlogin(), "")) self.handler = LgndrLogHandler(logging.CRITICAL) self.log.addHandler(self.handler) @@ -37,9 +39,9 @@ class LegendaryCore(LegendaryCoreReal): def unlock_installed(func): @functools.wraps(func) def unlock(self, *args, **kwargs): - self.log.debug("Using unlock decorator") + self.log.debug("%s: Using unlock decorator", func.__name__) if not self.lgd.lock_installed(): - self.log.info("Data is locked, trying to forcufully release it") + self.log.info("Data is locked, trying to forcefully release it") # self.lgd._installed_lock.release(force=True) try: ret = func(self, *args, **kwargs) @@ -50,6 +52,18 @@ class LegendaryCore(LegendaryCoreReal): return ret return unlock + @property + def default_platform(self) -> str: + os_default = "Mac" if sys_platform == "darwin" else "Windows" + usr_platform = self.lgd.config.get("Legendary", "default_platform", fallback=os_default) + return usr_platform if usr_platform in ("Windows", "Win32", "Mac") else os_default + + def update_check_enabled(self): + return False + + def update_notice_enabled(self): + return False + # skip_sync defaults to false but since Rare is persistent, skip by default # def get_installed_game(self, app_name, skip_sync=True) -> InstalledGame: # return super(LegendaryCore, self).get_installed_game(app_name, skip_sync) diff --git a/rare/main.py b/rare/main.py index 54c2d661..5bf89d58 100755 --- a/rare/main.py +++ b/rare/main.py @@ -18,6 +18,8 @@ def main() -> int: sys.stderr = open(os.devnull, 'w') os.environ["QT_QPA_PLATFORMTHEME"] = "" + if "LEGENDARY_CONFIG_PATH" in os.environ: + os.environ["LEGENDARY_CONFIG_PATH"] = os.path.expanduser(os.environ["LEGENDARY_CONFIG_PATH"]) # fix cx_freeze multiprocessing.freeze_support() diff --git a/rare/models/base_game.py b/rare/models/base_game.py index 8137fcf5..0a1ee7ab 100644 --- a/rare/models/base_game.py +++ b/rare/models/base_game.py @@ -97,15 +97,11 @@ class RareGameBase(QObject): @property def app_name(self) -> str: - return self.igame.app_name if self.igame is not None else self.game.app_name + return self.game.app_name @property def app_title(self) -> str: - return self.igame.title if self.igame is not None else self.game.app_title - - @property - def title(self) -> str: - return self.app_title + return self.game.app_title @property @abstractmethod @@ -125,6 +121,10 @@ class RareGameBase(QObject): """ return tuple(self.game.asset_infos.keys()) + @property + def default_platform(self) -> str: + return self.core.default_platform if self.core.default_platform in self.platforms else "Windows" + @property def is_mac(self) -> bool: """! @@ -173,7 +173,7 @@ class RareGameBase(QObject): @return str The current version of the game """ - return self.igame.version if self.igame is not None else self.game.app_version() + return self.igame.version if self.igame is not None else self.game.app_version(self.default_platform) @property def install_path(self) -> Optional[str]: @@ -234,7 +234,7 @@ class RareGameSlim(RareGameBase): status, (dt_local, dt_remote) = self.save_game_state def _upload(): - logger.info(f"Uploading save for {self.title}") + logger.info(f"Uploading save for {self.app_title}") self.state = RareGameSlim.State.SYNCING self.core.upload_save(self.app_name, self.igame.save_path, dt_local) self.state = RareGameSlim.State.IDLE @@ -246,7 +246,7 @@ class RareGameSlim(RareGameBase): logger.warning("Can't upload non existing save") return if self.state == RareGameSlim.State.SYNCING: - logger.error(f"{self.title} is already syncing") + logger.error(f"{self.app_title} is already syncing") return if thread: @@ -259,7 +259,7 @@ class RareGameSlim(RareGameBase): status, (dt_local, dt_remote) = self.save_game_state def _download(): - logger.info(f"Downloading save for {self.title}") + logger.info(f"Downloading save for {self.app_title}") self.state = RareGameSlim.State.SYNCING self.core.download_saves(self.app_name, self.latest_save.file.manifest_name, self.save_path) self.state = RareGameSlim.State.IDLE @@ -271,7 +271,7 @@ class RareGameSlim(RareGameBase): logger.error("Can't download non existing save") return if self.state == RareGameSlim.State.SYNCING: - logger.error(f"{self.title} is already syncing") + logger.error(f"{self.app_title} is already syncing") return if thread: diff --git a/rare/models/game.py b/rare/models/game.py index 3d54eeef..e3b19044 100644 --- a/rare/models/game.py +++ b/rare/models/game.py @@ -166,7 +166,8 @@ class RareGame(RareGameSlim): def update_game(self): self.game = self.core.get_game( - self.app_name, update_meta=False, platform=self.igame.platform if self.igame else "Windows" + self.app_name, update_meta=False, + platform=self.igame.platform if self.igame else self.default_platform ) def update_igame(self): @@ -228,7 +229,7 @@ class RareGame(RareGameSlim): if self.igame is not None: return self.game.app_version(self.igame.platform) else: - return self.game.app_version() + return self.game.app_version(self.default_platform) @property def has_update(self) -> bool: diff --git a/rare/models/image.py b/rare/models/image.py index 6202f5d6..68a87c9e 100644 --- a/rare/models/image.py +++ b/rare/models/image.py @@ -70,15 +70,15 @@ class ImageSize: DisplayWide = Preset(1, 1, Orientation.Wide, base=ImageWide) """! @brief Size and pixel ratio for wide 16/9 image display""" - Wide = DisplayWide + LibraryWide = Preset(1.21, 1, Orientation.Wide, base=ImageWide) - Normal = Display + Library = Preset(1.21, 1, base=Image) """! @brief Same as Display""" Small = Preset(3, 1, base=Image) """! @brief Small image size for displaying""" - SmallWide = Preset(1, 1, Orientation.Wide, base=ImageWide) + SmallWide = Preset(3, 1, Orientation.Wide, base=ImageWide) """! @brief Small image size for displaying""" Smaller = Preset(4, 1, base=Image) diff --git a/rare/shared/rare_core.py b/rare/shared/rare_core.py index abc68247..5b7301af 100644 --- a/rare/shared/rare_core.py +++ b/rare/shared/rare_core.py @@ -4,7 +4,7 @@ import time from argparse import Namespace from itertools import chain from logging import getLogger -from typing import Dict, Iterator, Callable, Optional, List, Union, Iterable, Tuple +from typing import Dict, Iterator, Callable, Optional, List, Union, Iterable, Tuple, Set from PyQt5.QtCore import QObject, pyqtSignal, QSettings, pyqtSlot, QThreadPool, QRunnable, QTimer from legendary.lfs.eos import EOSOverlayApp @@ -25,6 +25,7 @@ from .workers import ( ) from .workers.uninstall import uninstall_game from .workers.worker import QueueWorkerInfo, QueueWorkerState +from rare.utils import config_helper logger = getLogger("RareCore") @@ -54,9 +55,10 @@ class RareCore(QObject): self.args(args) self.signals(init=True) self.core(init=True) + config_helper.init_config_handler(self.__core) self.image_manager(init=True) - self.settings = QSettings() + self.settings = QSettings(self) self.queue_workers: List[QueueWorker] = [] self.queue_threadpool = QThreadPool() @@ -129,17 +131,44 @@ class RareCore(QObject): try: self.__core = LegendaryCore() except configparser.MissingSectionHeaderError as e: - logger.warning(f"Config is corrupt: {e}") - if config_path := os.environ.get("XDG_CONFIG_HOME"): - path = os.path.join(config_path, "legendary") + logger.warning("Config is corrupt: %s", e) + if config_path := os.environ.get('LEGENDARY_CONFIG_PATH'): + path = config_path + elif config_path := os.environ.get('XDG_CONFIG_HOME'): + path = os.path.join(config_path, 'legendary') else: - path = os.path.expanduser("~/.config/legendary") + path = os.path.expanduser('~/.config/legendary') + logger.info("Creating config in path: %s", config_path) with open(os.path.join(path, "config.ini"), "w") as config_file: config_file.write("[Legendary]") self.__core = LegendaryCore() + + # Initialize sections if they don't exist for section in ["Legendary", "default", "default.env"]: if section not in self.__core.lgd.config.sections(): self.__core.lgd.config.add_section(section) + + # Set some platform defaults if unset + def check_config(option: str, accepted: Set = None) -> bool: + _exists = self.__core.lgd.config.has_option("Legendary", option) + _value = self.__core.lgd.config.get("Legendary", option, fallback="") + _accepted = _value in accepted if accepted is not None else True + return _exists and bool(_value) and _accepted + + if not check_config("default_platform", {"Windows", "Win32", "Mac"}): + self.__core.lgd.config.set("Legendary", "default_platform", self.__core.default_platform) + if not check_config("install_dir"): + self.__core.lgd.config.set( + "Legendary", "install_dir", self.__core.get_default_install_dir() + ) + if not check_config("mac_install_dir"): + self.__core.lgd.config.set( + "Legendary", "mac_install_dir", self.__core.get_default_install_dir(self.__core.default_platform) + ) + # Always set these options + # Avoid falling back to Windows games on macOS + self.__core.lgd.config.set("Legendary", "install_platform_fallback", 'false') + # workaround if egl sync enabled, but no programdata_path # programdata_path might be unset if logging in through the browser if self.__core.egl_sync_enabled: @@ -148,6 +177,7 @@ class RareCore(QObject): else: if not os.path.exists(self.__core.egl.programdata_path): self.__core.lgd.config.remove_option("Legendary", "egl_sync") + self.__core.lgd.save_config() return self.__core @@ -176,7 +206,9 @@ class RareCore(QObject): del self.__args self.__args = None - del self.__eos_overlay + # del self.__eos_overlay + self.__eos_overlay = None + RareCore.__instance = None super(RareCore, self).deleteLater() @@ -357,7 +389,7 @@ class RareCore(QObject): yield game.game @property - def dlcs(self) -> Dict[str, Game]: + def dlcs(self) -> Dict[str, set[RareGame]]: """! RareGames that ARE DLCs themselves """ diff --git a/rare/shared/workers/fetch.py b/rare/shared/workers/fetch.py index 701e1890..b18ea83f 100644 --- a/rare/shared/workers/fetch.py +++ b/rare/shared/workers/fetch.py @@ -1,9 +1,10 @@ +import platform import time from argparse import Namespace from enum import IntEnum from logging import getLogger -from PyQt5.QtCore import QObject, pyqtSignal +from PyQt5.QtCore import QObject, pyqtSignal, QSettings from requests.exceptions import ConnectionError, HTTPError from rare.lgndr.core import LegendaryCore @@ -27,19 +28,54 @@ class FetchWorker(Worker): self.signals = FetchWorker.Signals() self.core = core self.args = args + self.settings = QSettings() def run_real(self): # Fetch regular EGL games with assets - self.signals.progress.emit(0, self.signals.tr("Updating game metadata")) start_time = time.time() + + want_unreal = self.settings.value("unreal_meta", False, bool) or self.args.debug + want_win32 = self.settings.value("win32_meta", False, bool) + want_macos = self.settings.value("macos_meta", False, bool) + need_macos = platform.system() == "Darwin" + need_windows = not any([want_win32, want_macos, need_macos, self.args.debug]) + + if want_win32 or self.args.debug: + logger.info( + "Requesting Win32 metadata due to %s, %s Unreal engine", + "settings" if want_win32 else "debug", + "with" if want_unreal else "without" + ) + self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows")) + self.core.get_game_and_dlc_list( + update_assets=not self.args.offline, platform="Win32", skip_ue=not want_unreal + ) + + if need_macos or want_macos or self.args.debug: + logger.info( + "Requesting macOS metadata due to %s, %s Unreal engine", + "platform" if need_macos else "settings" if want_macos else "debug", + "with" if want_unreal else "without" + ) + self.signals.progress.emit(15, self.signals.tr("Updating game metadata for macOS")) + self.core.get_game_and_dlc_list( + update_assets=not self.args.offline, platform="Mac", skip_ue=not want_unreal + ) + + if need_windows: + self.signals.progress.emit(00, self.signals.tr("Updating game metadata for Windows")) + logger.info( + "Requesting Windows metadata, %s Unreal engine", + "with" if want_unreal else "without" + ) games, dlc_dict = self.core.get_game_and_dlc_list( - update_assets=not self.args.offline, platform="Windows", skip_ue=False + update_assets=need_windows, platform="Windows", skip_ue=not want_unreal ) logger.debug(f"Games {len(games)}, games with DLCs {len(dlc_dict)}") logger.debug(f"Request games: {time.time() - start_time} seconds") # Fetch non-asset games - self.signals.progress.emit(10, self.signals.tr("Updating non-asset metadata")) + self.signals.progress.emit(30, self.signals.tr("Updating non-asset game metadata")) start_time = time.time() try: na_games, na_dlc_dict = self.core.get_non_asset_library_items(force_refresh=False, skip_ue=False) diff --git a/rare/shared/workers/wine_resolver.py b/rare/shared/workers/wine_resolver.py index 563e78b3..fe63ea4e 100644 --- a/rare/shared/workers/wine_resolver.py +++ b/rare/shared/workers/wine_resolver.py @@ -104,17 +104,17 @@ class OriginWineWorker(QRunnable): if install_dir: logger.debug("Found Unix install directory %s", install_dir) else: - logger.info("Could not find Unix install directory for %s", rgame.title) + logger.info("Could not find Unix install directory for %s", rgame.app_title) else: - logger.info("Could not find Wine install directory for %s", rgame.title) + logger.info("Could not find Wine install directory for %s", rgame.app_title) if install_dir: if os.path.isdir(install_dir): install_size = path_size(install_dir) rgame.set_origin_attributes(install_dir, install_size) - logger.info("Origin game %s (%s, %s)", rgame.title, install_dir, format_size(install_size)) + logger.info("Origin game %s (%s, %s)", rgame.app_title, install_dir, format_size(install_size)) else: - logger.warning("Origin game %s (%s does not exist)", rgame.title, install_dir) + logger.warning("Origin game %s (%s does not exist)", rgame.app_title, install_dir) else: - logger.info("Origin game %s is not installed", rgame.title) + logger.info("Origin game %s is not installed", rgame.app_title) logger.info("Origin worker finished in %ss", time.time() - t) diff --git a/rare/ui/components/tabs/games/integrations/import_group.py b/rare/ui/components/tabs/games/integrations/import_group.py index cc28fda1..ccd97153 100644 --- a/rare/ui/components/tabs/games/integrations/import_group.py +++ b/rare/ui/components/tabs/games/integrations/import_group.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ImportGroup(object): def setupUi(self, ImportGroup): ImportGroup.setObjectName("ImportGroup") - ImportGroup.resize(506, 184) + ImportGroup.resize(651, 218) ImportGroup.setWindowTitle("ImportGroup") ImportGroup.setWindowFilePath("") self.import_layout = QtWidgets.QFormLayout(ImportGroup) @@ -28,26 +28,35 @@ class Ui_ImportGroup(object): self.import_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.app_name_label) self.import_folder_label = QtWidgets.QLabel(ImportGroup) self.import_folder_label.setObjectName("import_folder_label") - self.import_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.import_folder_label) + self.import_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.import_folder_label) self.import_folder_check = QtWidgets.QCheckBox(ImportGroup) font = QtGui.QFont() font.setItalic(True) self.import_folder_check.setFont(font) self.import_folder_check.setObjectName("import_folder_check") - self.import_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.import_folder_check) + self.import_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.import_folder_check) self.import_dlcs_label = QtWidgets.QLabel(ImportGroup) self.import_dlcs_label.setObjectName("import_dlcs_label") - self.import_layout.setWidget(3, QtWidgets.QFormLayout.LabelRole, self.import_dlcs_label) + self.import_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.import_dlcs_label) self.import_dlcs_check = QtWidgets.QCheckBox(ImportGroup) font = QtGui.QFont() font.setItalic(True) self.import_dlcs_check.setFont(font) self.import_dlcs_check.setObjectName("import_dlcs_check") - self.import_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.import_dlcs_check) + self.import_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.import_dlcs_check) + self.import_force_label = QtWidgets.QLabel(ImportGroup) + self.import_force_label.setObjectName("import_force_label") + self.import_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.import_force_label) + self.import_force_check = QtWidgets.QCheckBox(ImportGroup) + font = QtGui.QFont() + font.setItalic(True) + self.import_force_check.setFont(font) + self.import_force_check.setObjectName("import_force_check") + self.import_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.import_force_check) self.import_button_label = QtWidgets.QLabel(ImportGroup) self.import_button_label.setText("Error") self.import_button_label.setObjectName("import_button_label") - self.import_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.import_button_label) + self.import_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.import_button_label) self.button_info_layout = QtWidgets.QHBoxLayout() self.button_info_layout.setObjectName("button_info_layout") self.import_button = QtWidgets.QPushButton(ImportGroup) @@ -58,16 +67,27 @@ class Ui_ImportGroup(object): self.import_button.setSizePolicy(sizePolicy) self.import_button.setObjectName("import_button") self.button_info_layout.addWidget(self.import_button) - self.import_layout.setLayout(5, QtWidgets.QFormLayout.FieldRole, self.button_info_layout) - self.import_force_label = QtWidgets.QLabel(ImportGroup) - self.import_force_label.setObjectName("import_force_label") - self.import_layout.setWidget(4, QtWidgets.QFormLayout.LabelRole, self.import_force_label) - self.import_force_check = QtWidgets.QCheckBox(ImportGroup) + self.import_layout.setLayout(6, QtWidgets.QFormLayout.FieldRole, self.button_info_layout) + self.platform_label = QtWidgets.QLabel(ImportGroup) + self.platform_label.setObjectName("platform_label") + self.import_layout.setWidget(2, QtWidgets.QFormLayout.LabelRole, self.platform_label) + self.platform_layout = QtWidgets.QHBoxLayout() + self.platform_layout.setObjectName("platform_layout") + self.platform_combo = QtWidgets.QComboBox(ImportGroup) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.platform_combo.sizePolicy().hasHeightForWidth()) + self.platform_combo.setSizePolicy(sizePolicy) + self.platform_combo.setObjectName("platform_combo") + self.platform_layout.addWidget(self.platform_combo) + self.platform_tooltip = QtWidgets.QLabel(ImportGroup) font = QtGui.QFont() font.setItalic(True) - self.import_force_check.setFont(font) - self.import_force_check.setObjectName("import_force_check") - self.import_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.import_force_check) + self.platform_tooltip.setFont(font) + self.platform_tooltip.setObjectName("platform_tooltip") + self.platform_layout.addWidget(self.platform_tooltip) + self.import_layout.setLayout(2, QtWidgets.QFormLayout.FieldRole, self.platform_layout) self.retranslateUi(ImportGroup) @@ -80,9 +100,11 @@ class Ui_ImportGroup(object): self.import_folder_check.setText(_translate("ImportGroup", "Scan the installation path for game folders and import them")) self.import_dlcs_label.setText(_translate("ImportGroup", "Import DLCs")) self.import_dlcs_check.setText(_translate("ImportGroup", "If a game has DLCs, try to import them too")) - self.import_button.setText(_translate("ImportGroup", "Import Game")) self.import_force_label.setText(_translate("ImportGroup", "Force import")) self.import_force_check.setText(_translate("ImportGroup", "Import game despite missing files")) + self.import_button.setText(_translate("ImportGroup", "Import Game")) + self.platform_label.setText(_translate("ImportGroup", "Platform")) + self.platform_tooltip.setText(_translate("ImportGroup", "Select the native platform of the game")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/games/integrations/import_group.ui b/rare/ui/components/tabs/games/integrations/import_group.ui index 43ed7e5e..60dbd349 100644 --- a/rare/ui/components/tabs/games/integrations/import_group.ui +++ b/rare/ui/components/tabs/games/integrations/import_group.ui @@ -6,8 +6,8 @@ 0 0 - 506 - 184 + 651 + 218 @@ -37,14 +37,14 @@ - + Import all folders - + @@ -56,14 +56,14 @@ - + Import DLCs - + @@ -76,13 +76,32 @@ + + + Force import + + + + + + + + true + + + + Import game despite missing files + + + + Error - + @@ -99,24 +118,38 @@ - - + + - Force import + Platform - - - - - true - - - - Import game despite missing files - - + + + + + + + 0 + 0 + + + + + + + + + true + + + + Select the native platform of the game + + + + diff --git a/rare/ui/components/tabs/settings/legendary.py b/rare/ui/components/tabs/settings/legendary.py index 634f5efa..829a60ab 100644 --- a/rare/ui/components/tabs/settings/legendary.py +++ b/rare/ui/components/tabs/settings/legendary.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/legendary.ui' # -# Created by: PyQt5 UI code generator 5.15.7 +# Created by: PyQt5 UI code generator 5.15.10 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_LegendarySettings(object): def setupUi(self, LegendarySettings): LegendarySettings.setObjectName("LegendarySettings") - LegendarySettings.resize(595, 334) + LegendarySettings.resize(681, 456) LegendarySettings.setWindowTitle("LegendarySettings") self.legendary_layout = QtWidgets.QHBoxLayout(LegendarySettings) self.legendary_layout.setObjectName("legendary_layout") @@ -125,21 +125,30 @@ class Ui_LegendarySettings(object): self.clean_button = QtWidgets.QPushButton(self.cleanup_group) self.clean_button.setObjectName("clean_button") self.cleanup_layout.addWidget(self.clean_button) - self.right_layout.addWidget(self.cleanup_group, 0, QtCore.Qt.AlignTop) - self.meta_group = QtWidgets.QGroupBox(LegendarySettings) - self.meta_group.setObjectName("meta_group") - self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.meta_group) + self.right_layout.addWidget(self.cleanup_group) + self.metadata_group = QtWidgets.QGroupBox(LegendarySettings) + self.metadata_group.setObjectName("metadata_group") + self.verticalLayout_2 = QtWidgets.QVBoxLayout(self.metadata_group) self.verticalLayout_2.setObjectName("verticalLayout_2") - self.win32_cb = QtWidgets.QCheckBox(self.meta_group) - self.win32_cb.setObjectName("win32_cb") - self.verticalLayout_2.addWidget(self.win32_cb) - self.mac_cb = QtWidgets.QCheckBox(self.meta_group) - self.mac_cb.setObjectName("mac_cb") - self.verticalLayout_2.addWidget(self.mac_cb) - self.refresh_game_meta_btn = QtWidgets.QPushButton(self.meta_group) - self.refresh_game_meta_btn.setObjectName("refresh_game_meta_btn") - self.verticalLayout_2.addWidget(self.refresh_game_meta_btn) - self.right_layout.addWidget(self.meta_group) + self.fetch_win32_check = QtWidgets.QCheckBox(self.metadata_group) + self.fetch_win32_check.setObjectName("fetch_win32_check") + self.verticalLayout_2.addWidget(self.fetch_win32_check) + self.fetch_macos_check = QtWidgets.QCheckBox(self.metadata_group) + self.fetch_macos_check.setObjectName("fetch_macos_check") + self.verticalLayout_2.addWidget(self.fetch_macos_check) + self.fetch_unreal_check = QtWidgets.QCheckBox(self.metadata_group) + self.fetch_unreal_check.setObjectName("fetch_unreal_check") + self.verticalLayout_2.addWidget(self.fetch_unreal_check) + self.metadata_info = QtWidgets.QLabel(self.metadata_group) + font = QtGui.QFont() + font.setItalic(True) + self.metadata_info.setFont(font) + self.metadata_info.setObjectName("metadata_info") + self.verticalLayout_2.addWidget(self.metadata_info) + self.refresh_metadata_button = QtWidgets.QPushButton(self.metadata_group) + self.refresh_metadata_button.setObjectName("refresh_metadata_button") + self.verticalLayout_2.addWidget(self.refresh_metadata_button) + self.right_layout.addWidget(self.metadata_group) spacerItem1 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) self.right_layout.addItem(spacerItem1) self.legendary_layout.addLayout(self.right_layout) @@ -148,11 +157,11 @@ class Ui_LegendarySettings(object): def retranslateUi(self, LegendarySettings): _translate = QtCore.QCoreApplication.translate - self.install_dir_group.setTitle(_translate("LegendarySettings", "Default Installation Directory")) - self.download_group.setTitle(_translate("LegendarySettings", "Download Settings")) - self.max_workers_label.setText(_translate("LegendarySettings", "Max Workers")) + self.install_dir_group.setTitle(_translate("LegendarySettings", "Default installation folder")) + self.download_group.setTitle(_translate("LegendarySettings", "Download settings")) + self.max_workers_label.setText(_translate("LegendarySettings", "Max sorkers")) self.max_workers_info_label.setText(_translate("LegendarySettings", "Less is slower (0: Default)")) - self.max_memory_label.setText(_translate("LegendarySettings", "Max Shared Memory")) + self.max_memory_label.setText(_translate("LegendarySettings", "Max shared memory")) self.max_memory_spin.setSuffix(_translate("LegendarySettings", "MiB")) self.max_memory_info_label.setText(_translate("LegendarySettings", "Less is slower (0: Default)")) self.preferred_cdn_label.setText(_translate("LegendarySettings", "Preferred CDN")) @@ -162,10 +171,12 @@ class Ui_LegendarySettings(object): self.cleanup_group.setTitle(_translate("LegendarySettings", "Cleanup")) self.clean_keep_manifests_button.setText(_translate("LegendarySettings", "Clean, but keep manifests")) self.clean_button.setText(_translate("LegendarySettings", "Remove everything")) - self.meta_group.setTitle(_translate("LegendarySettings", "Game metadata")) - self.win32_cb.setText(_translate("LegendarySettings", "Load 32bit data")) - self.mac_cb.setText(_translate("LegendarySettings", "Load MacOS data")) - self.refresh_game_meta_btn.setText(_translate("LegendarySettings", "Refresh game meta")) + self.metadata_group.setTitle(_translate("LegendarySettings", "Platforms")) + self.fetch_win32_check.setText(_translate("LegendarySettings", "Include Win32 games")) + self.fetch_macos_check.setText(_translate("LegendarySettings", "Include macOS games")) + self.fetch_unreal_check.setText(_translate("LegendarySettings", "Include Unreal engine")) + self.metadata_info.setText(_translate("LegendarySettings", "Restart Rare to apply")) + self.refresh_metadata_button.setText(_translate("LegendarySettings", "Refresh metadata")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/settings/legendary.ui b/rare/ui/components/tabs/settings/legendary.ui index a69ef823..65e69f1c 100644 --- a/rare/ui/components/tabs/settings/legendary.ui +++ b/rare/ui/components/tabs/settings/legendary.ui @@ -6,8 +6,8 @@ 0 0 - 595 - 334 + 681 + 456 @@ -19,7 +19,7 @@ - Default Installation Directory + Default installation folder Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -30,7 +30,7 @@ - Download Settings + Download settings Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -48,7 +48,7 @@ - Max Workers + Max sorkers @@ -90,7 +90,7 @@ - Max Shared Memory + Max shared memory @@ -200,7 +200,7 @@ - + Cleanup @@ -227,29 +227,48 @@ - + - Game metadata + Platforms - + - Load 32bit data + Include Win32 games - + - Load MacOS data + Include macOS games - + - Refresh game meta + Include Unreal engine + + + + + + + + true + + + + Restart Rare to apply + + + + + + + Refresh metadata diff --git a/rare/ui/components/tabs/settings/rare.py b/rare/ui/components/tabs/settings/rare.py index fc0bc69f..fc1abaff 100644 --- a/rare/ui/components/tabs/settings/rare.py +++ b/rare/ui/components/tabs/settings/rare.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/tabs/settings/rare.ui' # -# Created by: PyQt5 UI code generator 5.15.6 +# Created by: PyQt5 UI code generator 5.15.10 # # WARNING: Any manual changes made to this file will be lost when pyuic5 is # run again. Do not edit this file unless you know what you are doing. @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_RareSettings(object): def setupUi(self, RareSettings): RareSettings.setObjectName("RareSettings") - RareSettings.resize(517, 434) + RareSettings.resize(623, 428) RareSettings.setWindowTitle("RareSettings") self.rare_layout = QtWidgets.QHBoxLayout(RareSettings) self.rare_layout.setObjectName("rare_layout") @@ -148,25 +148,25 @@ class Ui_RareSettings(object): _translate = QtCore.QCoreApplication.translate self.interface_group.setTitle(_translate("RareSettings", "Interface")) self.lang_label.setText(_translate("RareSettings", "Language")) - self.color_label.setText(_translate("RareSettings", "Color Scheme")) + self.color_label.setText(_translate("RareSettings", "Color scheme")) self.color_select.setItemText(0, _translate("RareSettings", "None")) - self.style_label.setText(_translate("RareSettings", "Style Sheet")) + self.style_label.setText(_translate("RareSettings", "Style sheet")) self.style_select.setItemText(0, _translate("RareSettings", "None")) - self.interface_info.setText(_translate("RareSettings", "Restart Rare to apply.")) + self.interface_info.setText(_translate("RareSettings", "Restart Rare to apply changes.")) self.settings_group.setTitle(_translate("RareSettings", "Behavior")) self.save_size.setText(_translate("RareSettings", "Restore window size on application startup")) - self.notification.setText(_translate("RareSettings", "Show notification on download completion")) - self.log_games.setText(_translate("RareSettings", "Show console for game debug")) - self.sys_tray.setText(_translate("RareSettings", "Exit to System tray")) - self.auto_update.setText(_translate("RareSettings", "Update games on application startup")) - self.confirm_start.setText(_translate("RareSettings", "Confirm game launch")) - self.auto_sync_cloud.setText(_translate("RareSettings", "Automatically sync with cloud")) + self.notification.setText(_translate("RareSettings", "Show notifications when downloads complete")) + self.log_games.setText(_translate("RareSettings", "Show console windows when launching games")) + self.sys_tray.setText(_translate("RareSettings", "Exit to system tray")) + self.auto_update.setText(_translate("RareSettings", "Queue game updates on application startup")) + self.confirm_start.setText(_translate("RareSettings", "Confirm before launching games")) + self.auto_sync_cloud.setText(_translate("RareSettings", "Automatically upload/download cloud saves")) self.log_dir_group.setTitle(_translate("RareSettings", "Logs")) - self.log_dir_open_button.setText(_translate("RareSettings", "Open Log directory")) - self.log_dir_clean_button.setText(_translate("RareSettings", "Clean Log directory")) + self.log_dir_open_button.setText(_translate("RareSettings", "Open log folder")) + self.log_dir_clean_button.setText(_translate("RareSettings", "Clean log folder")) self.groupBox.setTitle(_translate("RareSettings", "Shortcuts")) - self.desktop_link_btn.setText(_translate("RareSettings", "Create Desktop link")) - self.startmenu_link_btn.setText(_translate("RareSettings", "Create start menu link")) + self.desktop_link_btn.setText(_translate("RareSettings", "Create on desktop")) + self.startmenu_link_btn.setText(_translate("RareSettings", "Create in menu")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/settings/rare.ui b/rare/ui/components/tabs/settings/rare.ui index 5f89464d..2178d31d 100644 --- a/rare/ui/components/tabs/settings/rare.ui +++ b/rare/ui/components/tabs/settings/rare.ui @@ -6,8 +6,8 @@ 0 0 - 517 - 434 + 623 + 428 @@ -51,7 +51,7 @@ - Color Scheme + Color scheme @@ -73,7 +73,7 @@ - Style Sheet + Style sheet @@ -100,7 +100,7 @@ - Restart Rare to apply. + Restart Rare to apply changes. true @@ -126,42 +126,42 @@ - Show notification on download completion + Show notifications when downloads complete - Show console for game debug + Show console windows when launching games - Exit to System tray + Exit to system tray - Update games on application startup + Queue game updates on application startup - Confirm game launch + Confirm before launching games - Automatically sync with cloud + Automatically upload/download cloud saves @@ -207,14 +207,14 @@ - Open Log directory + Open log folder - Clean Log directory + Clean log folder @@ -246,14 +246,14 @@ - Create Desktop link + Create on desktop - Create start menu link + Create in menu diff --git a/rare/widgets/flow_layout.py b/rare/widgets/flow_layout.py index f8d32735..c13b67b5 100644 --- a/rare/widgets/flow_layout.py +++ b/rare/widgets/flow_layout.py @@ -1,33 +1,55 @@ -from typing import Optional +from typing import Optional, List, overload -from PyQt5.QtCore import ( - Qt, - QRect, - QSize, - QPoint, -) -from PyQt5.QtWidgets import ( - QLayout, - QStyle, - QSizePolicy, - QLayoutItem, -) +from PyQt5.QtCore import Qt, QRect, QSize, QPoint +from PyQt5.QtWidgets import QLayout, QStyle, QSizePolicy, QLayoutItem, QWidget class FlowLayout(QLayout): - def __init__(self, parent=None, margin=-1, hspacing=-1, vspacing=-1): + def __init__(self, parent=None): super(FlowLayout, self).__init__(parent) - self._hspacing = hspacing - self._vspacing = vspacing - self._items = [] - self.setContentsMargins(margin, margin, margin, margin) self.setObjectName(type(self).__name__) + self._hspacing = -1 + self._vspacing = -1 + self._items: List[QLayoutItem] = [] def __del__(self): del self._items[:] + @overload + def indexOf(self, a0: QWidget) -> int: + try: + return next(idx for idx, item in enumerate(self._items) if item.widget() is a0) + except: + return -1 + + def indexOf(self, a0: QLayoutItem) -> int: + try: + return self._items.index(a0) + except: + return -1 + def addItem(self, a0: QLayoutItem) -> None: self._items.append(a0) + self.invalidate() + + def removeItem(self, a0: QLayoutItem) -> None: + self._items.remove(a0) + self.invalidate() + + def spacing(self) -> int: + hspacing = self.horizontalSpacing() + if hspacing == self.verticalSpacing(): + return hspacing + else: + return -1 + + def setSpacing(self, a0: int) -> None: + self._hspacing = self._vspacing = a0 + self.invalidate() + + def setHorizontalSpacing(self, a0: int) -> None: + self._hspacing = a0 + self.invalidate() def horizontalSpacing(self): if self._hspacing >= 0: @@ -35,6 +57,10 @@ class FlowLayout(QLayout): else: return self.smartSpacing(QStyle.PM_LayoutHorizontalSpacing) + def setVerticalSpacing(self, a0: int) -> None: + self._vspacing = a0 + self.invalidate() + def verticalSpacing(self): if self._vspacing >= 0: return self._vspacing @@ -51,11 +77,14 @@ class FlowLayout(QLayout): def takeAt(self, index: int) -> Optional[QLayoutItem]: if 0 <= index < len(self._items): - return self._items.pop(index) + item = self._items.pop(index) + self.invalidate() + return item return None def expandingDirections(self) -> Qt.Orientations: - return Qt.Horizontal | Qt.Vertical + return Qt.Orientations(Qt.Orientation(0)) + # return Qt.Horizontal | Qt.Vertical def hasHeightForWidth(self) -> bool: return True @@ -93,8 +122,6 @@ class FlowLayout(QLayout): if item.isEmpty(): continue widget = item.widget() - if not widget.isVisible(): - continue hspace = self.horizontalSpacing() if hspace == -1: hspace = widget.style().layoutSpacing( diff --git a/rare/widgets/indicator_edit.py b/rare/widgets/indicator_edit.py index c9a76bb6..9c31a737 100644 --- a/rare/widgets/indicator_edit.py +++ b/rare/widgets/indicator_edit.py @@ -4,7 +4,6 @@ from logging import getLogger from typing import Callable, Tuple, Optional, Dict, List from PyQt5.QtCore import ( - Qt, QSize, pyqtSignal, QFileInfo, @@ -135,7 +134,8 @@ class IndicatorLineEdit(QWidget): # Add line_edit self.line_edit = QLineEdit(self) self.line_edit.setObjectName(f"{type(self).__name__}Edit") - self.line_edit.setPlaceholderText(placeholder if placeholder else self.tr("Default")) + self.line_edit.setPlaceholderText(placeholder if placeholder else self.tr("Use global/default setting")) + self.line_edit.setToolTip(placeholder if placeholder else "") self.line_edit.setSizePolicy(horiz_policy, QSizePolicy.Fixed) # Add completer self.setCompleter(completer) diff --git a/rare/widgets/library_layout.py b/rare/widgets/library_layout.py index dd2c056d..721e1ad3 100644 --- a/rare/widgets/library_layout.py +++ b/rare/widgets/library_layout.py @@ -1,23 +1,18 @@ from typing import Callable -from PyQt5.QtCore import ( - Qt, - QRect, - QPoint, -) -from PyQt5.QtWidgets import ( - QSizePolicy, -) +from PyQt5.QtCore import Qt, QRect, QPoint +from PyQt5.QtWidgets import QSizePolicy from .flow_layout import FlowLayout class LibraryLayout(FlowLayout): - def __init__(self, parent=None, margin=6, spacing=11): - super(LibraryLayout, self).__init__(parent=parent, margin=margin, hspacing=spacing, vspacing=spacing) + def __init__(self, parent=None): + super(LibraryLayout, self).__init__(parent) def expandingDirections(self) -> Qt.Orientations: - return Qt.Horizontal | Qt.Vertical + return Qt.Orientations(Qt.Orientation(0)) + # return Qt.Horizontal | Qt.Vertical def setGeometry(self, a0: QRect) -> None: super(FlowLayout, self).setGeometry(a0) @@ -127,4 +122,4 @@ class LibraryLayout(FlowLayout): def sort(self, key: Callable, reverse=False) -> None: self._items.sort(key=key, reverse=reverse) - self.setGeometry(self.parent().contentsRect()) + self.setGeometry(self.parent().contentsRect().adjusted(*self.parent().getContentsMargins()))