diff --git a/rare/components/tabs/__init__.py b/rare/components/tabs/__init__.py index 03c245a9..c01448cf 100644 --- a/rare/components/tabs/__init__.py +++ b/rare/components/tabs/__init__.py @@ -25,18 +25,18 @@ class TabWidget(QTabWidget): self.setTabBar(MainTabBar(disabled_tab)) # Generate Tabs - self.games_tab = GamesTab() + self.games_tab = GamesTab(self) self.addTab(self.games_tab, self.tr("Games")) if not self.args.offline: # updates = self.games_tab.default_widget.game_list.updates - self.downloads_tab = DownloadsTab(self.games_tab.updates) + self.downloads_tab = DownloadsTab(self.games_tab.game_updates, self) self.addTab( self.downloads_tab, "Downloads" + ( - " (" + str(len(self.games_tab.updates)) + ")" - if len(self.games_tab.updates) != 0 + " (" + str(len(self.games_tab.game_updates)) + ")" + if len(self.games_tab.game_updates) != 0 else "" ), ) diff --git a/rare/components/tabs/downloads/__init__.py b/rare/components/tabs/downloads/__init__.py index 124681f2..012e9dba 100644 --- a/rare/components/tabs/downloads/__init__.py +++ b/rare/components/tabs/downloads/__init__.py @@ -1,6 +1,6 @@ import datetime from logging import getLogger -from typing import List, Dict, Union, Set +from typing import List, Dict, Union, Set, Optional from PyQt5.QtCore import QThread, pyqtSignal, QSettings, pyqtSlot from PyQt5.QtWidgets import ( @@ -18,6 +18,7 @@ from rare.components.dialogs.install_dialog import InstallDialog from rare.components.tabs.downloads.dl_queue_widget import DlQueueWidget, DlWidget from rare.components.tabs.downloads.download_thread import DownloadThread from rare.lgndr.models.downloading import UIUpdate +from rare.models.game import RareGame from rare.models.install import InstallOptionsModel, InstallQueueItemModel from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton from rare.ui.components.tabs.downloads.downloads_tab import Ui_DownloadsTab @@ -31,12 +32,13 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): dl_queue: List[InstallQueueItemModel] = [] dl_status = pyqtSignal(int) - def __init__(self, updates: Set): - super(DownloadsTab, self).__init__() + def __init__(self, game_updates: Set[RareGame], parent=None): + super(DownloadsTab, self).__init__(parent=parent) self.setupUi(self) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() - self.active_game: Game = None + + self.active_game: Optional[Game] = None self.analysis = None self.kill_button.clicked.connect(self.stop_download) @@ -54,21 +56,21 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.update_text = QLabel(self.tr("No updates available")) self.update_layout.addWidget(self.update_text) - self.update_text.setVisible(len(updates) == 0) + self.update_text.setVisible(len(game_updates) == 0) - for app_name in updates: - self.add_update(app_name) + for rgame in game_updates: + self.add_update(rgame) self.queue_widget.item_removed.connect(self.queue_item_removed) - self.signals.install_game.connect(self.get_install_options) - self.signals.game_uninstalled.connect(self.queue_item_removed) - self.signals.game_uninstalled.connect(self.remove_update) + self.signals.game.install.connect(self.get_install_options) + self.signals.game.uninstalled.connect(self.queue_item_removed) + self.signals.game.uninstalled.connect(self.remove_update) - self.signals.add_download.connect( + self.signals.download.enqueue_game.connect( lambda app_name: self.add_update(app_name) ) - self.signals.game_uninstalled.connect(self.game_uninstalled) + self.signals.game.uninstalled.connect(self.game_uninstalled) self.reset_infos() @@ -77,17 +79,17 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): w.update_button.setDisabled(False) w.update_with_settings.setDisabled(False) - def add_update(self, app_name: str): - if old_widget := self.update_widgets.get(app_name, False): + def add_update(self, rgame: RareGame): + if old_widget := self.update_widgets.get(rgame.app_name, False): old_widget.deleteLater() - self.update_widgets.pop(app_name) - widget = UpdateWidget(self.core, app_name, self) + self.update_widgets.pop(rgame.app_name) + widget = UpdateWidget(self.core, rgame, self) self.update_layout.addWidget(widget) - self.update_widgets[app_name] = widget + self.update_widgets[rgame.app_name] = widget widget.update_signal.connect(self.get_install_options) if QSettings().value("auto_update", False, bool): self.get_install_options( - InstallOptionsModel(app_name=app_name, update=True, silent=True) + InstallOptionsModel(app_name=rgame.app_name, update=True, silent=True) ) widget.update_button.setDisabled(True) self.update_text.setVisible(False) @@ -145,7 +147,7 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.analysis = queue_item.download.analysis self.dl_name.setText(self.active_game.app_title) - self.signals.installation_started.emit(self.active_game.app_name) + self.signals.progress.started.emit(self.active_game.app_name) @pyqtSlot(DownloadThread.ReturnStatus) def status(self, result: DownloadThread.ReturnStatus): @@ -179,10 +181,10 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.remove_update(game.app_name) self.signals.send_notification.emit(game.app_title) - self.signals.update_gamelist.emit([game.app_name]) + self.signals.game.installed.emit([game.app_name]) self.signals.update_download_tab_text.emit() - self.signals.installation_finished.emit(True, game.app_name) + self.signals.progress.finished.emit(game.app_name, True) self.reset_infos() @@ -199,7 +201,7 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): if w := self.update_widgets.get(self.active_game.app_name): w.update_button.setDisabled(False) w.update_with_settings.setDisabled(False) - self.signals.installation_finished.emit(False, self.active_game.app_name) + self.signals.progress.finished.emit(self.active_game.app_name, False) self.active_game = None if self.dl_queue: self.start_installation(self.dl_queue[0]) @@ -214,7 +216,8 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): self.downloaded.setText("n/a") self.analysis = None - def progress_update(self, ui_update: UIUpdate): + @pyqtSlot(str, UIUpdate) + def progress_update(self, app_name: str, ui_update: UIUpdate): self.progress_bar.setValue( 100 * ui_update.total_downloaded // self.analysis.dl_size ) @@ -226,7 +229,8 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): f"{get_size(ui_update.total_downloaded)} / {get_size(self.analysis.dl_size)}" ) self.time_left.setText(self.get_time(ui_update.estimated_time_left)) - self.signals.dl_progress.emit( + self.signals.progress.value.emit( + app_name, 100 * ui_update.total_downloaded // self.analysis.dl_size ) @@ -265,14 +269,13 @@ class DownloadsTab(QWidget, Ui_DownloadsTab): class UpdateWidget(QWidget): update_signal = pyqtSignal(InstallOptionsModel) - def __init__(self, core: LegendaryCore, app_name: str, parent): + def __init__(self, core: LegendaryCore, rgame: RareGame, parent): super(UpdateWidget, self).__init__(parent=parent) self.core = core - self.game: Game = core.get_game(app_name) - self.igame: InstalledGame = self.core.get_installed_game(app_name) + self.rgame = rgame layout = QVBoxLayout() - self.title = QLabel(self.igame.title) + self.title = QLabel(self.rgame.app_title) layout.addWidget(self.title) self.update_button = QPushButton(self.tr("Update Game")) @@ -284,7 +287,7 @@ class UpdateWidget(QWidget): layout.addWidget( QLabel( self.tr("Version: {} >> {}") - .format(self.igame.version, self.game.app_version(self.igame.platform)) + .format(self.rgame.version, self.rgame.remote_version) ) ) @@ -294,5 +297,5 @@ class UpdateWidget(QWidget): self.update_button.setDisabled(True) self.update_with_settings.setDisabled(True) self.update_signal.emit( - InstallOptionsModel(app_name=self.igame.app_name, update=True, silent=auto) + InstallOptionsModel(app_name=self.rgame.app_name, update=True, silent=auto) ) # True if settings diff --git a/rare/components/tabs/downloads/download_thread.py b/rare/components/tabs/downloads/download_thread.py index 6933b2f4..35a43a49 100644 --- a/rare/components/tabs/downloads/download_thread.py +++ b/rare/components/tabs/downloads/download_thread.py @@ -36,7 +36,7 @@ class DownloadThread(QThread): shortcuts: bool = False ret_status = pyqtSignal(ReturnStatus) - ui_update = pyqtSignal(UIUpdate) + ui_update = pyqtSignal(str, UIUpdate) def __init__(self, core: LegendaryCore, item: InstallQueueItemModel): super(DownloadThread, self).__init__() @@ -56,7 +56,8 @@ class DownloadThread(QThread): time.sleep(1) while self.item.download.dlm.is_alive(): try: - self.ui_update.emit(self.item.download.dlm.status_queue.get(timeout=1.0)) + self.ui_update.emit(self.item.download.game.app_name, + self.item.download.dlm.status_queue.get(timeout=1.0)) except queue.Empty: pass if self.dlm_signals.update: diff --git a/rare/components/tabs/games/__init__.py b/rare/components/tabs/games/__init__.py index 61985068..d5800225 100644 --- a/rare/components/tabs/games/__init__.py +++ b/rare/components/tabs/games/__init__.py @@ -1,3 +1,4 @@ +import time from logging import getLogger from typing import Tuple, Dict, Union, List, Set @@ -5,6 +6,7 @@ from PyQt5.QtCore import QSettings, Qt, pyqtSlot from PyQt5.QtWidgets import QStackedWidget, QVBoxLayout, QWidget, QScrollArea, QFrame from legendary.models.game import InstalledGame, Game +from rare.models.game import RareGame from rare.shared import ( LegendaryCoreSingleton, GlobalSignalsSingleton, @@ -17,13 +19,9 @@ from rare.widgets.sliding_stack import SlidingStackedWidget from .cloud_save_utils import CloudSaveUtils from .game_info import GameInfoTabs from .game_utils import GameUtils -from .game_widgets.base_installed_widget import BaseInstalledWidget -from .game_widgets.base_uninstalled_widget import BaseUninstalledWidget -from .game_widgets.installed_icon_widget import InstalledIconWidget -from .game_widgets.installed_list_widget import InstalledListWidget -from .game_widgets.installing_game_widget import InstallingGameWidget -from .game_widgets.uninstalled_icon_widget import UninstalledIconWidget -from .game_widgets.uninstalled_list_widget import UninstalledListWidget +from .game_widgets import LibraryWidgetController +from .game_widgets.icon_game_widget import IconGameWidget +from .game_widgets.list_game_widget import ListGameWidget from .head_bar import GameListHeadBar from .integrations import IntegrationsTabs @@ -40,17 +38,15 @@ class GamesTab(QStackedWidget): self.image_manager = ImageManagerSingleton() self.settings = QSettings() - self.widgets: Dict[str, Tuple[ - Union[InstalledIconWidget, UninstalledIconWidget], Union[InstalledListWidget, UninstalledListWidget]]] = {} - self.updates: Set = set() + self.widgets: Dict[str, Tuple[IconGameWidget, ListGameWidget]] = {} + self.game_updates: Set[RareGame] = set() self.active_filter: int = 0 - self.uninstalled_games: List[Game] = [] self.game_list: List[Game] = self.api_results.game_list - self.dlcs = self.api_results.dlcs - self.bit32 = self.api_results.bit32_games - self.mac_games = self.api_results.mac_games - self.no_assets = self.api_results.no_asset_games + self.dlcs: Dict[str, List[Game]] = self.api_results.dlcs + self.bit32: List[str] = self.api_results.bit32_games + self.mac_games: List[str] = self.api_results.mac_games + self.no_assets: List[Game] = self.api_results.no_asset_games self.game_utils = GameUtils(parent=self) @@ -64,13 +60,10 @@ class GamesTab(QStackedWidget): self.head_bar.goto_eos_ubisoft.connect(self.show_eos_ubisoft) self.games.layout().addWidget(self.head_bar) - self.game_info_tabs = GameInfoTabs(self.dlcs, self.game_utils, self) + self.game_info_tabs = GameInfoTabs(self.game_utils, self) self.game_info_tabs.back_clicked.connect(lambda: self.setCurrentWidget(self.games)) self.addWidget(self.game_info_tabs) - self.game_info_tabs.info.verification_finished.connect( - self.verification_finished - ) self.game_info_tabs.info.uninstalled.connect(lambda x: self.setCurrentWidget(self.games)) self.integrations_tabs = IntegrationsTabs(self) @@ -96,8 +89,6 @@ class GamesTab(QStackedWidget): else: self.no_assets = [] - self.installed = self.core.get_installed_list() - self.view_stack = SlidingStackedWidget(self.games) self.view_stack.setFrameStyle(QFrame.NoFrame) self.icon_view_scroll = QScrollArea(self.view_stack) @@ -116,17 +107,15 @@ class GamesTab(QStackedWidget): self.list_view.setLayout(QVBoxLayout(self.list_view)) self.list_view.layout().setContentsMargins(3, 3, 9, 3) self.list_view.layout().setAlignment(Qt.AlignTop) + self.library_controller = LibraryWidgetController( + self.icon_view, self.list_view, self + ) self.icon_view_scroll.setWidget(self.icon_view) self.list_view_scroll.setWidget(self.list_view) self.view_stack.addWidget(self.icon_view_scroll) self.view_stack.addWidget(self.list_view_scroll) self.games.layout().addWidget(self.view_stack) - # add installing game widget for icon view: List view not supported - self.installing_widget = InstallingGameWidget() - self.icon_view.layout().addWidget(self.installing_widget) - self.installing_widget.setVisible(False) - if not self.settings.value("icon_view", True, bool): self.view_stack.setCurrentWidget(self.list_view_scroll) self.head_bar.view.list() @@ -145,7 +134,7 @@ class GamesTab(QStackedWidget): ) ) self.head_bar.filterChanged.connect(self.filter_games) - self.head_bar.refresh_list.clicked.connect(self.update_list) + 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) @@ -154,40 +143,16 @@ class GamesTab(QStackedWidget): self.active_filter = self.head_bar.available_filters[f] # signals - self.signals.dl_progress.connect(self.installing_widget.set_status) - self.signals.installation_started.connect(self.installation_started) - self.signals.update_gamelist.connect(self.update_list) - self.signals.installation_finished.connect( - self.installation_finished - ) - self.signals.game_uninstalled.connect(lambda name: self.update_list([name])) + self.signals.game.installed.connect(self.update_count_games_label) + self.signals.game.uninstalled.connect(self.update_count_games_label) + self.signals.game.uninstalled.connect(lambda x: self.setCurrentIndex(0)) - self.game_utils.update_list.connect(self.update_list) + # self.signals.update_gamelist.connect(self.library_controller.update_list) + # self.game_utils.update_list.connect(self.library_controller.update_list) + start_t = time.time() self.setup_game_list() - - def installation_finished(self, app_name: str): - self.installing_widget.setVisible(False) - self.installing_widget.set_game("") - self.filter_games("") - - def installation_started(self, app_name: str): - self.installing_widget.set_game(app_name) - - i_widget, l_widget = self.widgets.get(app_name, (None, None)) - if not i_widget or not l_widget: - return - i_widget.setVisible(False) - l_widget.setVisible(False) - - def verification_finished(self, igame: InstalledGame): - # only if igame needs verification - i_widget, l_widget = self.widgets[igame.app_name] - i_widget.igame = igame - l_widget.igame = igame - - i_widget.leaveEvent(None) - l_widget.update_text() + print(f"Game list setup time: {time.time() - start_t}") @pyqtSlot() def show_import(self): @@ -204,93 +169,55 @@ class GamesTab(QStackedWidget): self.setCurrentWidget(self.integrations_tabs) self.integrations_tabs.show_eos_ubisoft() - @pyqtSlot(str) - def show_game_info(self, app_name): - self.game_info_tabs.update_game(app_name) + @pyqtSlot(RareGame) + def show_game_info(self, rgame): + self.game_info_tabs.update_game(rgame) self.setCurrentWidget(self.game_info_tabs) @pyqtSlot() def update_count_games_label(self): self.head_bar.set_games_count(len(self.core.get_installed_list()), len(self.game_list)) + # FIXME: Remove this when RareCore is in place + def __create_game_with_dlcs(self, game: Game) -> RareGame: + rgame = RareGame(game, self.core, self.image_manager) + if rgame.has_update: + self.game_updates.add(rgame) + if game_dlcs := self.dlcs[rgame.game.catalog_item_id]: + for dlc in game_dlcs: + rdlc = RareGame(dlc, self.core, self.image_manager) + if rdlc.has_update: + self.game_updates.add(rdlc) + rdlc.set_pixmap() + rgame.owned_dlcs.append(rdlc) + return rgame + def setup_game_list(self): self.update_count_games_label() - - # add installed games - for igame in sorted(self.core.get_installed_list(), key=lambda x: x.title): - icon_widget, list_widget = self.add_installed_widget(igame.app_name) - self.icon_view.layout().addWidget(icon_widget) - self.list_view.layout().addWidget(list_widget) - - for game in self.no_assets: - if not game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value") == "Origin": - icon_widget, list_widget = self.add_uninstalled_widget(game) - else: - icon_widget, list_widget = self.add_installed_widget(game.app_name) + for game in self.game_list + self.no_assets: + rgame = self.__create_game_with_dlcs(game) + icon_widget, list_widget = self.add_library_widget(rgame) if not icon_widget or not list_widget: - logger.warning(f"Ignoring {game.app_name} in game list") + logger.warning(f"Excluding {rgame.app_name} from the game list") continue self.icon_view.layout().addWidget(icon_widget) self.list_view.layout().addWidget(list_widget) - - for game in sorted(self.game_list, key=lambda x: x.app_title): - if not self.core.is_installed(game.app_name): - self.uninstalled_games.append(game) - icon_widget, list_widget = self.add_uninstalled_widget(game) - self.icon_view.layout().addWidget(icon_widget) - self.list_view.layout().addWidget(list_widget) + rgame.set_pixmap() self.filter_games(self.active_filter) - def add_installed_widget(self, app_name): - pixmap = self.image_manager.get_pixmap(app_name) + def add_library_widget(self, rgame: RareGame): try: - if pixmap.isNull(): - logger.info(f"{app_name} has a corrupt image.") - if app_name in self.no_asset_names and self.core.get_asset(app_name).namespace != "ue": - self.image_manager.download_image_blocking(self.core.get_game(app_name), force=True) - pixmap = self.image_manager.get_pixmap(app_name) - elif self.ue_name: - pixmap = self.image_manager.get_pixmap(self.ue_name) - - icon_widget = InstalledIconWidget(app_name, pixmap, self.game_utils) - list_widget = InstalledListWidget(app_name, pixmap, self.game_utils) + icon_widget, list_widget = self.library_controller.add_game(rgame, self.game_utils, self) except Exception as e: - logger.error(f"{app_name} is broken. Don't add it to game list: {e}") + logger.error(f"{rgame.app_name} is broken. Don't add it to game list: {e}") return None, None - - self.widgets[app_name] = (icon_widget, list_widget) - + self.widgets[rgame.app_name] = (icon_widget, list_widget) icon_widget.show_info.connect(self.show_game_info) list_widget.show_info.connect(self.show_game_info) - - if icon_widget.update_available: - self.updates.add(app_name) - return icon_widget, list_widget - - def add_uninstalled_widget(self, game): - pixmap = self.image_manager.get_pixmap(game.app_name, color=False) - try: - if pixmap.isNull(): - if self.core.get_asset(game.app_name).namespace != "ue": - logger.warning(f"{game.app_title} has a corrupt image. Reloading...") - self.image_manager.download_image_blocking(game, force=True) - pixmap = self.image_manager.get_pixmap(game.app_name, color=False) - elif self.ue_name: - pixmap = self.image_manager.get_pixmap(self.ue_name, color=False) - - icon_widget = UninstalledIconWidget(game, self.core, pixmap) - list_widget = UninstalledListWidget(self.core, game, pixmap) - except Exception as e: - logger.error(f"{game.app_name} is broken. Don't add it to game list: {e}") - return None, None - - icon_widget.show_info.connect(self.show_game_info) - list_widget.show_info.connect(self.show_game_info) - - self.widgets[game.app_name] = (icon_widget, list_widget) - return icon_widget, list_widget + @pyqtSlot(str) + @pyqtSlot(str, str) def filter_games(self, filter_name="all", search_text: str = ""): if not search_text and (t := self.head_bar.search_bar.text()): search_text = t @@ -300,177 +227,7 @@ class GamesTab(QStackedWidget): if not filter_name and (t := self.active_filter): filter_name = t - def get_visibility(widget) -> Tuple[bool, float]: - app_name = widget.game.app_name - - if not isinstance(widget, - InstallingGameWidget) and self.installing_widget.game and self.installing_widget.game.app_name == app_name: - visible = False - elif filter_name == "installed": - visible = self.core.is_installed(app_name) - elif filter_name == "offline": - if self.core.is_installed(app_name) and not isinstance(widget, InstallingGameWidget): - visible = widget.igame.can_run_offline - else: - visible = False - elif filter_name == "32bit" and self.bit32: - visible = app_name in self.bit32 - elif filter_name == "mac" and self.mac_games: - visible = app_name in self.mac_games - elif filter_name == "installable": - visible = app_name not in self.no_asset_names - elif filter_name == "include_ue": - visible = True - elif filter_name == "all": - # All visible except ue - try: - visible = self.core.get_asset(app_name, update=False).namespace != "ue" - except ValueError: - visible = True - else: - visible = True - - if ( - search_text.lower() not in widget.game.app_name.lower() - and search_text.lower() not in widget.game.app_title.lower() - ): - opacity = 0.25 - else: - opacity = 1.0 - return visible, opacity - - for t in self.widgets.values(): - visible, opacity = get_visibility(t[0]) - for w in t: - w.setVisible(visible) - w.image.setOpacity(opacity) - - self.sort_list(search_text) - - if self.installing_widget.game: - self.installing_widget.setVisible(get_visibility(self.installing_widget)[0]) - - @pyqtSlot() - def sort_list(self, sort_by: str = ""): - # lk: this is the existing sorting implemenation - # lk: it sorts by installed then by title - installing_widget = self.icon_view.layout().remove(type(self.installing_widget).__name__) - if sort_by: - self.icon_view.layout().sort(lambda x: (sort_by.lower() not in x.widget().game.app_title.lower(),)) - else: - self.icon_view.layout().sort( - lambda x: ( - not x.widget().is_installed, - x.widget().is_non_asset, - x.widget().app_title, - ) - ) - self.icon_view.layout().insert(0, installing_widget) - list_widgets = self.list_view.findChildren(InstalledListWidget) + self.list_view.findChildren( - UninstalledListWidget) - if sort_by: - list_widgets.sort(key=lambda x: (sort_by not in x.game.app_title.lower(),)) - else: - list_widgets.sort( - key=lambda x: (not x.is_installed, x.is_non_asset, x.app_title) - ) - for idx, wl in enumerate(list_widgets): - self.list_view.layout().insertWidget(idx, wl) - - def __remove_from_layout(self, app_name): - self.icon_view.layout().removeWidget(self.widgets[app_name][0]) - self.list_view.layout().removeWidget(self.widgets[app_name][1]) - self.widgets[app_name][0].deleteLater() - self.widgets[app_name][1].deleteLater() - self.widgets.pop(app_name) - - def __update_installed(self, app_name): - self.__remove_from_layout(app_name) - icon_widget, list_widget = self.add_installed_widget(app_name) - self.icon_view.layout().addWidget(icon_widget) - self.list_view.layout().addWidget(list_widget) - - def __update_uninstalled(self, app_name): - self.__remove_from_layout(app_name) - game = self.core.get_game(app_name, False) - try: - icon_widget, list_widget = self.add_uninstalled_widget(game) - self.icon_view.layout().addWidget(icon_widget) - self.list_view.layout().addWidget(list_widget) - except Exception: - pass - - def update_list(self, app_names: list = None): - logger.debug(f"Updating list for {app_names}") - if app_names: - update_list = False - for app_name in app_names: - if widgets := self.widgets.get(app_name): - - # from update - if self.core.is_installed(widgets[0].game.app_name) and isinstance( - widgets[0], BaseInstalledWidget - ): - logger.debug(f"Update Gamelist: Updated: {app_name}") - igame = self.core.get_installed_game(app_name) - for w in widgets: - w.igame = igame - w.update_available = ( - self.core.get_asset( - w.game.app_name, w.igame.platform, False - ).build_version - != igame.version - ) - widgets[0].leaveEvent(None) - # new installed - elif self.core.is_installed(app_name) and isinstance( - widgets[0], BaseUninstalledWidget - ): - logger.debug(f"Update Gamelist: New installed {app_name}") - self.__update_installed(app_name) - update_list = True - - # uninstalled - elif not self.core.is_installed( - widgets[0].game.app_name - ) and isinstance(widgets[0], BaseInstalledWidget): - logger.debug(f"Update list: Uninstalled: {app_name}") - self.__update_uninstalled(app_name) - update_list = True - - # do not update, if only update finished - if update_list: - self.sort_list() - - else: - installed_names = [i.app_name for i in self.core.get_installed_list()] - # get Uninstalled games - uninstalled_names = [] - games = self.core.get_game_list(True) - for game in sorted(games, key=lambda x: x.app_title): - if not game.app_name in installed_names: - uninstalled_names.append(game.app_name) - - new_installed_games = list( - set(installed_names) - set([i.app_name for i in self.installed]) - ) - new_uninstalled_games = list( - set(uninstalled_names) - - set([i.app_name for i in self.uninstalled_games]) - ) - - if (not new_uninstalled_games) and (not new_installed_games): - return - - if new_installed_games: - for name in new_installed_games: - self.__update_installed(name) - - for name in new_uninstalled_games: - self.__update_uninstalled(name) - - self.sort_list() - self.update_count_games_label() + self.library_controller.filter_list(filter_name, search_text.lower()) def toggle_view(self): self.settings.setValue("icon_view", not self.head_bar.view.isChecked()) diff --git a/rare/components/tabs/games/game_info/__init__.py b/rare/components/tabs/games/game_info/__init__.py index 2c5b0ac6..f1a967e1 100644 --- a/rare/components/tabs/games/game_info/__init__.py +++ b/rare/components/tabs/games/game_info/__init__.py @@ -1,11 +1,11 @@ from typing import Optional -from PyQt5.QtCore import Qt, pyqtSignal +from PyQt5.QtCore import Qt from PyQt5.QtGui import QKeyEvent from PyQt5.QtWidgets import QTreeView -from legendary.models.game import Game from rare.components.tabs.games.game_utils import GameUtils +from rare.models.game import RareGame from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton from rare.utils.extra_widgets import SideTabWidget from rare.utils.json_formatter import QJsonModel @@ -15,7 +15,7 @@ from .game_settings import GameSettings class GameInfoTabs(SideTabWidget): - def __init__(self, dlcs: dict, game_utils: GameUtils, parent=None): + def __init__(self, game_utils: GameUtils, parent=None): super(GameInfoTabs, self).__init__(show_back=True, parent=parent) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() @@ -26,8 +26,7 @@ class GameInfoTabs(SideTabWidget): self.settings = GameSettings(self) self.addTab(self.settings, self.tr("Settings")) - self.dlc_list = dlcs - self.dlc = GameDlc(self.dlc_list, game_utils, self) + self.dlc = GameDlc(game_utils, self) self.addTab(self.dlc, self.tr("Downloadable Content")) self.view = GameMetadataView() @@ -35,20 +34,16 @@ class GameInfoTabs(SideTabWidget): self.tabBar().setCurrentIndex(1) - def update_game(self, app_name: str): - installed = bool(self.core.get_installed_game(app_name)) + def update_game(self, rgame: RareGame): + self.info.update_game(rgame) - self.info.update_game(app_name) + self.settings.load_settings(rgame) + self.settings.setEnabled(rgame.is_installed) - self.settings.load_settings(app_name) - self.settings.setEnabled(installed) + self.dlc.update_dlcs(rgame) + self.dlc.setEnabled(bool(rgame.owned_dlcs)) - self.dlc.setEnabled( - not bool(len(self.dlc_list.get(self.core.get_game(app_name).catalog_item_id, [])) == 0) - ) - self.dlc.update_dlcs(app_name) - - self.view.update_game(self.core.get_game(app_name)) + self.view.update_game(rgame) self.setCurrentIndex(1) @@ -64,14 +59,14 @@ class GameMetadataView(QTreeView): self.setWordWrap(True) self.model = QJsonModel() self.setModel(self.model) - self.game: Optional[Game] = None + self.rgame: Optional[RareGame] = None - def update_game(self, game: Game): - self.game = game - self.title.setTitle(self.game.app_title) + def update_game(self, rgame: RareGame): + self.rgame = rgame + self.title.setTitle(self.rgame.app_title) self.model.clear() try: - self.model.load(game.__dict__) - except: + self.model.load(rgame.game.__dict__) + except Exception as e: pass self.resizeColumnToContents(0) diff --git a/rare/components/tabs/games/game_info/game_dlc.py b/rare/components/tabs/games/game_info/game_dlc.py index cc8b06eb..d300f54d 100644 --- a/rare/components/tabs/games/game_info/game_dlc.py +++ b/rare/components/tabs/games/game_info/game_dlc.py @@ -2,11 +2,11 @@ from typing import Optional from PyQt5.QtCore import pyqtSignal, pyqtSlot from PyQt5.QtWidgets import QFrame, QWidget, QMessageBox -from legendary.models.game import Game from rare.components.tabs.games.game_utils import GameUtils +from rare.models.game import RareGame from rare.models.install import InstallOptionsModel -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ImageManagerSingleton +from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton from rare.shared.image_manager import ImageSize from rare.ui.components.tabs.games.game_info.game_dlc import Ui_GameDlc from rare.ui.components.tabs.games.game_info.game_dlc_widget import Ui_GameDlcWidget @@ -16,27 +16,25 @@ from rare.widgets.image_widget import ImageWidget class GameDlc(QWidget): install_dlc = pyqtSignal(str, bool) - def __init__(self, dlcs: dict, game_utils: GameUtils, parent=None): + def __init__(self, game_utils: GameUtils, parent=None): super(GameDlc, self).__init__(parent=parent) self.ui = Ui_GameDlc() self.ui.setupUi(self) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() - self.game: Optional[Game] = None + self.rgame: Optional[RareGame] = None self.game_utils = game_utils self.ui.available_dlc_scroll.setFrameStyle(QFrame.NoFrame) self.ui.installed_dlc_scroll.setFrameStyle(QFrame.NoFrame) - self.dlcs = dlcs self.installed_dlc_widgets = list() self.available_dlc_widgets = list() - def update_dlcs(self, app_name): - self.game = self.core.get_game(app_name) - dlcs = self.dlcs[self.game.catalog_item_id] - self.title.setTitle(self.game.app_title) + def update_dlcs(self, rgame: RareGame): + self.rgame = rgame + self.title.setTitle(self.rgame.app_title) if self.installed_dlc_widgets: for dlc_widget in self.installed_dlc_widgets: @@ -49,8 +47,8 @@ class GameDlc(QWidget): dlc_widget.deleteLater() self.available_dlc_widgets.clear() - for dlc in sorted(dlcs, key=lambda x: x.app_title): - if self.core.is_installed(dlc.app_name): + for dlc in sorted(self.rgame.owned_dlcs, key=lambda x: x.app_title): + if dlc.is_installed: dlc_widget = GameDlcWidget(dlc, True) self.ui.installed_dlc_contents_layout.addWidget(dlc_widget) dlc_widget.uninstall.connect(self.uninstall) @@ -68,34 +66,29 @@ class GameDlc(QWidget): self.ui.available_dlc_label.setVisible(not self.available_dlc_widgets) self.ui.available_dlc_scroll.setVisible(bool(self.available_dlc_widgets)) - @pyqtSlot(str) - def uninstall(self, app_name): - if self.game_utils.uninstall_game(app_name): - self.update_dlcs(self.game.app_name) + @pyqtSlot(RareGame) + def uninstall(self, rgame: RareGame): + if self.game_utils.uninstall_game(rgame.app_name): + self.update_dlcs(self.rgame) def install(self, app_name): - if not self.core.is_installed(self.game.app_name): + if not self.core.is_installed(self.rgame.app_name): QMessageBox.warning( self, "Error", - self.tr("Base Game is not installed. Please install {} first").format( - self.game.app_title - ), + self.tr("Base Game is not installed. Please install {} first").format(self.rgame.app_title), ) return - self.signals.install_game.emit( - InstallOptionsModel(app_name=app_name, update=True) - ) + self.signals.game.install.emit(InstallOptionsModel(app_name=app_name, update=True)) class GameDlcWidget(QFrame): - install = pyqtSignal(str) # Appname - uninstall = pyqtSignal(str) + install = pyqtSignal(RareGame) + uninstall = pyqtSignal(RareGame) - def __init__(self, dlc: Game, installed: bool, parent=None): + def __init__(self, dlc: RareGame, installed: bool, parent=None): super(GameDlcWidget, self).__init__(parent=parent) - self.image_manager = ImageManagerSingleton() self.ui = Ui_GameDlcWidget() self.ui.setupUi(self) self.dlc = dlc @@ -105,10 +98,10 @@ class GameDlcWidget(QFrame): self.ui.dlc_layout.insertWidget(0, self.image) self.ui.dlc_name.setText(dlc.app_title) - self.ui.version.setText(dlc.app_version()) + self.ui.version.setText(dlc.version) self.ui.app_name.setText(dlc.app_name) - self.image.setPixmap(self.image_manager.get_pixmap(dlc.app_name)) + self.image.setPixmap(dlc.pixmap) if installed: self.ui.action_button.setProperty("uninstall", 1) @@ -122,9 +115,9 @@ class GameDlcWidget(QFrame): def uninstall_dlc(self): self.ui.action_button.setDisabled(True) self.ui.action_button.setText(self.tr("Uninstalling")) - self.uninstall.emit(self.dlc.app_name) + self.uninstall.emit(self.dlc) def install_game(self): self.ui.action_button.setDisabled(True) self.ui.action_button.setText(self.tr("Installing")) - self.install.emit(self.dlc.app_name) + self.install.emit(self.dlc) diff --git a/rare/components/tabs/games/game_info/game_info.py b/rare/components/tabs/games/game_info/game_info.py index 91e19187..75663d21 100644 --- a/rare/components/tabs/games/game_info/game_info.py +++ b/rare/components/tabs/games/game_info/game_info.py @@ -23,6 +23,7 @@ from PyQt5.QtWidgets import ( ) from legendary.models.game import Game, InstalledGame +from rare.models.game import RareGame from rare.models.install import InstallOptionsModel from rare.shared import ( LegendaryCoreSingleton, @@ -313,12 +314,13 @@ class GameInfo(QWidget, Ui_GameInfo): def show_menu_after_browse(self): self.move_button.showMenu() - def update_game(self, app_name: str): - self.game = self.core.get_game(app_name) - self.igame = self.core.get_installed_game(self.game.app_name) + def update_game(self, rgame: RareGame): + # FIXME: Use RareGame for the rest of the code + self.game = rgame.game + self.igame = rgame.igame self.title.setTitle(self.game.app_title) - self.image.setPixmap(self.image_manager.get_pixmap(self.game.app_name, color=True)) + self.image.setPixmap(rgame.pixmap) self.app_name.setText(self.game.app_name) if self.igame: @@ -361,7 +363,7 @@ class GameInfo(QWidget, Ui_GameInfo): self.game_actions_stack.setCurrentIndex(0) try: - is_ue = self.core.get_asset(app_name).namespace == "ue" + is_ue = self.core.get_asset(rgame.app_name).namespace == "ue" except ValueError: is_ue = False grade_visible = not is_ue and platform.system() != "Windows" @@ -403,5 +405,5 @@ class GameInfo(QWidget, Ui_GameInfo): self.move_button.setEnabled(False) self.verify_button.setEnabled(False) - self.move_game_pop_up.update_game(app_name) + self.move_game_pop_up.update_game(rgame.app_name) diff --git a/rare/components/tabs/games/game_info/game_settings.py b/rare/components/tabs/games/game_info/game_settings.py index 3a3628ab..6f44ae15 100644 --- a/rare/components/tabs/games/game_info/game_settings.py +++ b/rare/components/tabs/games/game_info/game_settings.py @@ -8,6 +8,7 @@ from legendary.models.game import Game, InstalledGame from rare.components.tabs.settings import DefaultGameSettings from rare.components.tabs.settings.widgets.pre_launch import PreLaunchSettings +from rare.models.game import RareGame from rare.shared.workers.wine_resolver import WineResolver from rare.utils import config_helper from rare.utils.extra_widgets import PathEdit @@ -133,11 +134,13 @@ class GameSettings(DefaultGameSettings): config_helper.remove_option(self.game.app_name, option) config_helper.save_config() - def load_settings(self, app_name): + def load_settings(self, rgame: RareGame): self.change = False + # FIXME: Use RareGame for the rest of the code + app_name = rgame.app_name super(GameSettings, self).load_settings(app_name) - self.game = self.core.get_game(app_name) - self.igame = self.core.get_installed_game(self.game.app_name) + self.game = rgame.game + self.igame = rgame.igame if self.igame: if self.igame.can_run_offline: offline = self.core.lgd.config.get(self.game.app_name, "offline", fallback="unset") diff --git a/rare/components/tabs/games/game_widgets/__init__.py b/rare/components/tabs/games/game_widgets/__init__.py index e69de29b..2e1928d7 100644 --- a/rare/components/tabs/games/game_widgets/__init__.py +++ b/rare/components/tabs/games/game_widgets/__init__.py @@ -0,0 +1,200 @@ +from typing import Tuple, List, Union, Optional + +from PyQt5.QtCore import QObject, pyqtSlot +from PyQt5.QtWidgets import QWidget + +from rare.components.tabs.games.game_utils import GameUtils +from rare.lgndr.core import LegendaryCore +from rare.models.game import RareGame +from rare.models.signals import GlobalSignals +from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ApiResultsSingleton +from .icon_game_widget import IconGameWidget +from .list_game_widget import ListGameWidget + + +class LibraryWidgetController(QObject): + def __init__(self, icon_container: QWidget, list_container: QWidget, parent: QWidget = None): + super(LibraryWidgetController, self).__init__(parent=parent) + self._icon_container: QWidget = icon_container + self._list_container: QWidget = list_container + self.core: LegendaryCore = LegendaryCoreSingleton() + self.signals: GlobalSignals = GlobalSignalsSingleton() + self.api_results = ApiResultsSingleton() + + self.signals.progress.started.connect(self.start_progress) + self.signals.progress.value.connect(self.update_progress) + self.signals.progress.finished.connect(self.finish_progress) + + self.signals.game.installed.connect(self.on_install) + self.signals.game.uninstalled.connect(self.on_uninstall) + self.signals.game.verified.connect(self.on_verified) + + def add_game(self, rgame: RareGame, game_utils: GameUtils, parent): + return self.add_widgets(rgame, game_utils, parent) + + def add_widgets(self, rgame: RareGame, game_utils: GameUtils, parent) -> Tuple[IconGameWidget, ListGameWidget]: + icon_widget = IconGameWidget(rgame, game_utils, parent) + list_widget = ListGameWidget(rgame, game_utils, parent) + return icon_widget, list_widget + + def __find_widget(self, app_name: str) -> Tuple[Union[IconGameWidget, None], Union[ListGameWidget, None]]: + iw = self._icon_container.findChild(IconGameWidget, app_name) + lw = self._list_container.findChild(ListGameWidget, app_name) + return iw, lw + + @staticmethod + def __visibility(widget, filter_name, search_text) -> Tuple[bool, float]: + if filter_name == "installed": + visible = widget.rgame.is_installed + elif filter_name == "offline": + visible = widget.rgame.can_run_offline + elif filter_name == "32bit": + visible = widget.rgame.is_win32 + elif filter_name == "mac": + visible = widget.rgame.is_mac + elif filter_name == "installable": + visible = not widget.rgame.is_non_asset + elif filter_name == "include_ue": + visible = True + elif filter_name == "all": + visible = not widget.rgame.is_unreal + else: + visible = True + + if ( + search_text not in widget.rgame.app_name.lower() + and search_text not in widget.rgame.app_title.lower() + ): + opacity = 0.25 + else: + opacity = 1.0 + + return visible, opacity + + def filter_list(self, filter_name="all", search_text: str = ""): + icon_widgets = self._icon_container.findChildren(IconGameWidget) + list_widgets = self._list_container.findChildren(ListGameWidget) + for iw in icon_widgets: + visibility, opacity = self.__visibility(iw, filter_name, search_text) + iw.setOpacity(opacity) + iw.setVisible(visibility) + for lw in list_widgets: + visibility, opacity = self.__visibility(lw, filter_name, search_text) + lw.setOpacity(opacity) + lw.setVisible(visibility) + self.sort_list(search_text) + + @pyqtSlot() + def sort_list(self, sort_by: str = ""): + # lk: this is the existing sorting implemenation + # lk: it sorts by installed then by title + if sort_by: + self._icon_container.layout().sort(lambda x: (sort_by not in x.widget().rgame.app_title.lower(),)) + else: + self._icon_container.layout().sort( + lambda x: ( + not x.widget().rgame.is_installed, + x.widget().rgame.is_non_asset, + x.widget().rgame.app_title, + ) + ) + list_widgets = self._list_container.findChildren(ListGameWidget) + if sort_by: + list_widgets.sort(key=lambda x: (sort_by not in x.rgame.app_title.lower(),)) + else: + list_widgets.sort( + key=lambda x: (not x.rgame.is_installed, x.rgame.is_non_asset, x.rgame.app_title) + ) + for idx, wl in enumerate(list_widgets): + self._list_container.layout().insertWidget(idx, wl) + + @pyqtSlot() + @pyqtSlot(list) + def update_list(self, app_names: List[str] = None): + if not app_names: + # lk: base it on icon widgets, the two lists should be identical + icon_widgets = self._icon_container.findChildren(IconGameWidget) + list_widgets = self._list_container.findChildren(ListGameWidget) + icon_app_names = set([iw.rgame.app_name for iw in icon_widgets]) + list_app_names = set([lw.rgame.app_name for lw in list_widgets]) + games = self.api_results.game_list + self.api_results.no_asset_games + game_app_names = set([g.app_name for g in games]) + new_icon_app_names = game_app_names.difference(icon_app_names) + new_list_app_names = game_app_names.difference(list_app_names) + for app_name in new_icon_app_names: + game = self.rare_core.get_game(app_name) + iw = IconGameWidget(game) + self._icon_container.layout().addWidget(iw) + for app_name in new_list_app_names: + game = self.rare_core.get_game(app_name) + lw = ListGameWidget(game) + self._list_container.layout().addWidget(lw) + self.sort_list() + + @pyqtSlot(list) + def on_install(self, app_names: List[str]): + for app_name in app_names: + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.set_installed(True) + if lw is not None: + lw.rgame.set_installed(True) + self.sort_list() + + @pyqtSlot(str) + def on_uninstall(self, app_name: str): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.set_installed(False) + if lw is not None: + lw.rgame.set_installed(False) + self.sort_list() + + @pyqtSlot(str) + def on_verified(self, app_name: str): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.needs_verification = False + if lw is not None: + lw.rgame.needs_verification = False + + # lk: this should go in downloads and happen once + def __find_game_for_dlc(self, app_name: str) -> Optional[str]: + game = self.core.get_game(app_name, False) + # lk: how can an app_name not refer to a game? + if not game: + return None + if game.is_dlc: + game_list = self.core.get_game_list(update_assets=False) + game = list( + filter( + lambda x: x.asset_infos["Windows"].catalog_item_id == game.metadata["mainGameItem"]["id"], + game_list, + ) + ) + return game[0].app_name + return app_name + + @pyqtSlot(str) + def start_progress(self, app_name: str): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.start_progress() + if lw is not None: + lw.rgame.start_progress() + + @pyqtSlot(str, int) + def update_progress(self, app_name: str, progress: int): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.update_progress(progress) + if lw is not None: + lw.rgame.update_progress(progress) + + @pyqtSlot(str, bool) + def finish_progress(self, app_name: str, stopped: bool): + iw, lw = self.__find_widget(app_name) + if iw is not None: + iw.rgame.finish_progress(not stopped, 0, "") + if lw is not None: + lw.rgame.finish_progress(not stopped, 0, "") diff --git a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py b/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py deleted file mode 100644 index a4147fff..00000000 --- a/rare/components/tabs/games/game_widgets/base_uninstalled_widget.py +++ /dev/null @@ -1,72 +0,0 @@ -from logging import getLogger - -from PyQt5.QtCore import pyqtSignal, Qt -from PyQt5.QtWidgets import QFrame, QAction -from legendary.models.game import Game - -from rare.shared import ImageManagerSingleton -from rare.shared.image_manager import ImageSize -from rare.widgets.image_widget import ImageWidget - -logger = getLogger("Uninstalled") - - -class BaseUninstalledWidget(QFrame): - show_info = pyqtSignal(str) - - def __init__(self, game, core, pixmap): - super(BaseUninstalledWidget, self).__init__() - self.image_manager = ImageManagerSingleton() - - self.game = game - if self.game.app_title == "Unreal Engine": - self.game.app_title = f"{self.game.app_title} {self.game.app_name.split('_')[-1]}" - - self.core = core - self.image = ImageWidget(self) - self.image.setFixedSize(ImageSize.Display) - self.image.setPixmap(pixmap) - self.installing = False - self.setContextMenuPolicy(Qt.ActionsContextMenu) - - reload_image = QAction(self.tr("Reload Image"), self) - reload_image.triggered.connect(self.reload_image) - self.addAction(reload_image) - - def reload_image(self): - self.image_manager.download_image_blocking(self.game, force=True) - pm = self.image_manager.get_pixmap(self.game.app_name, color=False) - self.image.setPixmap(pm) - - def install(self): - self.show_info.emit(self.game.app_name) - - # From RareGame, added from sorting to work - @property - def is_non_asset(self) -> bool: - """! - @brief Property to report if a Game doesn't have assets - - Typically, games have assets, however some games that require - other launchers do not have them. Rare treats these games as installed - offering to execute their launcher. - - @return bool If the game doesn't have assets - """ - return not self.game.asset_infos - - @property - def is_installed(self) -> bool: - """! - @brief Property to report if a game is installed - - This returns True if InstalledGame data have been loaded for the game - or if the game is a game without assets, for example an Origin game. - - @return bool If the game should be considered installed - """ - return False or self.is_non_asset - - @property - def app_title(self) -> str: - return self.game.app_title diff --git a/rare/components/tabs/games/game_widgets/base_installed_widget.py b/rare/components/tabs/games/game_widgets/game_widget.py similarity index 55% rename from rare/components/tabs/games/game_widgets/base_installed_widget.py rename to rare/components/tabs/games/game_widgets/game_widget.py index 6281595a..0f0941d7 100644 --- a/rare/components/tabs/games/game_widgets/base_installed_widget.py +++ b/rare/components/tabs/games/game_widgets/game_widget.py @@ -1,40 +1,124 @@ import os import platform from logging import getLogger +from typing import Optional -from PyQt5.QtCore import pyqtSignal, QProcess, QSettings, QStandardPaths, Qt, QByteArray -from PyQt5.QtGui import QPixmap -from PyQt5.QtWidgets import QFrame, QMessageBox, QAction +from PyQt5.QtCore import pyqtSignal, QSettings, QStandardPaths, Qt +from PyQt5.QtGui import QMouseEvent +from PyQt5.QtWidgets import QMessageBox, QAction +from legendary.models.game import Game, InstalledGame from rare.components.tabs.games.game_utils import GameUtils -from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ImageManagerSingleton -from rare.shared.image_manager import ImageSize +from rare.models.game import RareGame +from rare.shared import ( + LegendaryCoreSingleton, + GlobalSignalsSingleton, + ArgumentsSingleton, + ImageManagerSingleton, +) from rare.utils.misc import create_desktop_link -from rare.widgets.image_widget import ImageWidget +from .library_widget import LibraryWidget -logger = getLogger("Game") +logger = getLogger("BaseGameWidget") -class BaseInstalledWidget(QFrame): - launch_signal = pyqtSignal(str, QProcess, list) - show_info = pyqtSignal(str) - finish_signal = pyqtSignal(str, int) - proc: QProcess() +class GameWidget(LibraryWidget): + show_info = pyqtSignal(RareGame) - def __init__(self, app_name, pixmap: QPixmap, game_utils: GameUtils): - super(BaseInstalledWidget, self).__init__() + def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None): + super(GameWidget, self).__init__(parent=parent) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() self.args = ArgumentsSingleton() self.image_manager = ImageManagerSingleton() + self.game_utils = game_utils + self.rgame = rgame + self.rgame.signals.widget.update.connect( + lambda: self.setPixmap(self.rgame.pixmap) + ) + self.rgame.signals.progress.start.connect( + lambda: self.showProgress( + self.image_manager.get_pixmap(self.rgame.app_name, True), + self.image_manager.get_pixmap(self.rgame.app_name, False) + ) + ) + self.rgame.signals.progress.update.connect( + lambda p: self.updateProgress(p) + ) + self.rgame.signals.progress.finish.connect( + lambda e: self.hideProgress(e) + ) + + self.rgame: RareGame = rgame self.syncing_cloud_saves = False + self.game_running = False + + self.settings = QSettings() + + self.installing = False + self.setContextMenuPolicy(Qt.ActionsContextMenu) + launch = QAction(self.tr("Launch"), self) + launch.triggered.connect(self.launch) + self.addAction(launch) + + if self.rgame.game.supports_cloud_saves: + sync = QAction(self.tr("Sync with cloud"), self) + sync.triggered.connect(self.sync_game) + self.addAction(sync) + + desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) + if os.path.exists( + os.path.join(desktop, f"{self.rgame.app_title}.desktop") + ) or os.path.exists(os.path.join(desktop, f"{self.rgame.app_title}.lnk")): + self.create_desktop = QAction(self.tr("Remove Desktop link")) + else: + self.create_desktop = QAction(self.tr("Create Desktop link")) + if self.rgame.is_installed: + self.create_desktop.triggered.connect( + lambda: self.create_desktop_link("desktop") + ) + self.addAction(self.create_desktop) + + applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation) + if platform.system() == "Linux": + start_menu_file = os.path.join(applications, f"{self.rgame.app_title}.desktop") + elif platform.system() == "Windows": + start_menu_file = os.path.join(applications, "..", f"{self.rgame.app_title}.lnk") + else: + start_menu_file = "" + if platform.system() in ["Windows", "Linux"]: + if os.path.exists(start_menu_file): + self.create_start_menu = QAction(self.tr("Remove start menu link")) + else: + self.create_start_menu = QAction(self.tr("Create start menu link")) + if self.rgame.is_installed: + self.create_start_menu.triggered.connect( + lambda: self.create_desktop_link("start_menu") + ) + self.addAction(self.create_start_menu) + + reload_image = QAction(self.tr("Reload Image"), self) + reload_image.triggered.connect(self.reload_image) + self.addAction(reload_image) + + if not self.rgame.is_origin: + uninstall = QAction(self.tr("Uninstall"), self) + self.addAction(uninstall) + uninstall.triggered.connect( + lambda: self.signals.game.uninstalled.emit(self.rgame.app_name) + if self.game_utils.uninstall_game(self.rgame.app_name) + else None + ) + self.texts = { - "needs_verification": self.tr("Please verify game before playing"), + "static": { + "needs_verification": self.tr("Please verify game before playing"), + }, "hover": { - "update_available": self.tr("Start game without version check"), + "update_available": self.tr("Start without version check"), "launch": self.tr("Launch Game"), "launch_origin": self.tr("Launch/Link"), "running": self.tr("Game running"), @@ -49,101 +133,60 @@ class BaseInstalledWidget(QFrame): }, } - self.game = self.core.get_game(app_name) - self.igame = self.core.get_installed_game(app_name) # None if origin - - if self.game.app_title == "Unreal Engine": - self.game.app_title = f"{self.game.app_title} {self.game.app_name.split('_')[-1]}" - - self.is_only_offline = False - - try: - self.core.get_asset(app_name, platform=self.igame.platform).build_version - except ValueError: - logger.warning(f"Game {self.game.app_title} has no metadata. Set offline true") - self.is_only_offline = True - except AttributeError: - pass - - self.image = ImageWidget(self) - self.image.setFixedSize(ImageSize.Display) - self.image.setPixmap(pixmap) - self.game_running = False - self.offline = self.args.offline - self.update_available = False - if self.igame and self.core.lgd.assets: - try: - remote_version = self.core.get_asset( - self.game.app_name, platform=self.igame.platform, update=False - ).build_version - except ValueError: - logger.error(f"Asset error for {self.game.app_title}") - self.update_available = False + @property + def enterEventText(self) -> str: + if self.rgame.is_installed: + if self.game_running: + return self.texts["hover"]["running"] + elif (not self.rgame.is_origin) and self.rgame.needs_verification: + return self.texts["static"]["needs_verification"] + elif self.rgame.is_foreign: + return self.texts["hover"]["launch_offline"] + elif self.rgame.has_update: + return self.texts["hover"]["update_available"] else: - if remote_version != self.igame.version: - self.update_available = True - - self.data = QByteArray() - self.settings = QSettings() - - self.setContextMenuPolicy(Qt.ActionsContextMenu) - launch = QAction(self.tr("Launch"), self) - launch.triggered.connect(self.launch) - self.addAction(launch) - - if self.game.supports_cloud_saves: - sync = QAction(self.tr("Sync with cloud"), self) - sync.triggered.connect(self.sync_game) - self.addAction(sync) - - desktop = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation) - if os.path.exists( - os.path.join(desktop, f"{self.game.app_title}.desktop") - ) or os.path.exists(os.path.join(desktop, f"{self.game.app_title}.lnk")): - self.create_desktop = QAction(self.tr("Remove Desktop link")) + return self.tr("Game Info") + # return self.texts["hover"]["launch" if self.igame else "launch_origin"] else: - self.create_desktop = QAction(self.tr("Create Desktop link")) - if self.igame: - self.create_desktop.triggered.connect( - lambda: self.create_desktop_link("desktop") - ) - self.addAction(self.create_desktop) - - applications = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation) - if platform.system() == "Linux": - start_menu_file = os.path.join(applications, f"{self.game.app_title}.desktop") - elif platform.system() == "Windows": - start_menu_file = os.path.join(applications, "..", f"{self.game.app_title}.lnk") - else: - start_menu_file = "" - if platform.system() in ["Windows", "Linux"]: - if os.path.exists(start_menu_file): - self.create_start_menu = QAction(self.tr("Remove start menu link")) + if not self.installing: + return self.tr("Game Info") else: - self.create_start_menu = QAction(self.tr("Create start menu link")) - if self.igame: - self.create_start_menu.triggered.connect( - lambda: self.create_desktop_link("start_menu") - ) - self.addAction(self.create_start_menu) + return self.tr("Installation running") - reload_image = QAction(self.tr("Reload Image"), self) - reload_image.triggered.connect(self.reload_image) - self.addAction(reload_image) + @property + def leaveEventText(self) -> str: + if self.rgame.is_installed: + if self.game_running: + return self.texts["default"]["running"] + elif self.syncing_cloud_saves: + return self.texts["default"]["syncing"] + elif self.rgame.is_foreign: + return self.texts["default"]["no_meta"] + elif self.rgame.has_update: + return self.texts["default"]["update_available"] + elif (not self.rgame.is_origin) and self.rgame.needs_verification: + return self.texts["static"]["needs_verification"] + else: + return "" + else: + if self.installing: + return "Installation..." + else: + return "" - if self.igame is not None: - uninstall = QAction(self.tr("Uninstall"), self) - self.addAction(uninstall) - uninstall.triggered.connect( - lambda: self.signals.update_gamelist.emit([self.game.app_name]) - if self.game_utils.uninstall_game(self.game.app_name) - else None - ) + def mousePressEvent(self, e: QMouseEvent) -> None: + # left button + if e.button() == 1: + self.show_info.emit(self.rgame) + # right + elif e.button() == 2: + pass # self.showMenu(e) - def reload_image(self): - self.image_manager.download_image_blocking(self.game, force=True) - pm = self.image_manager.get_pixmap(self.game.app_name, color=True) - self.image.setPixmap(pm) + def reload_image(self) -> None: + self.rgame.refresh_pixmap() + + def install(self): + self.show_info.emit(self.rgame) def create_desktop_link(self, type_of_link): if type_of_link == "desktop": @@ -154,9 +197,9 @@ class BaseInstalledWidget(QFrame): return if platform.system() == "Windows": - shortcut_path = os.path.join(shortcut_path, f"{self.game.app_title}.lnk") + shortcut_path = os.path.join(shortcut_path, f"{self.rgame.app_title}.lnk") elif platform.system() == "Linux": - shortcut_path = os.path.join(shortcut_path, f"{self.game.app_title}.desktop") + shortcut_path = os.path.join(shortcut_path, f"{self.rgame.app_title}.desktop") else: QMessageBox.warning( self, @@ -167,7 +210,7 @@ class BaseInstalledWidget(QFrame): if not os.path.exists(shortcut_path): try: - if not create_desktop_link(self.game.app_name, self.core, type_of_link): + if not create_desktop_link(self.rgame.app_name, self.core, type_of_link): return except PermissionError: QMessageBox.warning( @@ -189,16 +232,16 @@ class BaseInstalledWidget(QFrame): def launch(self, offline=False, skip_version_check=False): if self.game_running: return - offline = offline or self.is_only_offline - if self.is_only_offline and not self.igame.can_run_offline: + offline = offline or self.rgame.is_foreign + if self.rgame.is_foreign and not self.rgame.can_run_offline: QMessageBox.warning(self, "Warning", self.tr("This game is probably not in your library and it cannot be launched offline")) return - if self.game.supports_cloud_saves and not offline: + if self.rgame.game.supports_cloud_saves and not offline: self.syncing_cloud_saves = True self.game_utils.prepare_launch( - self.game.app_name, offline, skip_version_check + self.rgame, offline, skip_version_check ) def sync_finished(self, app_name): @@ -207,7 +250,7 @@ class BaseInstalledWidget(QFrame): def sync_game(self): try: sync = self.game_utils.cloud_save_utils.sync_before_launch_game( - self.game.app_name, True + self.rgame.app_name, True ) except Exception: sync = False @@ -218,37 +261,3 @@ class BaseInstalledWidget(QFrame): if error: QMessageBox.warning(self, "Error", error) self.game_running = False - - # From RareGame, added from sorting to work - @property - def is_non_asset(self) -> bool: - """! - @brief Property to report if a Game doesn't have assets - - Typically, games have assets, however some games that require - other launchers do not have them. Rare treats these games as installed - offering to execute their launcher. - - @return bool If the game doesn't have assets - """ - return not self.game.asset_infos - - @property - def is_origin(self) -> bool: - return self.game.metadata.get("customAttributes", {}).get("ThirdPartyManagedApp", {}).get("value") == "Origin" - - @property - def is_installed(self) -> bool: - """! - @brief Property to report if a game is installed - - This returns True if InstalledGame data have been loaded for the game - or if the game is a game without assets, for example an Origin game. - - @return bool If the game should be considered installed - """ - return (self.igame is not None) or self.is_non_asset - - @property - def app_title(self) -> str: - return self.igame.title if self.igame is not None else self.game.app_title diff --git a/rare/components/tabs/games/game_widgets/icon_game_widget.py b/rare/components/tabs/games/game_widgets/icon_game_widget.py new file mode 100644 index 00000000..ffbc6984 --- /dev/null +++ b/rare/components/tabs/games/game_widgets/icon_game_widget.py @@ -0,0 +1,77 @@ +from logging import getLogger + +from PyQt5.QtCore import QEvent, pyqtSignal + +from rare.components.tabs.games.game_utils import GameUtils +from rare.models.game import RareGame +from rare.shared.image_manager import ImageSize +from .game_widget import GameWidget +from .icon_widget import IconWidget + +logger = getLogger("IconGameWidget") + + +class IconGameWidget(GameWidget): + is_ready = False + update_game = pyqtSignal() + + def __init__(self, rgame: RareGame, game_utils: GameUtils, parent=None): + super(IconGameWidget, self).__init__(rgame, game_utils, parent) + self.setObjectName(f"{rgame.app_name}") + self.setFixedSize(ImageSize.Display) + self.ui = IconWidget() + self.ui.setupUi(self) + + self.game_utils.finished.connect(self.game_finished) + + self.ui.title_label.setText(f"