1
0
Fork 0
mirror of synced 2024-07-02 05:01:00 +12:00
Rare/rare/components/tabs/games/game_widgets/game_widget.py
loathingKernel 1721677e33 GameWidget: Implement reactive and interactive labels
The `status_label` displays what is currently going on with the game.
It reflects the current operation running on it or if it requires special
attention (update, needs verification etc)

The `tooltip_label` displays hover information such as what happens
if a part of the widget is clicked or in the case of the launch button if
the game can run (without version check, offline etc)

The context menu on the widgets will be updated and populated according
to the installation state of the game. Since the context menu was revised
the shortcut creation code was revised too to make it more compact.

the `create_desktop_link` and `get_rare_executable` functions are moved
from `rare.utils.misc` to `rare.utils.paths` to avoid cyclical imports and
better grouping. Two functions are added, `desktop_link_path` to uniformly
calculate the path of the shortcut and `desktop_links_supported` which
checks if Rare supports creating shortcuts on the current platform.
`desktop_links_supported` should be used as safeguard before `desktop_link_path`.

Desktop links are currently untested on Windows but if `shortcut.Description`
works as expected, it should be good to go.
2023-02-07 13:41:59 +02:00

284 lines
11 KiB
Python

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