import platform from abc import ABCMeta from logging import getLogger from PyQt5.QtCore import pyqtSignal, Qt, pyqtSlot, QObject, QEvent from PyQt5.QtGui import QMouseEvent from PyQt5.QtWidgets import QMessageBox, QAction, QLabel, QPushButton from rare.models.game import RareGame from rare.shared import ( LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton, ImageManagerSingleton, ) from rare.utils.paths import desktop_links_supported, desktop_link_path, create_desktop_link from .library_widget import LibraryWidget logger = getLogger("GameWidget") class GameWidgetUi(metaclass=ABCMeta): status_label: QLabel install_btn: QPushButton launch_btn: QPushButton tooltip_label: QLabel class GameWidget(LibraryWidget): show_info = pyqtSignal(RareGame) def __init__(self, rgame: RareGame, parent=None): super(GameWidget, self).__init__(parent=parent) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() self.args = ArgumentsSingleton() self.image_manager = ImageManagerSingleton() self.rgame: RareGame = rgame self.setContextMenuPolicy(Qt.ActionsContextMenu) self.launch_action = QAction(self.tr("Launch"), self) self.launch_action.triggered.connect(self._launch) self.install_action = QAction(self.tr("Install"), self) self.install_action.triggered.connect(self._install) # self.sync_action = QAction(self.tr("Sync with cloud"), self) # self.sync_action.triggered.connect(self.sync_saves) self.desktop_link_action = QAction(self) self.desktop_link_action.triggered.connect( lambda: self._create_link(self.rgame.folder_name, "desktop") ) self.menu_link_action = QAction(self) self.menu_link_action.triggered.connect( lambda: self._create_link(self.rgame.folder_name, "start_menu") ) self.reload_action = QAction(self.tr("Reload Image"), self) self.reload_action.triggered.connect(self._on_reload_image) self.uninstall_action = QAction(self.tr("Uninstall"), self) self.uninstall_action.triggered.connect(self._uninstall) self.update_actions() # signals self.rgame.signals.widget.update.connect(lambda: self.setPixmap(self.rgame.pixmap)) self.rgame.signals.widget.update.connect(self.update_buttons) self.rgame.signals.widget.update.connect(self.update_state) self.rgame.signals.game.installed.connect(self.update_actions) self.rgame.signals.game.uninstalled.connect(self.update_actions) 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.state_strings = { RareGame.State.IDLE: "", RareGame.State.RUNNING: self.tr("Running..."), RareGame.State.DOWNLOADING: self.tr("Downloading..."), RareGame.State.VERIFYING: self.tr("Verifying..."), RareGame.State.MOVING: self.tr("Moving..."), RareGame.State.UNINSTALLING: self.tr("Uninstalling..."), "has_update": self.tr("Update available"), "needs_verification": self.tr("Needs verification"), "not_can_launch": self.tr("Can't launch"), } self.hover_strings = { "info": self.tr("Show infromation"), "install": self.tr("Install game"), "can_launch": self.tr("Launch game"), "is_foreign": self.tr("Launch offline"), "has_update": self.tr("Launch without version check"), "is_origin": self.tr("Launch/Link"), "not_can_launch": self.tr("Can't launch"), } # lk: abstract class for typing, the `self.ui` attribute should be used # lk: by the Ui class in the children. It must contain at least the same # lk: attributes as `GameWidgetUi` class self.ui = GameWidgetUi() @pyqtSlot() def update_state(self): if self.rgame.is_idle: if self.rgame.has_update: self.ui.status_label.setText(self.state_strings["has_update"]) elif self.rgame.needs_verification: self.ui.status_label.setText(self.state_strings["needs_verification"]) elif not self.rgame.can_launch and self.rgame.is_installed: self.ui.status_label.setText(self.state_strings["not_can_launch"]) else: self.ui.status_label.setText(self.state_strings[self.rgame.state]) else: self.ui.status_label.setText(self.state_strings[self.rgame.state]) self.ui.status_label.setVisible(bool(self.ui.status_label.text())) @pyqtSlot() def update_buttons(self): self.ui.install_btn.setVisible(not self.rgame.is_installed) self.ui.install_btn.setEnabled(not self.rgame.is_installed) self.ui.launch_btn.setVisible(self.rgame.is_installed) self.ui.launch_btn.setEnabled(self.rgame.can_launch) @pyqtSlot() def update_actions(self): for action in self.actions(): self.removeAction(action) if self.rgame.is_installed or self.rgame.is_origin: self.addAction(self.launch_action) else: self.addAction(self.install_action) # if self.rgame.game.supports_cloud_saves: # self.addAction(self.sync_action) if desktop_links_supported() and self.rgame.is_installed: if desktop_link_path(self.rgame.folder_name, "desktop").exists(): self.desktop_link_action.setText(self.tr("Remove Desktop link")) else: self.desktop_link_action.setText(self.tr("Create Desktop link")) self.addAction(self.desktop_link_action) if desktop_link_path(self.rgame.folder_name, "start_menu").exists(): self.menu_link_action.setText(self.tr("Remove Menu link")) else: self.menu_link_action.setText(self.tr("Create Menu link")) self.addAction(self.menu_link_action) self.addAction(self.reload_action) if self.rgame.is_installed and not self.rgame.is_origin: self.addAction(self.uninstall_action) def eventFilter(self, a0: QObject, a1: QEvent) -> bool: if a0 is self.ui.launch_btn: if a1.type() == QEvent.Enter: if not self.rgame.can_launch: self.ui.tooltip_label.setText(self.hover_strings["not_can_launch"]) elif self.rgame.is_origin: self.ui.tooltip_label.setText(self.hover_strings["is_origin"]) elif self.rgame.has_update: self.ui.tooltip_label.setText(self.hover_strings["has_update"]) elif self.rgame.is_foreign and self.rgame.can_run_offline: self.ui.tooltip_label.setText(self.hover_strings["is_foreign"]) elif self.rgame.can_launch: self.ui.tooltip_label.setText(self.hover_strings["can_launch"]) return True if a1.type() == QEvent.Leave: self.ui.tooltip_label.setText(self.hover_strings["info"]) # return True if a0 is self.ui.install_btn: if a1.type() == QEvent.Enter: self.ui.tooltip_label.setText(self.hover_strings["install"]) return True if a1.type() == QEvent.Leave: self.ui.tooltip_label.setText(self.hover_strings["info"]) # return True if a0 is self: if a1.type() == QEvent.Enter: self.ui.tooltip_label.setText(self.hover_strings["info"]) return super().eventFilter(a0, a1) def mousePressEvent(self, e: QMouseEvent) -> None: # left button if e.button() == 1: self.show_info.emit(self.rgame) # right elif e.button() == 2: super(GameWidget, self).mousePressEvent(e) @pyqtSlot() def _on_reload_image(self) -> None: self.rgame.refresh_pixmap() @pyqtSlot() @pyqtSlot(bool, bool) def _launch(self, offline=False, skip_version_check=False): if offline or (self.rgame.is_foreign and self.rgame.can_run_offline): offline = True # if self.rgame.game.supports_cloud_saves and not offline: # self.syncing_cloud_saves = True if self.rgame.has_update: skip_version_check = True self.rgame.launch( offline=offline, skip_update_check=skip_version_check ) @pyqtSlot() def _install(self): self.show_info.emit(self.rgame) @pyqtSlot() def _uninstall(self): self.show_info.emit(self.rgame) def _create_link(self, name, link_type): if not desktop_links_supported(): QMessageBox.warning( self, self.tr("Warning"), self.tr("Creating shortcuts is currently unsupported on {}").format(platform.system()), ) return shortcut_path = desktop_link_path(name, link_type) if not shortcut_path.exists(): try: if not create_desktop_link( app_name=self.rgame.app_name, app_title=self.rgame.app_title, link_name=self.rgame.folder_name, link_type=link_type, ): raise PermissionError except PermissionError: QMessageBox.warning(self, "Error", "Could not create shortcut.") return if link_type == "desktop": self.desktop_link_action.setText(self.tr("Remove Desktop link")) elif link_type == "start_menu": self.menu_link_action.setText(self.tr("Remove Start Menu link")) else: if shortcut_path.exists(): shortcut_path.unlink(missing_ok=True) if link_type == "desktop": self.desktop_link_action.setText(self.tr("Create Desktop link")) elif link_type == "start_menu": self.menu_link_action.setText(self.tr("Create Start Menu link")) # def sync_finished(self, app_name): # self.syncing_cloud_saves = False # def sync_game(self): # try: # sync = self.game_utils.cloud_save_utils.sync_before_launch_game( # self.rgame.app_name, True # ) # except Exception: # sync = False # if sync: # self.syncing_cloud_saves = True # def game_finished(self, app_name, error): # if error: # QMessageBox.warning(self, "Error", error) # self.game_running = False