1
0
Fork 0
mirror of synced 2024-06-26 18:20:50 +12:00
Rare/rare/utils/misc.py
loathingKernel 1959516b3f GameWidget: Toggle buttons on widget update
ElidedLabel: Use `sizeHint()` for width

Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com>
2023-02-04 17:38:07 +02:00

362 lines
11 KiB
Python

import os
import platform
import shlex
import sys
from logging import getLogger
from typing import List, Union
import qtawesome
import requests
from PyQt5.QtCore import (
pyqtSignal,
QObject,
QRunnable,
QSettings,
QStandardPaths,
QFile,
QDir, Qt,
)
from PyQt5.QtGui import QPalette, QColor, QImage, QFontMetrics
from PyQt5.QtWidgets import qApp, QStyleFactory, QWidget, QLabel
from legendary.core import LegendaryCore
from legendary.models.game import Game
from requests.exceptions import HTTPError
from rare.models.apiresults import ApiResults
from rare.utils.paths import image_dir, resources_path
if platform.system() == "Windows":
# noinspection PyUnresolvedReferences
from win32com.client import Dispatch # pylint: disable=E0401
logger = getLogger("Utils")
settings = QSettings("Rare", "Rare")
color_role_map = {
0: "WindowText",
1: "Button",
2: "Light",
3: "Midlight",
4: "Dark",
5: "Mid",
6: "Text",
7: "BrightText",
8: "ButtonText",
9: "Base",
10: "Window",
11: "Shadow",
12: "Highlight",
13: "HighlightedText",
14: "Link",
15: "LinkVisited",
16: "AlternateBase",
# 17: "NoRole",
18: "ToolTipBase",
19: "ToolTipText",
20: "PlaceholderText",
# 21: "NColorRoles",
}
color_group_map = {
0: "Active",
1: "Disabled",
2: "Inactive",
}
def load_color_scheme(path: str) -> QPalette:
palette = QPalette()
scheme = QSettings(path, QSettings.IniFormat)
try:
scheme.beginGroup("ColorScheme")
for g in color_group_map:
scheme.beginGroup(color_group_map[g])
group = QPalette.ColorGroup(g)
for r in color_role_map:
role = QPalette.ColorRole(r)
color = scheme.value(color_role_map[r], None)
if color is not None:
palette.setColor(group, role, QColor(color))
else:
palette.setColor(group, role, palette.color(QPalette.Active, role))
scheme.endGroup()
scheme.endGroup()
except:
palette = None
return palette
def set_color_pallete(color_scheme: str):
if not color_scheme:
qApp.setStyle(QStyleFactory.create(qApp.property("rareDefaultQtStyle")))
qApp.setStyleSheet("")
qApp.setPalette(qApp.style().standardPalette())
return
qApp.setStyle(QStyleFactory.create("Fusion"))
custom_palette = load_color_scheme(f":/schemes/{color_scheme}")
if custom_palette is not None:
qApp.setPalette(custom_palette)
icon_color = qApp.palette().color(QPalette.Foreground).name()
qtawesome.set_defaults(color=icon_color)
def get_color_schemes() -> List[str]:
colors = []
for file in QDir(":/schemes"):
colors.append(file)
return colors
def set_style_sheet(style_sheet: str):
if not style_sheet:
qApp.setStyle(QStyleFactory.create(qApp.property("rareDefaultQtStyle")))
qApp.setStyleSheet("")
return
qApp.setStyle(QStyleFactory.create("Fusion"))
file = QFile(f":/stylesheets/{style_sheet}/stylesheet.qss")
file.open(QFile.ReadOnly)
stylesheet = file.readAll().data().decode("utf-8")
qApp.setStyleSheet(stylesheet)
icon_color = qApp.palette().color(QPalette.Text).name()
qtawesome.set_defaults(color="#eeeeee")
def get_style_sheets() -> List[str]:
styles = []
for file in QDir(":/stylesheets/"):
styles.append(file)
return styles
def get_translations():
langs = ["en"]
for i in os.listdir(os.path.join(resources_path, "languages")):
if i.endswith(".qm") and not i.startswith("qt_"):
langs.append(i.split(".")[0])
return langs
def get_latest_version():
try:
resp = requests.get(
"https://api.github.com/repos/Dummerle/Rare/releases/latest"
)
tag = resp.json()["tag_name"]
return tag
except requests.exceptions.ConnectionError:
return "0.0.0"
def get_size(b: Union[int, float]) -> str:
for i in ["", "K", "M", "G", "T", "P", "E"]:
if b < 1024:
return f"{b:.2f}{i}B"
b /= 1024
def get_rare_executable() -> List[str]:
# lk: detect if nuitka
if "__compiled__" in globals():
executable = [sys.executable]
elif platform.system() == "Linux" or platform.system() == "Darwin":
if p := os.environ.get("APPIMAGE"):
executable = [p]
else:
if sys.executable == os.path.abspath(sys.argv[0]):
executable = [sys.executable]
else:
executable = [sys.executable, os.path.abspath(sys.argv[0])]
elif platform.system() == "Windows":
executable = [sys.executable]
if sys.executable != os.path.abspath(sys.argv[0]):
executable.append(os.path.abspath(sys.argv[0]))
if executable[0].endswith("python.exe"):
# be sure to start consoleless then
executable[0] = executable[0].replace("python.exe", "pythonw.exe")
if executable[1].endswith("rare"):
executable[1] = executable[1] + ".exe"
else:
executable = [sys.executable]
executable[0] = os.path.abspath(executable[0])
return executable
def create_desktop_link(app_name=None, core: LegendaryCore = None, type_of_link="desktop",
for_rare: bool = False) -> bool:
if not for_rare:
igame = core.get_installed_game(app_name)
icon = os.path.join(os.path.join(image_dir(), igame.app_name, "installed.png"))
icon = icon.replace(".png", "")
if platform.system() == "Linux":
if type_of_link == "desktop":
path = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
elif type_of_link == "start_menu":
path = QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation)
else:
return False
if not os.path.exists(path):
return False
executable = get_rare_executable()
executable = shlex.join(executable)
if for_rare:
with open(os.path.join(path, "Rare.desktop"), "w") as desktop_file:
desktop_file.write(
"[Desktop Entry]\n"
f"Name=Rare\n"
f"Type=Application\n"
f"Categories=Game;\n"
f"Icon={os.path.join(resources_path, 'images', 'Rare.png')}\n"
f"Exec={executable}\n"
"Terminal=false\n"
"StartupWMClass=Rare\n"
)
else:
with open(os.path.join(path, f"{igame.title}.desktop"), "w") as desktop_file:
desktop_file.write(
"[Desktop Entry]\n"
f"Name={igame.title}\n"
f"Type=Application\n"
f"Categories=Game;\n"
f"Icon={icon}.png\n"
f"Exec={executable} launch {app_name}\n"
"Terminal=false\n"
"StartupWMClass=Rare\n"
)
os.chmod(os.path.join(path, f"{igame.title}.desktop"), 0o755)
return True
elif platform.system() == "Windows":
# Target of shortcut
if type_of_link == "desktop":
target_folder = QStandardPaths.writableLocation(QStandardPaths.DesktopLocation)
elif type_of_link == "start_menu":
target_folder = os.path.join(
QStandardPaths.writableLocation(QStandardPaths.ApplicationsLocation),
".."
)
else:
logger.warning("No valid type of link")
return False
if not os.path.exists(target_folder):
return False
if for_rare:
linkName = "Rare.lnk"
else:
linkName = igame.title
# TODO: this conversion is not applied everywhere (see base_installed_widget), should it?
for c in r'<>?":|\/*':
linkName = linkName.replace(c, "")
linkName = f"{linkName.strip()}.lnk"
# Path to location of link file
pathLink = os.path.join(target_folder, linkName)
# Add shortcut
shell = Dispatch("WScript.Shell")
shortcut = shell.CreateShortCut(pathLink)
executable = get_rare_executable()
arguments = []
if len(executable) > 1:
arguments.extend(executable[1:])
executable = executable[0]
if not for_rare:
arguments.extend(["launch", app_name])
shortcut.Targetpath = executable
# Maybe there is a better solution, but windows does not accept single quotes (Windows is weird)
shortcut.Arguments = shlex.join(arguments).replace("'", '"')
if for_rare:
shortcut.WorkingDirectory = QStandardPaths.writableLocation(QStandardPaths.HomeLocation)
# Icon
if for_rare:
icon_location = os.path.join(resources_path, "images", "Rare.ico")
else:
if not os.path.exists(f"{icon}.ico"):
img = QImage()
img.load(f"{icon}.png")
img.save(f"{icon}.ico")
logger.info("Created ico file")
icon_location = f"{icon}.ico"
shortcut.IconLocation = os.path.abspath(icon_location)
shortcut.save()
return True
# mac OS is based on Darwin
elif platform.system() == "Darwin":
return False
class CloudWorker(QRunnable):
class Signals(QObject):
# List[SaveGameFile]
result_ready = pyqtSignal(list)
def __init__(self, core: LegendaryCore):
super(CloudWorker, self).__init__()
self.core = core
self.signals = CloudWorker.Signals()
self.setAutoDelete(True)
def run(self) -> None:
try:
result = self.core.get_save_games()
except HTTPError:
result = None
self.signals.result_ready.emit(result)
def get_raw_save_path(game: Game):
if game.supports_cloud_saves:
return (
game.metadata.get("customAttributes", {})
.get("CloudSaveFolder", {})
.get("value")
)
def get_default_platform(app_name, api_results: ApiResults):
if platform.system() != "Darwin" or app_name not in api_results.mac_games:
return "Windows"
else:
return "Mac"
def icon(icn_str: str, fallback: str = None, **kwargs):
try:
return qtawesome.icon(icn_str, **kwargs)
except Exception as e:
if not fallback:
logger.warning(f"{e} {icn_str}")
if fallback:
try:
return qtawesome.icon(fallback, **kwargs)
except Exception as e:
logger.error(str(e))
if kwargs.get("color"):
kwargs["color"] = "red"
return qtawesome.icon("ei.error", **kwargs)
def widget_object_name(widget: QWidget, app_name: str) -> str:
return f"{type(widget).__name__}_{app_name}"
def elide_text(label: QLabel, text: str) -> str:
metrics = QFontMetrics(label.font())
return metrics.elidedText(text, Qt.ElideRight, label.sizeHint().width())