From 1c296474c5d8722d3e480af60a3ab9483925f8b5 Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Wed, 4 May 2022 23:11:41 +0300 Subject: [PATCH 1/4] Add a bunch of accumulated fixes. Shared: Require an argument to initialize the each singleton, if it is called uninitialized, raise a RuntimeError InstallDialog: Use QCheckBox label for the information text and remove the layout LaunchDialog: Minor code clarity improvements Console: add a Dialog with the process's environment variables GameUtils: Inherit the system's environment and not a clean one ImportGroup: Add the ability to automatically import all games in a folder RareStyle: Use rgb values, remove hex codes and rgba values IndicatorLineEdit/PathEdit: Infer object names from class name, don't override layout method Models: Type fields as Optional (`Union[, None]`) Paths: Use pathlib for everything Signed-off-by: Stelios Tsampas --- rare/app.py | 6 +- rare/components/dialogs/install_dialog.py | 2 - rare/components/dialogs/launch_dialog.py | 31 +- rare/components/extra/console.py | 103 +++++- rare/components/tabs/games/game_utils.py | 6 +- rare/components/tabs/games/head_bar.py | 27 +- .../tabs/games/import_sync/import_group.py | 113 ++++--- rare/components/tabs/settings/widgets/eos.py | 8 +- rare/resources/resources.py | Bin 1030089 -> 1030939 bytes .../stylesheets/RareStyle/stylesheet.qss | 296 ++++++++++-------- rare/shared.py | 26 +- rare/ui/components/dialogs/install_dialog.py | 65 ++-- rare/ui/components/dialogs/install_dialog.ui | 103 +++--- .../components/dialogs/login/login_dialog.py | 23 +- .../components/dialogs/login/login_dialog.ui | 25 +- .../tabs/games/game_info/game_info.py | 1 - .../tabs/games/game_info/game_info.ui | 3 - .../tabs/games/import_sync/import_group.py | 33 +- .../tabs/games/import_sync/import_group.ui | 33 +- rare/utils/extra_widgets.py | 95 +++--- rare/utils/legendary_utils.py | 13 + rare/utils/models.py | 20 +- rare/utils/paths.py | 18 +- setup.py | 4 +- 24 files changed, 594 insertions(+), 460 deletions(-) diff --git a/rare/app.py b/rare/app.py index a8db08dd..d78fd3b5 100644 --- a/rare/app.py +++ b/rare/app.py @@ -68,7 +68,7 @@ class App(QApplication): # init Legendary try: - self.core = LegendaryCoreSingleton() + self.core = LegendaryCoreSingleton(init=True) except configparser.MissingSectionHeaderError as e: logger.warning(f"Config is corrupt: {e}") if config_path := os.environ.get("XDG_CONFIG_HOME"): @@ -77,7 +77,7 @@ class App(QApplication): path = os.path.expanduser("~/.config/legendary") with open(os.path.join(path, "config.ini"), "w") as config_file: config_file.write("[Legendary]") - self.core = LegendaryCoreSingleton() + self.core = LegendaryCoreSingleton(init=True) if "Legendary" not in self.core.lgd.config.sections(): self.core.lgd.config.add_section("Legendary") self.core.lgd.save_config() @@ -101,7 +101,7 @@ class App(QApplication): self.setOrganizationName("Rare") self.settings = QSettings() - self.signals = GlobalSignalsSingleton() + self.signals = GlobalSignalsSingleton(init=True) self.signals.exit_app.connect(self.exit_app) self.signals.send_notification.connect( diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py index ca3496bd..00aefdb4 100644 --- a/rare/components/dialogs/install_dialog.py +++ b/rare/components/dialogs/install_dialog.py @@ -141,11 +141,9 @@ class InstallDialog(QDialog, Ui_InstallDialog): if self.dl_item.options.overlay: self.platform_label.setVisible(False) self.platform_combo_box.setVisible(False) - self.ignore_space_info_label.setVisible(False) self.ignore_space_check.setVisible(False) self.ignore_space_label.setVisible(False) self.download_only_check.setVisible(False) - self.download_only_info_label.setVisible(False) self.download_only_label.setVisible(False) self.shortcut_cb.setVisible(False) self.shortcut_lbl.setVisible(False) diff --git a/rare/components/dialogs/launch_dialog.py b/rare/components/dialogs/launch_dialog.py index 4a903dfe..b79acdc1 100644 --- a/rare/components/dialogs/launch_dialog.py +++ b/rare/components/dialogs/launch_dialog.py @@ -23,11 +23,11 @@ class LaunchDialogSignals(QObject): class ImageWorker(QRunnable): - def __init__(self, core: LegendaryCore): + def __init__(self): super(ImageWorker, self).__init__() self.signals = LaunchDialogSignals() self.setAutoDelete(True) - self.core = core + self.core = LegendaryCoreSingleton() def run(self): download_images(self.signals.image_progress, self.signals.result, self.core) @@ -45,16 +45,16 @@ class ApiRequestWorker(QRunnable): def run(self) -> None: if self.settings.value("mac_meta", platform.system() == "Darwin", bool): try: - result = self.core.get_game_and_dlc_list(True, "Mac") + result = self.core.get_game_and_dlc_list(update_assets=True, platform="Mac") except HTTPError: result = [], {} - self.signals.result.emit(result, "mac") else: - self.signals.result.emit(([], {}), "mac") + result = [], {} + self.signals.result.emit(result, "mac") if self.settings.value("win32_meta", False, bool): try: - result = self.core.get_game_and_dlc_list(True, "Win32") + result = self.core.get_game_and_dlc_list(update_assets=True, platform="Win32") except HTTPError: result = [], {} else: @@ -65,7 +65,7 @@ class ApiRequestWorker(QRunnable): class LaunchDialog(QDialog, Ui_LaunchDialog): quit_app = pyqtSignal(int) start_app = pyqtSignal() - finished = 0 + completed = 0 def __init__(self, parent=None): super(LaunchDialog, self).__init__(parent=parent) @@ -76,8 +76,7 @@ class LaunchDialog(QDialog, Ui_LaunchDialog): self.core = LegendaryCoreSingleton() self.args = ArgumentsSingleton() - self.thread_pool = QThreadPool() - self.thread_pool.setMaxThreadCount(2) + self.thread_pool = QThreadPool().globalInstance() self.api_results = ApiResults() def login(self): @@ -113,15 +112,15 @@ class LaunchDialog(QDialog, Ui_LaunchDialog): if not self.args.offline: self.image_info.setText(self.tr("Downloading Images")) - image_worker = ImageWorker(self.core) + image_worker = ImageWorker() image_worker.signals.image_progress.connect(self.update_image_progbar) image_worker.signals.result.connect(self.handle_api_worker_result) self.thread_pool.start(image_worker) # gamelist and no_asset games are from Image worker - worker = ApiRequestWorker() - worker.signals.result.connect(self.handle_api_worker_result) - self.thread_pool.start(worker) + api_worker = ApiRequestWorker() + api_worker.signals.result.connect(self.handle_api_worker_result) + self.thread_pool.start(api_worker) # cloud save from another worker, because it is used in cloud_save_utils too cloud_worker = CloudWorker() @@ -131,7 +130,7 @@ class LaunchDialog(QDialog, Ui_LaunchDialog): self.thread_pool.start(cloud_worker) else: - self.finished = 2 + self.completed = 2 if self.core.lgd.assets: ( self.api_results.game_list, @@ -183,10 +182,10 @@ class LaunchDialog(QDialog, Ui_LaunchDialog): self.finish() def finish(self): - if self.finished == 1: + if self.completed == 1: logger.info("App starting") self.image_info.setText(self.tr("Starting...")) ApiResultsSingleton(self.api_results) self.start_app.emit() else: - self.finished += 1 + self.completed += 1 diff --git a/rare/components/extra/console.py b/rare/components/extra/console.py index 70551efb..cefaf0ec 100644 --- a/rare/components/extra/console.py +++ b/rare/components/extra/console.py @@ -1,32 +1,50 @@ +from PyQt5.QtCore import Qt, QCoreApplication, QMetaObject, QProcessEnvironment from PyQt5.QtGui import QTextCursor, QFont from PyQt5.QtWidgets import ( QPlainTextEdit, - QWidget, + QDialog, QPushButton, QFileDialog, QVBoxLayout, + QHBoxLayout, + QSpacerItem, + QSizePolicy, QTableWidget, QTableWidgetItem, QAbstractItemView, QDialogButtonBox, QHeaderView, ) -class ConsoleWindow(QWidget): - def __init__(self): - super(ConsoleWindow, self).__init__() - self.layout = QVBoxLayout() +class ConsoleWindow(QDialog): + env: QProcessEnvironment + + def __init__(self, parent=None): + super(ConsoleWindow, self).__init__(parent=parent) self.setWindowTitle("Rare Console") self.setGeometry(0, 0, 600, 400) + layout = QVBoxLayout() - self.console = Console() - self.layout.addWidget(self.console) + self.console = Console(self) + layout.addWidget(self.console) + + button_layout = QHBoxLayout() + button_layout.addItem(QSpacerItem(0, 0, QSizePolicy.Expanding, QSizePolicy.Fixed)) + + self.env_button = QPushButton(self.tr("Show environment")) + button_layout.addWidget(self.env_button) + self.env_button.clicked.connect(self.show_env) self.save_button = QPushButton(self.tr("Save output to file")) - self.layout.addWidget(self.save_button) + button_layout.addWidget(self.save_button) self.save_button.clicked.connect(self.save) - self.clear_button = QPushButton(self.tr("Clear")) - self.layout.addWidget(self.clear_button) + self.clear_button = QPushButton(self.tr("Clear console")) + button_layout.addWidget(self.clear_button) self.clear_button.clicked.connect(self.console.clear) - self.setLayout(self.layout) + layout.addLayout(button_layout) + + self.setLayout(layout) + + self.env_variables = EnvVariables(self) + self.env_variables.hide() def save(self): file, ok = QFileDialog.getSaveFileName( @@ -40,6 +58,13 @@ class ConsoleWindow(QWidget): f.close() self.save_button.setText(self.tr("Saved")) + def set_env(self, env: QProcessEnvironment): + self.env = env + + def show_env(self): + self.env_variables.setTable(self.env) + self.env_variables.show() + def log(self, text: str, end: str = "\n"): self.console.log(text + end) @@ -47,9 +72,61 @@ class ConsoleWindow(QWidget): self.console.error(text + end) +class EnvVariables(QDialog): + + class Ui(object): + def __init__(self, container): + layout = QVBoxLayout() + self.table = QTableWidget(container) + self.table.setColumnCount(2) + + self.table.setHorizontalHeaderItem(0, QTableWidgetItem()) + self.table.setHorizontalHeaderItem(1, QTableWidgetItem()) + font = QFont() + font.setFamily(u"Monospace") + self.table.setFont(font) + self.table.setEditTriggers(QAbstractItemView.NoEditTriggers) + self.table.setAlternatingRowColors(True) + self.table.setSelectionBehavior(QAbstractItemView.SelectRows) + self.table.setSortingEnabled(True) + self.table.setCornerButtonEnabled(True) + self.table.horizontalHeader().setVisible(True) + self.table.horizontalHeader().setStretchLastSection(True) + self.table.verticalHeader().setVisible(False) + self.table.horizontalHeaderItem(0).setText(container.tr("Variable")) + self.table.horizontalHeaderItem(1).setText(container.tr("Value")) + layout.addWidget(self.table) + + self.buttons = QDialogButtonBox(container) + self.buttons.setOrientation(Qt.Horizontal) + self.buttons.setStandardButtons(QDialogButtonBox.Close) + self.buttons.accepted.connect(container.accept) + self.buttons.rejected.connect(container.reject) + layout.addWidget(self.buttons) + + container.setLayout(layout) + container.setObjectName(type(self).__name__) + container.setWindowTitle("Rare Console Environment") + container.setGeometry(0, 0, 600, 400) + + def __init__(self, parent=None): + super(EnvVariables, self).__init__(parent=parent) + self.ui = EnvVariables.Ui(self) + + def setTable(self, env: QProcessEnvironment): + self.ui.table.clearContents() + self.ui.table.setRowCount(len(env.keys())) + + for idx, key in enumerate(env.keys()): + self.ui.table.setItem(idx, 0, QTableWidgetItem(env.keys()[idx])) + self.ui.table.setItem(idx, 1, QTableWidgetItem(env.value(env.keys()[idx]))) + + self.ui.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) + + class Console(QPlainTextEdit): - def __init__(self): - super().__init__() + def __init__(self, parent=None): + super(Console, self).__init__(parent=parent) self.setReadOnly(True) self.setFont(QFont("monospace")) self._cursor_output = self.textCursor() diff --git a/rare/components/tabs/games/game_utils.py b/rare/components/tabs/games/game_utils.py index f6412334..faafa6cf 100644 --- a/rare/components/tabs/games/game_utils.py +++ b/rare/components/tabs/games/game_utils.py @@ -245,7 +245,7 @@ class GameUtils(QObject): def _launch_pre_command(self, env: dict): proc = QProcess() - environment = QProcessEnvironment() + environment = QProcessEnvironment().systemEnvironment() for e in env: environment.insert(e, env[e]) proc.setProcessEnvironment(environment) @@ -260,12 +260,13 @@ class GameUtils(QObject): str(proc.readAllStandardError().data(), "utf-8", "ignore") ) ) + self.console.set_env(environment) return proc def _get_process(self, app_name, env): process = GameProcess(app_name) - environment = QProcessEnvironment() + environment = QProcessEnvironment().systemEnvironment() for e in env: environment.insert(e, env[e]) process.setProcessEnvironment(environment) @@ -287,6 +288,7 @@ class GameUtils(QObject): and QSettings().value("show_console", False, bool)) else None ) + self.console.set_env(environment) return process def _launch_origin(self, app_name, process: QProcess): diff --git a/rare/components/tabs/games/head_bar.py b/rare/components/tabs/games/head_bar.py index ba104333..77085442 100644 --- a/rare/components/tabs/games/head_bar.py +++ b/rare/components/tabs/games/head_bar.py @@ -19,7 +19,6 @@ class GameListHeadBar(QWidget): def __init__(self, parent=None): super(GameListHeadBar, self).__init__(parent=parent) self.api_results = ApiResultsSingleton() - self.setLayout(QHBoxLayout()) # self.installed_only = QCheckBox(self.tr("Installed only")) self.settings = QSettings() # self.installed_only.setChecked(self.settings.value("installed_only", False, bool)) @@ -32,7 +31,6 @@ class GameListHeadBar(QWidget): self.tr("Installed only"), self.tr("Offline Games"), ]) - self.layout().addWidget(self.filter) self.available_filters = [ "all", @@ -61,40 +59,43 @@ class GameListHeadBar(QWidget): self.filter.setCurrentIndex(0) self.filter.currentIndexChanged.connect(self.filter_changed) - self.layout().addStretch(1) self.import_game = QPushButton(icon("mdi.import", "fa.arrow-down"), self.tr("Import Game")) self.import_clicked = self.import_game.clicked - self.layout().addWidget(self.import_game) self.egl_sync = QPushButton(icon("mdi.sync", "fa.refresh"), self.tr("Sync with EGL")) self.egl_sync_clicked = self.egl_sync.clicked - self.layout().addWidget(self.egl_sync) # FIXME: Until it is ready # self.egl_sync.setEnabled(False) - self.layout().addStretch(1) - icon_label = QLabel() icon_label.setPixmap(icon("fa.search").pixmap(QSize(20, 20))) - self.layout().addWidget(icon_label) self.search_bar = QLineEdit() self.search_bar.setObjectName("search_bar") self.search_bar.setFrame(False) self.search_bar.setMinimumWidth(200) self.search_bar.setPlaceholderText(self.tr("Search Game")) - self.layout().addWidget(self.search_bar) - self.layout().addStretch(2) checked = QSettings().value("icon_view", True, bool) self.view = SelectViewWidget(checked) - self.layout().addWidget(self.view) - self.layout().addStretch(1) self.refresh_list = QPushButton() self.refresh_list.setIcon(icon("fa.refresh")) # Reload icon - self.layout().addWidget(self.refresh_list) + + layout = QHBoxLayout() + layout.addWidget(self.filter) + layout.addStretch(1) + layout.addWidget(self.import_game) + layout.addWidget(self.egl_sync) + layout.addStretch(1) + layout.addWidget(icon_label) + layout.addWidget(self.search_bar) + layout.addStretch(3) + layout.addWidget(self.view) + layout.addStretch(1) + layout.addWidget(self.refresh_list) + self.setLayout(layout) def filter_changed(self, i): self.filterChanged.emit(self.available_filters[i]) diff --git a/rare/components/tabs/games/import_sync/import_group.py b/rare/components/tabs/games/import_sync/import_group.py index e296d6e7..fa5e52a1 100644 --- a/rare/components/tabs/games/import_sync/import_group.py +++ b/rare/components/tabs/games/import_sync/import_group.py @@ -1,7 +1,8 @@ import json import os from logging import getLogger -from typing import List, Tuple +from pathlib import Path +from typing import List, Tuple, Optional from PyQt5.QtCore import Qt, QModelIndex, pyqtSignal from PyQt5.QtGui import QStandardItemModel @@ -21,9 +22,7 @@ class AppNameCompleter(QCompleter): def __init__(self, app_names: List, parent=None): super(AppNameCompleter, self).__init__(parent) # pylint: disable=E1136 - super(AppNameCompleter, self).activated[QModelIndex].connect( - self.__activated_idx - ) + super(AppNameCompleter, self).activated[QModelIndex].connect(self.__activated_idx) model = QStandardItemModel(len(app_names), 2) for idx, game in enumerate(app_names): @@ -52,18 +51,17 @@ class AppNameCompleter(QCompleter): # lk: Note to self, the completer and popup models are different. # lk: Getting the index from the popup and trying to use it in the completer will return invalid results if isinstance(idx, QModelIndex): - self.activated.emit( - self.popup().model().data(self.popup().model().index(idx.row(), 1)) - ) + self.activated.emit(self.popup().model().data(self.popup().model().index(idx.row(), 1))) # TODO: implement conversion from app_name to app_title (signal loop here) # if isinstance(idx_str, str): # self.activated.emit(idx_str) -class ImportGroup(QGroupBox, Ui_ImportGroup): +class ImportGroup(QGroupBox): def __init__(self, parent=None): super(ImportGroup, self).__init__(parent=parent) - self.setupUi(self) + self.ui = Ui_ImportGroup() + self.ui.setupUi(self) self.core = LegendaryCoreSingleton() self.signals = GlobalSignalsSingleton() self.api_results = ApiResultsSingleton() @@ -84,23 +82,32 @@ class ImportGroup(QGroupBox, Ui_ImportGroup): parent=self, ) self.path_edit.textChanged.connect(self.path_changed) - self.path_edit_layout.addWidget(self.path_edit) + self.ui.path_edit_layout.addWidget(self.path_edit) - self.app_name = IndicatorLineEdit( + self.app_name_edit = IndicatorLineEdit( placeholder=self.tr("Use in case the app name was not found automatically"), completer=AppNameCompleter( - app_names=[ - (i.app_name, i.app_title) for i in self.api_results.game_list - ] + app_names=[(i.app_name, i.app_title) for i in self.api_results.game_list] ), edit_func=self.app_name_edit_cb, parent=self, ) - self.app_name.textChanged.connect(self.app_name_changed) - self.app_name_layout.addWidget(self.app_name) + self.app_name_edit.textChanged.connect(self.app_name_changed) + self.ui.app_name_layout.addWidget(self.app_name_edit) - self.import_button.setEnabled(False) - self.import_button.clicked.connect(self.import_game) + self.ui.import_button.setEnabled(False) + self.ui.import_button.clicked.connect( + lambda: self.import_games(self.path_edit.text()) + if self.ui.import_folder_check.isChecked() + else self.import_game(self.path_edit.text()) + ) + + self.ui.import_folder_check.stateChanged.connect( + lambda s: self.ui.import_button.setEnabled(s or (not s and self.app_name_edit.is_valid)) + ) + self.ui.import_folder_check.stateChanged.connect( + lambda s: self.app_name_edit.setEnabled(not s) + ) def path_edit_cb(self, path) -> Tuple[bool, str, str]: if os.path.exists(path): @@ -113,11 +120,12 @@ class ImportGroup(QGroupBox, Ui_ImportGroup): return False, path, "" def path_changed(self, path): - self.info_label.setText(str()) + self.ui.info_label.setText(str()) + self.ui.import_folder_check.setChecked(False) if self.path_edit.is_valid: - self.app_name.setText(self.find_app_name(path)) + self.app_name_edit.setText(self.find_app_name(path)) else: - self.app_name.setText(str()) + self.app_name_edit.setText(str()) def app_name_edit_cb(self, text) -> Tuple[bool, str, str]: if not text: @@ -128,56 +136,59 @@ class ImportGroup(QGroupBox, Ui_ImportGroup): return False, text, IndicatorLineEdit.reasons.game_not_installed def app_name_changed(self, text): - self.info_label.setText(str()) - if self.app_name.is_valid: - self.import_button.setEnabled(True) + self.ui.info_label.setText(str()) + if self.app_name_edit.is_valid: + self.ui.import_button.setEnabled(True) else: - self.import_button.setEnabled(False) + self.ui.import_button.setEnabled(False) - def find_app_name(self, path): - if not os.path.exists(os.path.join(path, ".egstore")): - return None - for i in os.listdir(os.path.join(path, ".egstore")): - if i.endswith(".mancpn"): - file = os.path.join(path, ".egstore", i) - return json.load(open(file, "r")).get("AppName") + def find_app_name(self, path: str) -> Optional[str]: + if os.path.exists(os.path.join(path, ".egstore")): + for i in os.listdir(os.path.join(path, ".egstore")): + if i.endswith(".mancpn"): + file = os.path.join(path, ".egstore", i) + return json.load(open(file, "r")).get("AppName") + elif app_name := legendary_utils.resolve_aliases( + self.core, os.path.basename(os.path.normpath(path))): + return app_name else: - logger.warning("File was not found") - return None + logger.warning(f"Could not find AppName for {path}") + return None def import_game(self, path=None): - app_name = self.app_name.text() if not path: path = self.path_edit.text() - if not app_name: + if not (app_name := self.app_name_edit.text()): # try to find app name if a_n := self.find_app_name(path): app_name = a_n else: - self.info_label.setText(self.tr("Could not find app name")) + self.ui.info_label.setText(self.tr("Could not find AppName")) return + self.__import_game(app_name, path) - if not ( - err := legendary_utils.import_game(self.core, app_name=app_name, path=path) - ): + def import_games(self, path=None): + if not path: + path = self.path_edit.text() + path = Path(path) + for child in path.iterdir(): + if child.is_dir(): + if (app_name := self.find_app_name(str(child))) is not None: + self.__import_game(app_name, str(child)) + + def __import_game(self, app_name, path): + if not (err := legendary_utils.import_game(self.core, app_name=app_name, path=path)): igame = self.core.get_installed_game(app_name) - self.info_label.setText( - self.tr("Successfully imported {}").format(igame.title) - ) - self.app_name.setText(str()) + logger.info(f"Successfully imported {igame.title}") + self.ui.info_label.setText(self.tr("Successfully imported {}").format(igame.title)) self.signals.update_gamelist.emit([app_name]) - if ( - igame.version - != self.core.get_asset(app_name, igame.platform, False).build_version - ): + if igame.version != self.core.get_asset(app_name, igame.platform, False).build_version: # update available self.signals.add_download.emit(igame.app_name) self.signals.update_download_tab_text.emit() else: logger.warning(f'Failed to import "{app_name}"') - self.info_label.setText( - self.tr("Could not import {}: {}").format(app_name, err) - ) + self.ui.info_label.setText(self.tr("Could not import {}: {}").format(app_name, err)) return diff --git a/rare/components/tabs/settings/widgets/eos.py b/rare/components/tabs/settings/widgets/eos.py index 28bbad87..81cfb762 100644 --- a/rare/components/tabs/settings/widgets/eos.py +++ b/rare/components/tabs/settings/widgets/eos.py @@ -202,9 +202,11 @@ class EosWidget(QGroupBox, Ui_EosWidget): def install_overlay(self, update=False): if platform.system() != "Windows": - if QMessageBox.No == QMessageBox.question(self, "Warning", - self.tr("Epic overlay is currently not supported by wine, so it won't work. Install anyway? "), - QMessageBox.Yes | QMessageBox.No, QMessageBox.No): + if QMessageBox.No == QMessageBox.question( + self, + self.tr("Warning"), + self.tr("Epic overlay is currently not supported by wine, so it won't work. Install anyway? "), + QMessageBox.Yes | QMessageBox.No, QMessageBox.No): return base_path = os.path.expanduser("~/legendary/.overlay") diff --git a/rare/resources/resources.py b/rare/resources/resources.py index 391f917628cbe193404f66185bab0d926a22ede9..3f35740ffa4579a32864f17841d1184574487461 100644 GIT binary patch delta 15185 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 diff --git a/rare/resources/stylesheets/RareStyle/stylesheet.qss b/rare/resources/stylesheets/RareStyle/stylesheet.qss index 5683ae8d..25a94baa 100644 --- a/rare/resources/stylesheets/RareStyle/stylesheet.qss +++ b/rare/resources/stylesheets/RareStyle/stylesheet.qss @@ -1,42 +1,44 @@ /* [Text] -normal: #eeeeee -- main font color -disabled: #43474d -- disabled font color - +normal: #eeeeee rgb(238, 238, 238) -- main font color +disabled: #43474d rgb( 67, 71, 77) -- disabled font color + rgba( 67, 71, 77, 55%) == rgb( 51, 54, 59) + rgba( 67, 71, 77, 25%) == rgb( 41, 43, 47) [Background] -normal: #202225 -- main background color -editable: #333344 -- background color for reactive/editable widgets (TextEdits, ProgressBars etc) -hover: #222233 -- background color when hovering over reactive widgets (Buttons, Headers) -selection: #2f4f4f -- background color for selectable widgets -alternate: #282a2e -- background color for alternating rows in List/Tree/TableViews and for ScrollBars +normal: #202225 rgb( 32, 34, 37) -- main background color +editable: #333344 rgb( 51, 51, 68) -- background color for reactive/editable widgets (TextEdits, ProgressBars etc) +hover: #222233 rgb( 34, 34, 51) -- background color when hovering over reactive widgets (Buttons, Headers) +selection: #2f4f4f rgb( 47, 79, 79) -- background color for selectable widgets +alternate: #282a2e rgb( 40, 42, 46) -- background color for alternating rows in List/Tree/TableViews and for ScrollBars [Border] -normal: #483d8b -- border color for most widgets -editable: #2f4f4f -- border color for editable widgets (TextEdits, ProgressBars etc) -highlight: #5246a0 -- border color for dropdown widgets, a bit lighter than border for more separation -disabled: #43474d -- border for disabled widgets -alternate: #3c3f41 -- border color for gradient backgrounds on widgets like Tabs and Popups +normal: #483d8b rgb( 72, 61, 139) -- border color for most widgets + rgba( 72, 61, 139, 25%) == rgb( 42, 40, 63) +editable: #2f4f4f rgb( 47, 79, 79) -- border color for editable widgets (TextEdits, ProgressBars etc) +highlight: #5246a0 rgb( 82, 70, 160) -- border color for dropdown widgets, a bit lighter than border for more separation +disabled: #43474d rgb( 67, 71, 77) -- border for disabled widgets +alternate: #3c3f41 rgb( 60, 63, 65) -- border color for gradient backgrounds on widgets like Tabs and Popups [Special] -info-normal: #bbb -- infoLabel -install-normal: #090 -- install -install-hover: #060 -- install -install-disabled: #020 -- install -uninstall-normal: #900 -- uninstall -uninstall-hover: #600 -- uninstall -uninstall-disabled: #200 -- uninstall +info-normal: #bbb rgb(187, 187, 187) -- infoLabel +install-normal: #070 rgb( 0, 119, 0) -- install +install-hover: #050 rgb( 0, 85, 0) -- install +install-disabled: #020 rgb( 0, 34, 0) -- install +uninstall-normal: #700 rgb(119, 0, 0) -- uninstall +uninstall-hover: #500 rgb( 85, 0, 0) -- uninstall +uninstall-disabled: #200 rgb( 34, 0, 0) -- uninstall */ * { - color: #eeeeee; - border-color: #483d8b; - background-color: #202225; + color: rgb(238, 238, 238); + border-color: rgb( 72, 61, 139); + background-color: rgb( 32, 34, 37); } *:disabled, *:editable:disabled { - color: #43474d; - border-color: #43474d; - background-color: #202225; + color: rgb( 67, 71, 77); + border-color: rgb( 67, 71, 77); + background-color: rgb( 32, 34, 37); } QLabel, @@ -44,10 +46,10 @@ QLabel:disabled { border-width: 0px; background-color: transparent; padding: 0px; - selection-background-color: #2f4f4f; + selection-background-color: rgb( 47, 79, 79); } QLabel[infoLabel="1"] { - color: #bbb; + color: rgb(187, 187, 187); font-style: italic; font-weight: normal; } @@ -94,9 +96,9 @@ QSpinBox, QDoubleSpinBox, QProgressBar, QScrollBar { - border-color: #2f4f4f; - background-color: #333344; - selection-background-color: #2f4f4f; + border-color: rgb( 47, 79, 79); + background-color: rgb( 51, 51, 68); + selection-background-color: rgb( 47, 79, 79); } QLineEdit, QTextEdit, @@ -123,17 +125,17 @@ QStackedWidget[noBorder="1"] { border-color: transparent; } QComboBox { - background-color: rgba(67, 71, 77, 55%); + background-color: rgb( 51, 54, 59); } QComboBox:disabled { - background-color: rgba(67, 71, 77, 25%); + background-color: rgb( 41, 43, 47); } QComboBox:!editable:hover { - background-color: #222233; + background-color: rgb( 34, 34, 51); } *::item:selected, QComboBox QAbstractItemView { - selection-background-color: #2f4f4f; + selection-background-color: rgb( 47, 79, 79); } *::drop-down, *::drop-down:editable, @@ -150,19 +152,19 @@ QComboBox QAbstractItemView { *::drop-down:editable:disabled, *::up-button:disabled, *::down-button:disabled { - border-color: #43474d; + border-color: rgb( 67, 71, 77); background-color: transparent; } *::drop-down { subcontrol-position: top right; - border-color: #483d8b; - border-left-color: #5246a0; /* #483d8b lighter */ + border-color: rgb( 72, 61, 139); + border-left-color: rgb( 82, 70, 160); /* rgb( 72, 61, 139) lighter */ } *::drop-down:editable, *::up-button, *::down-button { - border-color: #2f4f4f; - background-color: #3c3f41; + border-color: rgb( 47, 79, 79); + background-color: rgb( 60, 63, 65); } *::drop-down, *::drop-down:editable { @@ -189,12 +191,12 @@ QProgressBar { QProgressBar::chunk { width: 2%; margin: 0%; - background-color: #2f4f4f; + background-color: rgb( 47, 79, 79); } QScrollBar { border-radius: 4px; padding: 1px; - background-color: #282a2e; + background-color: rgb( 40, 42, 46); } QScrollBar:vertical { width: 11px; @@ -229,8 +231,8 @@ QScrollBar::sub-line:horizontal { QScrollBar::handle { border-width: 1px; border-style: solid; - border-color: #483d8b; - background-color: #333344; + border-color: rgb( 72, 61, 139); + background-color: rgb( 51, 51, 68); border-radius: 2px; min-height: 30px; min-width: 30px; @@ -259,11 +261,11 @@ QListView, QTreeView, QTableView { outline: 0; - gridline-color: #282a2e; + gridline-color: rgb( 40, 42, 46); show-decoration-selected: 0; selection-background-color: transparent; - background-color: #202225; - alternate-background-color: #282a2e; + background-color: rgb( 32, 34, 37); + alternate-background-color: rgb( 40, 42, 46); } QListView::item, @@ -282,28 +284,28 @@ QTableView[currentRow="0"]::item { QListView::item:hover, QTreeView::item:hover, QTableView::item:hover { - border-color: #483d8b; - background-color: #222233; + border-color: rgb( 72, 61, 139); + background-color: rgb( 34, 34, 51); } QListView::item:selected, QTreeView::item:selected, QTableView::item:selected { - border-color: #483d8b; - background-color: #2f4f4f; + border-color: rgb( 72, 61, 139); + background-color: rgb( 47, 79, 79); } QPushButton, QToolButton { - background-color: rgba(67, 71, 77, 55%); + background-color: rgb( 51, 54, 59); } QPushButton:disabled, QToolButton:disabled { - background-color: rgba(67, 71, 77, 25%); + background-color: rgb( 41, 43, 47); } QPushButton:hover, QToolButton:hover, QHeaderView::section:hover { - background-color: #222233; + background-color: rgb( 34, 34, 51); } QPushButton, QToolButton { @@ -327,36 +329,36 @@ QPushButton#menu { } QPushButton#menu_button { border-width: 0px; - background-color: #3c3f41; + background-color: rgb( 60, 63, 65); width: 18px; height: 18px; } QPushButton#menu_button:hover { - background-color: "#333344"; + background-color: rgb( 51, 51, 68); } QPushButton[install="1"], QPushButton#install_button { - background-color: "#070"; + background-color: rgb( 0, 119, 0); } QPushButton[install="1"]:hover, QPushButton#install_button:hover { - background-color: "#050"; + background-color: rgb( 0, 85, 0); } QPushButton[install="1"]:disabled, QPushButton#install_button:disabled { - background-color: "#020"; + background-color: rgb( 0, 34, 0); } QPushButton[uninstall="1"], QPushButton#uninstall_button { - background-color: "#900"; + background-color: rgb(119, 0, 0); } QPushButton[uninstall="1"]:hover, QPushButton#uninstall_button:hover { - background-color: "#600"; + background-color: rgb( 85, 0, 0); } QPushButton[uninstall="1"]:disabled, QPushButton#uninstall_button:disabled { - background-color: "#200"; + background-color: rgb( 34, 0, 0); } QPushButton#success{ background-color: lime; @@ -376,10 +378,10 @@ QRadioButton::indicator, QListView::indicator, QTreeView::indicator, QTableView::indicator { - border-color: #2f4f4f; + border-color: rgb( 47, 79, 79); border-width: 1px; border-style: solid; - background-color: #202225; + background-color: rgb( 32, 34, 37); } QCheckBox::indicator, QRadioButton::indicator, @@ -395,7 +397,7 @@ QRadioButton::indicator:disabled, QListView::indicator:disabled, QTreeView::indicator:disabled, QTableView::indicator:disabled { - border-color: #43474d; + border-color: rgb( 67, 71, 77); } QRadioButton::indicator { border-radius: 5%; @@ -469,17 +471,20 @@ QGroupBox::title { border-style: solid; border-top-left-radius: 4px; border-bottom-right-radius: 4px; - border-color: #483d8b; + border-color: rgb( 72, 61, 139); padding: 1px; /* background: qlineargradient( - x1: -2, y1: 0, x2: 1, y2: 1, stop: 0 #483d8b, stop: 1 #202225); + x1: -2, y1: 0, + x2: 1, y2: 1, + stop: 0 rgb( 72, 61, 139), + stop: 1 rgb( 32, 34, 37)); */ - background-color: rgba(72, 61, 139, 25%); + background-color: rgb( 42, 40, 63); } QGroupBox::title:disabled { - border-color: #43474d; - background-color: rgba(67, 71, 77, 25%); + border-color: rgb( 67, 71, 77); + background-color: rgb( 41, 43, 47); } QSizeGrip { @@ -495,7 +500,7 @@ QSizeGrip { #search_bar { padding: 3px; border-radius: 5px; - background-color: "#333344"; + background-color: rgb( 51, 51, 68); } QTabWidget::pane { @@ -519,54 +524,66 @@ QTabBar::tab:bottom { } QTabBar::tab:top:hover, QTabBar::tab:bottom:hover { - border-color: #483d8b; + border-color: rgb( 72, 61, 139); border-left-color: transparent; border-right-color: transparent; } QTabBar::tab:top { border-top-width: 3px; - border-top-color: #3c3f41; - border-bottom-color: #483d8b; + border-top-color: rgb( 60, 63, 65); + border-bottom-color: rgb( 72, 61, 139); background: qlineargradient( - x1: 0, y1: -1, x2: 0, y2: 1, stop: 0 #3c3f41, stop: 1 #202225); + x1: 0, y1: -1, + x2: 0, y2: 1, + stop: 0 rgb( 60, 63, 65), + stop: 1 rgb( 32, 34, 37)); } QTabBar::tab:bottom { border-bottom-width: 3px; - border-top-color: #483d8b; - border-bottom-color: #3c3f41; + border-top-color: rgb( 72, 61, 139); + border-bottom-color: rgb( 60, 63, 65); background: qlineargradient( - x1: 0, y1: 2, x2: 0, y2: 0, stop: 0 #3c3f41, stop: 1 #202225); + x1: 0, y1: 2, + x2: 0, y2: 0, + stop: 0 rgb( 60, 63, 65), + stop: 1 rgb( 32, 34, 37)); } QTabBar::tab:top:hover { background: qlineargradient( - x1: 0, y1: -1, x2: 0, y2: 1, stop: 0 #483d8b, stop: 1 #202225); + x1: 0, y1: -1, + x2: 0, y2: 1, + stop: 0 rgb( 72, 61, 139), + stop: 1 rgb( 32, 34, 37)); } QTabBar::tab:bottom:hover { background: qlineargradient( - x1: 0, y1: 2, x2: 0, y2: 0, stop: 0 #483d8b, stop: 1 #202225); + x1: 0, y1: 2, + x2: 0, y2: 0, + stop: 0 rgb( 72, 61, 139), + stop: 1 rgb( 32, 34, 37)); } QTabBar::tab:top:selected { - border-color: #483d8b; + border-color: rgb( 72, 61, 139); border-bottom-color: transparent; - background: #202225; + background: rgb( 32, 34, 37); } QTabBar::tab:bottom:selected { - border-color: #483d8b; + border-color: rgb( 72, 61, 139); border-top-color: transparent; - background: #202225; + background: rgb( 32, 34, 37); } QTabBar::tab:top:disabled { - border-bottom-color: #3c3f41; + border-bottom-color: rgb( 60, 63, 65); } QTabBar::tab:bottom:disabled { - border-top-color: #3c3f41; + border-top-color: rgb( 60, 63, 65); } QTabBar::tab:top:selected:disabled { - border-color: #3c3f41; + border-color: rgb( 60, 63, 65); border-bottom-color: transparent; } QTabBar::tab:bottom:selected:disabled { - border-color: #3c3f41; + border-color: rgb( 60, 63, 65); border-top-color: transparent; } QTabBar::tab:left, @@ -576,54 +593,66 @@ QTabBar::tab:right { } QTabBar::tab:left:hover, QTabBar::tab:right:hover { - border-color: #483d8b; + border-color: rgb( 72, 61, 139); border-top-color: transparent; border-bottom-color: transparent; } QTabBar::tab:left { border-left-width: 3px; - border-left-color: #3c3f41; - border-right-color: #483d8b; + border-left-color: rgb( 60, 63, 65); + border-right-color: rgb( 72, 61, 139); background: qlineargradient( - x1: -1, y1: 0, x2: 1, y2: 0, stop: 0 #3c3f41, stop: 1 #202225); + x1: -1, y1: 0, + x2: 1, y2: 0, + stop: 0 rgb( 60, 63, 65), + stop: 1 rgb( 32, 34, 37)); } QTabBar::tab:right { border-right-width: 3px; - border-right-color: #3c3f41; - border-left-color: #483d8b; + border-right-color: rgb( 60, 63, 65); + border-left-color: rgb( 72, 61, 139); background: qlineargradient( - x1: 2, y1: 0, x2: 0, y2: 0, stop: 0 #3c3f41, stop: 1 #202225); + x1: 2, y1: 0, + x2: 0, y2: 0, + stop: 0 rgb( 60, 63, 65), + stop: 1 rgb( 32, 34, 37)); } QTabBar::tab:left:hover { background: qlineargradient( - x1: -1, y1: 0, x2: 1, y2: 0, stop: 0 #483d8b, stop: 1 #202225); + x1: -1, y1: 0, + x2: 1, y2: 0, + stop: 0 rgb( 72, 61, 139), + stop: 1 rgb( 32, 34, 37)); } QTabBar::tab:right:hover { background: qlineargradient( - x1: 2, y1: 0, x2: 0, y2: 0, stop: 0 #483d8b, stop: 1 #202225); + x1: 2, y1: 0, + x2: 0, y2: 0, + stop: 0 rgb( 72, 61, 139), + stop: 1 rgb( 32, 34, 37)); } QTabBar::tab:left:selected { - border-color: #483d8b; + border-color: rgb( 72, 61, 139); border-right-color: transparent; - background: #202225; + background: rgb( 32, 34, 37); } QTabBar::tab:right:selected { - border-color: #483d8b; + border-color: rgb( 72, 61, 139); border-left-color: transparent; - background: #202225; + background: rgb( 32, 34, 37); } QTabBar::tab:left:disabled { - border-right-color: #3c3f41; + border-right-color: rgb( 60, 63, 65); } QTabBar::tab:right:disabled { - border-left-color: #3c3f41; + border-left-color: rgb( 60, 63, 65); } QTabBar::tab:left:selected:disabled { - border-color: #3c3f41; + border-color: rgb( 60, 63, 65); border-right-color: transparent; } QTabBar::tab:right:selected:disabled { - border-color: #3c3f41; + border-color: rgb( 60, 63, 65); border-left-color: transparent; } @@ -631,21 +660,24 @@ QTabBar#MainTabBar { border-width: 1px; border-style: solid; border-color: transparent; - border-bottom-color: #483d8b; + border-bottom-color: rgb( 72, 61, 139); /* background: qlineargradient( - x1: 0, y1: -3, x2: 0, y2: 1, stop: 0 #3c3f41, stop: 1 #202225); + x1: 0, y1: -3, + x2: 0, y2: 1, + stop: 0 rgb( 60, 63, 65), + stop: 1 rgb( 32, 34, 37)); */ } QTabBar#MainTabBar:disabled { border-color: transparent; - border-bottom-color: #3c3f41; + border-bottom-color: rgb( 60, 63, 65); } QTabBar#MainTabBar::tab { margin-left: 3px; margin-right: 3px; border-top-color: transparent; - border-bottom-color: #483d8b; + border-bottom-color: rgb( 72, 61, 139); padding: 5px; }/* QTabBar#MainTabBar::tab:top:first, @@ -659,25 +691,28 @@ QTabBar#MainTabBar::tab:bottom:last { border-right: transparent; }*/ QTabBar#MainTabBar::tab:top:hover { - border-top-color: #483d8b; + border-top-color: rgb( 72, 61, 139); } QTabBar#MainTabBar::tab:top:selected { - border-color: #483d8b; - border-bottom-color: #202225; + border-color: rgb( 72, 61, 139); + border-bottom-color: rgb( 32, 34, 37); } QTabBar#SideTabBar { border-width: 1px; border-style: solid; border-color: transparent; - border-right-color: #483d8b; + border-right-color: rgb( 72, 61, 139); /* background: qlineargradient( - x1: -3, y1: 0, x2: 1, y2: 0, stop: 0 #3c3f41, stop: 1 #202225); + x1: -3, y1: 0, + x2: 1, y2: 0, + stop: 0 rgb( 60, 63, 65), + stop: 1 rgb( 32, 34, 37)); */ } QTabBar#SideTabBar:disabled { border-color: transparent; - border-right-color: #3c3f41; + border-right-color: rgb( 60, 63, 65); } QTabBar#SideTabBar::tab { margin-top: 3px; @@ -703,44 +738,47 @@ QStatusBar { border-width: 1px; border-style: solid; border-color: transparent; - border-top-color: #483d8b; - border-bottom-color: #3c3f41; + border-top-color: rgb( 72, 61, 139); + border-bottom-color: rgb( 60, 63, 65); background: qlineargradient( - x1: 0, y1: 3, x2: 0, y2: 0, stop: 0 #3c3f41, stop: 1 #202225); + x1: 0, y1: 3, + x2: 0, y2: 0, + stop: 0 rgb( 60, 63, 65), + stop: 1 rgb( 32, 34, 37)); } QToolTip { border-width: 1px; border-style: solid; - border-color: #483d8b; + border-color: rgb( 72, 61, 139); border-radius: 4px; padding: 1px; opacity: 200; } QBalloonTip { - color: #eeeeee; - background-color: #202225; + color: rgb(238, 238, 238); + background-color: rgb( 32, 34, 37); border-width: 1px; border-style: solid; - border-color: #483d8b; + border-color: rgb( 72, 61, 139); border-radius: 4px; padding: 1px; } /* Wrapper settings styling */ QPushButton#WrapperWidgetButton { - border-color: rgba(67, 71, 77, 55%); + border-color: rgb( 51, 54, 59); } QPushButton#WrapperWidgetButton:disabled { - border-color: rgba(67, 71, 77, 25%); + border-color: rgb( 41, 43, 47); } QScrollArea#WrapperSettingsScroll { - border-color: #2f4f4f; - background-color: #282a2e; + border-color: rgb( 47, 79, 79); + background-color: rgb( 40, 42, 46); } QScrollBar#WrapperSettingsScrollBar { - background-color: #282a2e; + background-color: rgb( 40, 42, 46); } QLabel#WrapperSettingsLabel { border-width: 1px; @@ -748,6 +786,6 @@ QLabel#WrapperSettingsLabel { border-radius: 2px; padding: 2px; color: #999; - border-color: #2f4f4f; - background-color: #282a2e; + border-color: rgb( 47, 79, 79); + background-color: rgb( 40, 42, 46); } diff --git a/rare/shared.py b/rare/shared.py index 5de855fd..33ce579f 100644 --- a/rare/shared.py +++ b/rare/shared.py @@ -1,37 +1,47 @@ from argparse import Namespace +from typing import Optional from legendary.core import LegendaryCore + from rare.utils.models import ApiResults, GlobalSignals -_legendary_core_singleton: LegendaryCore = None -_global_signals_singleton: GlobalSignals = None -_arguments_singleton: Namespace = None -_api_results_singleton: ApiResults = None +_legendary_core_singleton: Optional[LegendaryCore] = None +_global_signals_singleton: Optional[GlobalSignals] = None +_arguments_singleton: Optional[Namespace] = None +_api_results_singleton: Optional[ApiResults] = None -def LegendaryCoreSingleton() -> LegendaryCore: +def LegendaryCoreSingleton(init: bool = False) -> LegendaryCore: global _legendary_core_singleton + if _legendary_core_singleton is None and not init: + raise RuntimeError("Uninitialized use of LegendaryCoreSingleton") if _legendary_core_singleton is None: _legendary_core_singleton = LegendaryCore() return _legendary_core_singleton -def GlobalSignalsSingleton() -> GlobalSignals: +def GlobalSignalsSingleton(init: bool = False) -> GlobalSignals: global _global_signals_singleton + if _global_signals_singleton is None and not init: + raise RuntimeError("Uninitialized use of GlobalSignalsSingleton") if _global_signals_singleton is None: _global_signals_singleton = GlobalSignals() return _global_signals_singleton -def ArgumentsSingleton(args: Namespace = None) -> Namespace: +def ArgumentsSingleton(args: Namespace = None) -> Optional[Namespace]: global _arguments_singleton + if _arguments_singleton is None and args is None: + raise RuntimeError("Uninitialized use of ArgumentsSingleton") if _arguments_singleton is None: _arguments_singleton = args return _arguments_singleton -def ApiResultsSingleton(res: ApiResults = None) -> ApiResults: +def ApiResultsSingleton(res: ApiResults = None) -> Optional[ApiResults]: global _api_results_singleton + if _api_results_singleton is None and res is None: + raise RuntimeError("Uninitialized use of ApiResultsSingleton") if _api_results_singleton is None: _api_results_singleton = res return _api_results_singleton diff --git a/rare/ui/components/dialogs/install_dialog.py b/rare/ui/components/dialogs/install_dialog.py index c8bb87dd..80927028 100644 --- a/rare/ui/components/dialogs/install_dialog.py +++ b/rare/ui/components/dialogs/install_dialog.py @@ -14,7 +14,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_InstallDialog(object): def setupUi(self, InstallDialog): InstallDialog.setObjectName("InstallDialog") - InstallDialog.resize(324, 347) + InstallDialog.resize(337, 372) InstallDialog.setWindowTitle("Rare") self.install_dialog_layout = QtWidgets.QFormLayout(InstallDialog) self.install_dialog_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) @@ -58,6 +58,7 @@ class Ui_InstallDialog(object): sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(self.force_download_check.sizePolicy().hasHeightForWidth()) self.force_download_check.setSizePolicy(sizePolicy) + self.force_download_check.setText("") self.force_download_check.setObjectName("force_download_check") self.install_dialog_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.force_download_check) self.platform_label = QtWidgets.QLabel(InstallDialog) @@ -74,50 +75,14 @@ class Ui_InstallDialog(object): self.ignore_space_label = QtWidgets.QLabel(InstallDialog) self.ignore_space_label.setObjectName("ignore_space_label") self.install_dialog_layout.setWidget(5, QtWidgets.QFormLayout.LabelRole, self.ignore_space_label) - self.ignore_space_layout = QtWidgets.QHBoxLayout() - self.ignore_space_layout.setObjectName("ignore_space_layout") - self.ignore_space_check = QtWidgets.QCheckBox(InstallDialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.ignore_space_check.sizePolicy().hasHeightForWidth()) - self.ignore_space_check.setSizePolicy(sizePolicy) - self.ignore_space_check.setObjectName("ignore_space_check") - self.ignore_space_layout.addWidget(self.ignore_space_check) - self.ignore_space_info_label = QtWidgets.QLabel(InstallDialog) - font = QtGui.QFont() - font.setItalic(True) - self.ignore_space_info_label.setFont(font) - self.ignore_space_info_label.setObjectName("ignore_space_info_label") - self.ignore_space_layout.addWidget(self.ignore_space_info_label) - self.install_dialog_layout.setLayout(5, QtWidgets.QFormLayout.FieldRole, self.ignore_space_layout) self.download_only_label = QtWidgets.QLabel(InstallDialog) self.download_only_label.setObjectName("download_only_label") self.install_dialog_layout.setWidget(6, QtWidgets.QFormLayout.LabelRole, self.download_only_label) - self.download_only_layout = QtWidgets.QHBoxLayout() - self.download_only_layout.setObjectName("download_only_layout") - self.download_only_check = QtWidgets.QCheckBox(InstallDialog) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.download_only_check.sizePolicy().hasHeightForWidth()) - self.download_only_check.setSizePolicy(sizePolicy) - self.download_only_check.setText("") - self.download_only_check.setObjectName("download_only_check") - self.download_only_layout.addWidget(self.download_only_check) - self.download_only_info_label = QtWidgets.QLabel(InstallDialog) - font = QtGui.QFont() - font.setItalic(True) - self.download_only_info_label.setFont(font) - self.download_only_info_label.setObjectName("download_only_info_label") - self.download_only_layout.addWidget(self.download_only_info_label) - self.install_dialog_layout.setLayout(6, QtWidgets.QFormLayout.FieldRole, self.download_only_layout) self.shortcut_lbl = QtWidgets.QLabel(InstallDialog) self.shortcut_lbl.setObjectName("shortcut_lbl") self.install_dialog_layout.setWidget(7, QtWidgets.QFormLayout.LabelRole, self.shortcut_lbl) self.shortcut_cb = QtWidgets.QCheckBox(InstallDialog) self.shortcut_cb.setText("") - self.shortcut_cb.setChecked(True) self.shortcut_cb.setObjectName("shortcut_cb") self.install_dialog_layout.setWidget(7, QtWidgets.QFormLayout.FieldRole, self.shortcut_cb) self.sdl_list_label = QtWidgets.QLabel(InstallDialog) @@ -181,6 +146,28 @@ class Ui_InstallDialog(object): self.install_preqs_check.setText("") self.install_preqs_check.setObjectName("install_preqs_check") self.install_dialog_layout.setWidget(8, QtWidgets.QFormLayout.FieldRole, self.install_preqs_check) + self.ignore_space_check = QtWidgets.QCheckBox(InstallDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.ignore_space_check.sizePolicy().hasHeightForWidth()) + self.ignore_space_check.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setItalic(True) + self.ignore_space_check.setFont(font) + self.ignore_space_check.setObjectName("ignore_space_check") + self.install_dialog_layout.setWidget(5, QtWidgets.QFormLayout.FieldRole, self.ignore_space_check) + self.download_only_check = QtWidgets.QCheckBox(InstallDialog) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.download_only_check.sizePolicy().hasHeightForWidth()) + self.download_only_check.setSizePolicy(sizePolicy) + font = QtGui.QFont() + font.setItalic(True) + self.download_only_check.setFont(font) + self.download_only_check.setObjectName("download_only_check") + self.install_dialog_layout.setWidget(6, QtWidgets.QFormLayout.FieldRole, self.download_only_check) self.retranslateUi(InstallDialog) QtCore.QMetaObject.connectSlotsByName(InstallDialog) @@ -194,9 +181,7 @@ class Ui_InstallDialog(object): self.force_download_label.setText(_translate("InstallDialog", "Force redownload")) self.platform_label.setText(_translate("InstallDialog", "Platform")) self.ignore_space_label.setText(_translate("InstallDialog", "Ignore free space")) - self.ignore_space_info_label.setText(_translate("InstallDialog", "Use with caution!")) self.download_only_label.setText(_translate("InstallDialog", "Download only")) - self.download_only_info_label.setText(_translate("InstallDialog", "Do not try to install.")) self.shortcut_lbl.setText(_translate("InstallDialog", "Create shortcut")) self.sdl_list_label.setText(_translate("InstallDialog", "Optional packs")) self.download_size_label.setText(_translate("InstallDialog", "Download size")) @@ -209,6 +194,8 @@ class Ui_InstallDialog(object): self.verify_button.setText(_translate("InstallDialog", "Verify")) self.install_button.setText(_translate("InstallDialog", "Install")) self.install_preqs_lbl.setText(_translate("InstallDialog", "Install prerequisites")) + self.ignore_space_check.setText(_translate("InstallDialog", "Use with caution!")) + self.download_only_check.setText(_translate("InstallDialog", "Do not try to install.")) if __name__ == "__main__": diff --git a/rare/ui/components/dialogs/install_dialog.ui b/rare/ui/components/dialogs/install_dialog.ui index 908d2a6f..5c43d499 100644 --- a/rare/ui/components/dialogs/install_dialog.ui +++ b/rare/ui/components/dialogs/install_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 324 - 347 + 337 + 372 @@ -85,6 +85,9 @@ 0 + + + @@ -111,32 +114,6 @@ - - - - - - - 0 - 0 - - - - - - - - - true - - - - Use with caution! - - - - - @@ -144,35 +121,6 @@ - - - - - - - 0 - 0 - - - - - - - - - - - - true - - - - Do not try to install. - - - - - @@ -185,9 +133,6 @@ - - true - @@ -323,7 +268,43 @@ - + + + + + + + + + 0 + 0 + + + + + true + + + + Use with caution! + + + + + + + + 0 + 0 + + + + + true + + + + Do not try to install. diff --git a/rare/ui/components/dialogs/login/login_dialog.py b/rare/ui/components/dialogs/login/login_dialog.py index e5e5b84b..b7650abd 100644 --- a/rare/ui/components/dialogs/login/login_dialog.py +++ b/rare/ui/components/dialogs/login/login_dialog.py @@ -2,7 +2,7 @@ # Form implementation generated from reading ui file 'rare/ui/components/dialogs/login/login_dialog.ui' # -# Created by: PyQt5 UI code generator 5.15.4 +# Created by: PyQt5 UI code generator 5.15.6 # # 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,16 +14,17 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_LoginDialog(object): def setupUi(self, LoginDialog): LoginDialog.setObjectName("LoginDialog") - LoginDialog.resize(498, 269) - self.verticalLayout = QtWidgets.QVBoxLayout(LoginDialog) - self.verticalLayout.setObjectName("verticalLayout") - spacerItem = QtWidgets.QSpacerItem(477, 17, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - self.verticalLayout.addItem(spacerItem) + LoginDialog.resize(492, 267) + self.login_layout = QtWidgets.QVBoxLayout(LoginDialog) + self.login_layout.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) + self.login_layout.setObjectName("login_layout") + spacerItem = QtWidgets.QSpacerItem(0, 17, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.login_layout.addItem(spacerItem) self.welcome_label = QtWidgets.QLabel(LoginDialog) self.welcome_label.setObjectName("welcome_label") - self.verticalLayout.addWidget(self.welcome_label) - spacerItem1 = QtWidgets.QSpacerItem(477, 17, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Fixed) - self.verticalLayout.addItem(spacerItem1) + self.login_layout.addWidget(self.welcome_label) + spacerItem1 = QtWidgets.QSpacerItem(0, 17, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding) + self.login_layout.addItem(spacerItem1) self.login_stack = QtWidgets.QStackedWidget(LoginDialog) self.login_stack.setEnabled(True) self.login_stack.setMinimumSize(QtCore.QSize(480, 135)) @@ -73,7 +74,7 @@ class Ui_LoginDialog(object): self.login_browser_radio.setObjectName("login_browser_radio") self.login_page_layout.addWidget(self.login_browser_radio, 1, 0, 1, 1) self.login_stack.addWidget(self.login_page) - self.verticalLayout.addWidget(self.login_stack) + self.login_layout.addWidget(self.login_stack) self.button_layout = QtWidgets.QHBoxLayout() self.button_layout.setObjectName("button_layout") spacerItem3 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum) @@ -87,7 +88,7 @@ class Ui_LoginDialog(object): self.next_button = QtWidgets.QPushButton(LoginDialog) self.next_button.setObjectName("next_button") self.button_layout.addWidget(self.next_button) - self.verticalLayout.addLayout(self.button_layout) + self.login_layout.addLayout(self.button_layout) self.retranslateUi(LoginDialog) self.login_stack.setCurrentIndex(0) diff --git a/rare/ui/components/dialogs/login/login_dialog.ui b/rare/ui/components/dialogs/login/login_dialog.ui index d4051943..2adbb98a 100644 --- a/rare/ui/components/dialogs/login/login_dialog.ui +++ b/rare/ui/components/dialogs/login/login_dialog.ui @@ -6,25 +6,22 @@ 0 0 - 498 - 269 + 492 + 267 Rare Login - + + + QLayout::SetFixedSize + - - Qt::Vertical - - - QSizePolicy::Fixed - - 477 + 0 17 @@ -39,15 +36,9 @@ - - Qt::Vertical - - - QSizePolicy::Fixed - - 477 + 0 17 diff --git a/rare/ui/components/tabs/games/game_info/game_info.py b/rare/ui/components/tabs/games/game_info/game_info.py index b5a57ee4..755b0a2f 100644 --- a/rare/ui/components/tabs/games/game_info/game_info.py +++ b/rare/ui/components/tabs/games/game_info/game_info.py @@ -16,7 +16,6 @@ class Ui_GameInfo(object): GameInfo.setObjectName("GameInfo") GameInfo.resize(791, 583) self.layout_game_info = QtWidgets.QHBoxLayout(GameInfo) - self.layout_game_info.setSizeConstraint(QtWidgets.QLayout.SetFixedSize) self.layout_game_info.setObjectName("layout_game_info") self.image = QtWidgets.QLabel(GameInfo) self.image.setFrameShape(QtWidgets.QFrame.StyledPanel) diff --git a/rare/ui/components/tabs/games/game_info/game_info.ui b/rare/ui/components/tabs/games/game_info/game_info.ui index 73b9be19..a97bfa66 100644 --- a/rare/ui/components/tabs/games/game_info/game_info.ui +++ b/rare/ui/components/tabs/games/game_info/game_info.ui @@ -14,9 +14,6 @@ Game Info - - QLayout::SetFixedSize - diff --git a/rare/ui/components/tabs/games/import_sync/import_group.py b/rare/ui/components/tabs/games/import_sync/import_group.py index 7b13f92d..f1e8222e 100644 --- a/rare/ui/components/tabs/games/import_sync/import_group.py +++ b/rare/ui/components/tabs/games/import_sync/import_group.py @@ -8,30 +8,37 @@ # run again. Do not edit this file unless you know what you are doing. -from PyQt5 import QtCore, QtWidgets +from PyQt5 import QtCore, QtGui, QtWidgets class Ui_ImportGroup(object): def setupUi(self, ImportGroup): ImportGroup.setObjectName("ImportGroup") - ImportGroup.resize(235, 127) + ImportGroup.resize(501, 154) ImportGroup.setWindowTitle("ImportGroup") ImportGroup.setWindowFilePath("") self.import_layout = QtWidgets.QFormLayout(ImportGroup) - self.import_layout.setLabelAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTrailing | QtCore.Qt.AlignVCenter) + self.import_layout.setLabelAlignment(QtCore.Qt.AlignRight|QtCore.Qt.AlignTrailing|QtCore.Qt.AlignVCenter) self.import_layout.setObjectName("import_layout") self.path_edit_label = QtWidgets.QLabel(ImportGroup) self.path_edit_label.setObjectName("path_edit_label") self.import_layout.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.path_edit_label) - self.app_name_label = QtWidgets.QLabel(ImportGroup) - self.app_name_label.setObjectName("app_name_label") - self.import_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.app_name_label) self.path_edit_layout = QtWidgets.QHBoxLayout() self.path_edit_layout.setObjectName("path_edit_layout") self.import_layout.setLayout(0, QtWidgets.QFormLayout.FieldRole, self.path_edit_layout) + self.app_name_label = QtWidgets.QLabel(ImportGroup) + self.app_name_label.setObjectName("app_name_label") + self.import_layout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.app_name_label) self.app_name_layout = QtWidgets.QHBoxLayout() self.app_name_layout.setObjectName("app_name_layout") self.import_layout.setLayout(1, QtWidgets.QFormLayout.FieldRole, self.app_name_layout) + 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.info_label = QtWidgets.QLabel(ImportGroup) + self.info_label.setText("") + self.info_label.setObjectName("info_label") + self.import_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.info_label) self.import_button = QtWidgets.QPushButton(ImportGroup) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Maximum, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) @@ -39,11 +46,13 @@ class Ui_ImportGroup(object): sizePolicy.setHeightForWidth(self.import_button.sizePolicy().hasHeightForWidth()) self.import_button.setSizePolicy(sizePolicy) self.import_button.setObjectName("import_button") - self.import_layout.setWidget(3, QtWidgets.QFormLayout.FieldRole, self.import_button) - self.info_label = QtWidgets.QLabel(ImportGroup) - self.info_label.setText("") - self.info_label.setObjectName("info_label") - self.import_layout.setWidget(2, QtWidgets.QFormLayout.FieldRole, self.info_label) + self.import_layout.setWidget(4, QtWidgets.QFormLayout.FieldRole, self.import_button) + 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.retranslateUi(ImportGroup) QtCore.QMetaObject.connectSlotsByName(ImportGroup) @@ -53,7 +62,9 @@ class Ui_ImportGroup(object): ImportGroup.setTitle(_translate("ImportGroup", "Import EGL game from a directory")) self.path_edit_label.setText(_translate("ImportGroup", "Installation path")) self.app_name_label.setText(_translate("ImportGroup", "Override app name")) + self.import_folder_label.setText(_translate("ImportGroup", "Import all folders")) self.import_button.setText(_translate("ImportGroup", "Import Game")) + self.import_folder_check.setText(_translate("ImportGroup", "Scan the installation path for game folders and import them")) if __name__ == "__main__": diff --git a/rare/ui/components/tabs/games/import_sync/import_group.ui b/rare/ui/components/tabs/games/import_sync/import_group.ui index 9e156c41..0545451f 100644 --- a/rare/ui/components/tabs/games/import_sync/import_group.ui +++ b/rare/ui/components/tabs/games/import_sync/import_group.ui @@ -6,8 +6,8 @@ 0 0 - 235 - 127 + 501 + 154 @@ -30,6 +30,9 @@ + + + @@ -37,13 +40,24 @@ - - - + + + + Import all folders + + + + + + + + + + @@ -57,9 +71,14 @@ - + + + + true + + - + Scan the installation path for game folders and import them diff --git a/rare/utils/extra_widgets.py b/rare/utils/extra_widgets.py index 117f0516..ba880244 100644 --- a/rare/utils/extra_widgets.py +++ b/rare/utils/extra_widgets.py @@ -4,6 +4,7 @@ from typing import Callable, Tuple from PyQt5.QtCore import ( Qt, + QEvent, QCoreApplication, QRect, QSize, @@ -49,6 +50,7 @@ class FlowLayout(QLayout): self._vspacing = vspacing self._items = [] self.setContentsMargins(margin, margin, margin, margin) + self.setObjectName(type(self).__name__) def __del__(self): del self._items[:] @@ -148,7 +150,9 @@ class FlowLayout(QLayout): class IndicatorReasons: dir_not_empty = QCoreApplication.translate("IndicatorReasons", "Directory is not empty") wrong_format = QCoreApplication.translate("IndicatorReasons", "Given text has wrong format") - game_not_installed = QCoreApplication.translate("IndicatorReasons", "Game is not installed or does not exist") + game_not_installed = QCoreApplication.translate( + "IndicatorReasons", "Game is not installed or does not exist" + ) dir_not_exist = QCoreApplication.translate("IndicatorReasons", "Directory does not exist") file_not_exist = QCoreApplication.translate("IndicatorReasons", "File does not exist") wrong_path = QCoreApplication.translate("IndicatorReasons", "Wrong Directory") @@ -160,29 +164,29 @@ class IndicatorLineEdit(QWidget): reasons = IndicatorReasons() def __init__( - self, - text: str = "", - placeholder: str = "", - completer: QCompleter = None, - edit_func: Callable[[str], Tuple[bool, str, str]] = None, - save_func: Callable[[str], None] = None, - horiz_policy: QSizePolicy = QSizePolicy.Expanding, - parent=None, + self, + text: str = "", + placeholder: str = "", + completer: QCompleter = None, + edit_func: Callable[[str], Tuple[bool, str, str]] = None, + save_func: Callable[[str], None] = None, + horiz_policy: QSizePolicy = QSizePolicy.Expanding, + parent=None, ): super(IndicatorLineEdit, self).__init__(parent=parent) - self.setObjectName("IndicatorLineEdit") - self.layout = QHBoxLayout(self) - self.layout.setObjectName("layout") - self.layout.setContentsMargins(0, 0, 0, 0) + self.setObjectName(type(self).__name__) + layout = QHBoxLayout(self) + layout.setObjectName(f"{self.objectName()}Layout") + layout.setContentsMargins(0, 0, 0, 0) # Add line_edit self.line_edit = QLineEdit(self) - self.line_edit.setObjectName("line_edit") + self.line_edit.setObjectName(f"{type(self).__name__}Edit") self.line_edit.setPlaceholderText(placeholder) self.line_edit.setSizePolicy(horiz_policy, QSizePolicy.Fixed) # Add hint_label to line_edit self.line_edit.setLayout(QHBoxLayout()) self.hint_label = QLabel() - self.hint_label.setObjectName("HintLabel") + self.hint_label.setObjectName(f"{type(self).__name__}Label") self.hint_label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) self.line_edit.layout().setContentsMargins(0, 0, 10, 0) self.line_edit.layout().addWidget(self.hint_label) @@ -191,20 +195,16 @@ class IndicatorLineEdit(QWidget): completer.popup().setItemDelegate(QStyledItemDelegate(self)) completer.popup().setAlternatingRowColors(True) self.line_edit.setCompleter(completer) - self.layout.addWidget(self.line_edit) + layout.addWidget(self.line_edit) if edit_func is not None: self.indicator_label = QLabel() - self.indicator_label.setPixmap( - qta_icon("ei.info-circle", color="gray").pixmap(16, 16) - ) + self.indicator_label.setPixmap(qta_icon("ei.info-circle", color="gray").pixmap(16, 16)) self.indicator_label.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.layout.addWidget(self.indicator_label) + layout.addWidget(self.indicator_label) if not placeholder: _translate = QCoreApplication.translate - self.line_edit.setPlaceholderText( - _translate(self.__class__.__name__, "Default") - ) + self.line_edit.setPlaceholderText(_translate(self.__class__.__name__, "Default")) self.edit_func = edit_func self.save_func = save_func @@ -233,9 +233,7 @@ class IndicatorLineEdit(QWidget): def __indicator(self, res, reason=None): color = "green" if res else "red" - self.indicator_label.setPixmap( - qta_icon("ei.info-circle", color=color).pixmap(16, 16) - ) + self.indicator_label.setPixmap(qta_icon("ei.info-circle", color=color).pixmap(16, 16)) if reason: self.indicator_label.setToolTip(reason) else: @@ -297,16 +295,16 @@ class PathEdit(IndicatorLineEdit): compl_model = QFileSystemModel() def __init__( - self, - path: str = "", - file_type: QFileDialog.FileType = QFileDialog.AnyFile, - type_filter: str = "", - name_filter: str = "", - placeholder: str = "", - edit_func: Callable[[str], Tuple[bool, str, str]] = None, - save_func: Callable[[str], None] = None, - horiz_policy: QSizePolicy = QSizePolicy.Expanding, - parent=None, + self, + path: str = "", + file_type: QFileDialog.FileType = QFileDialog.AnyFile, + type_filter: str = "", + name_filter: str = "", + placeholder: str = "", + edit_func: Callable[[str], Tuple[bool, str, str]] = None, + save_func: Callable[[str], None] = None, + horiz_policy: QSizePolicy = QSizePolicy.Expanding, + parent=None, ): try: self.compl_model.setOptions( @@ -320,7 +318,7 @@ class PathEdit(IndicatorLineEdit): self.compl_model.setRootPath(path) self.completer.setModel(self.compl_model) - edit_func = self._wrap_edit_function(edit_func) + edit_func = self.__wrap_edit_function(edit_func) super(PathEdit, self).__init__( text=path, @@ -331,11 +329,12 @@ class PathEdit(IndicatorLineEdit): horiz_policy=horiz_policy, parent=parent, ) - self.setObjectName("PathEdit") - self.line_edit.setMinimumSize(QSize(300, 0)) + self.setObjectName(type(self).__name__) + self.line_edit.setMinimumSize(QSize(250, 0)) self.path_select = QToolButton(self) - self.path_select.setObjectName("path_select") - self.layout.addWidget(self.path_select) + self.path_select.setObjectName(f"{type(self).__name__}Button") + layout = self.layout() + layout.addWidget(self.path_select) _translate = QCoreApplication.translate self.path_select.setText(_translate("PathEdit", "Browse...")) @@ -361,7 +360,7 @@ class PathEdit(IndicatorLineEdit): self.line_edit.setText(names[0]) self.compl_model.setRootPath(names[0]) - def _wrap_edit_function(self, edit_function: Callable[[str], Tuple[bool, str, str]]): + def __wrap_edit_function(self, edit_function: Callable[[str], Tuple[bool, str, str]]): if edit_function: return lambda text: edit_function(os.path.expanduser(text) if text.startswith("~") else text) @@ -478,9 +477,7 @@ class SelectViewWidget(QWidget): def __init__(self, icon_view: bool): super(SelectViewWidget, self).__init__() self.icon_view = icon_view - self.setStyleSheet( - """QPushButton{border: none; background-color: transparent}""" - ) + self.setStyleSheet("""QPushButton{border: none; background-color: transparent}""") self.icon_view_button = QPushButton() self.list_view = QPushButton() if icon_view: @@ -505,7 +502,9 @@ class SelectViewWidget(QWidget): return self.icon_view def icon(self): - self.icon_view_button.setIcon(qta_icon("mdi.view-grid-outline", "ei.th-large", color="orange")) + self.icon_view_button.setIcon( + qta_icon("mdi.view-grid-outline", "ei.th-large", color="orange") + ) self.list_view.setIcon(qta_icon("fa5s.list", "ei.th-list")) self.icon_view = False self.toggled.emit() @@ -589,9 +588,7 @@ class ButtonLineEdit(QLineEdit): "QLineEdit {padding-right: %dpx; }" % (buttonSize.width() + frameWidth + 1) ) self.setMinimumSize( - max( - self.minimumSizeHint().width(), buttonSize.width() + frameWidth * 2 + 2 - ), + max(self.minimumSizeHint().width(), buttonSize.width() + frameWidth * 2 + 2), max( self.minimumSizeHint().height(), buttonSize.height() + frameWidth * 2 + 2, diff --git a/rare/utils/legendary_utils.py b/rare/utils/legendary_utils.py index 77e09788..a32de593 100644 --- a/rare/utils/legendary_utils.py +++ b/rare/utils/legendary_utils.py @@ -157,6 +157,19 @@ class VerifyWorker(QRunnable): self.signals.summary.emit(len(failed), len(missing), self.app_name) +# FIXME: lk: ah ef me sideways, we can't even import this thing properly +# FIXME: lk: so copy it here +def resolve_aliases(core: LegendaryCore, name): + # make sure aliases exist if not yet created + core.update_aliases(force=False) + name = name.strip() + # resolve alias (if any) to real app name + return core.lgd.config.get( + section='Legendary.aliases', option=name, + fallback=core.lgd.aliases.get(name.lower(), name) + ) + + def import_game(core: LegendaryCore, app_name: str, path: str) -> str: _tr = QCoreApplication.translate logger.info(f"Import {app_name}") diff --git a/rare/utils/models.py b/rare/utils/models.py index 37b152ec..54743e4e 100644 --- a/rare/utils/models.py +++ b/rare/utils/models.py @@ -2,7 +2,7 @@ import os import platform from dataclasses import field, dataclass from multiprocessing import Queue -from typing import Union, List +from typing import Union, List, Optional from PyQt5.QtCore import QObject, pyqtSignal @@ -46,9 +46,9 @@ class InstallDownloadModel: @dataclass class InstallQueueItemModel: - status_q: Queue = None - download: InstallDownloadModel = None - options: InstallOptionsModel = None + status_q: Optional[Queue] = None + download: Optional[InstallDownloadModel] = None + options: Optional[InstallOptionsModel] = None def __bool__(self): return ( @@ -105,12 +105,12 @@ class PathSpec: @dataclass class ApiResults: - game_list: list = None - dlcs: dict = None - bit32_games: list = None - mac_games: list = None - no_asset_games: list = None - saves: list = None + game_list: Optional[list] = None + dlcs: Optional[dict] = None + bit32_games: Optional[list] = None + mac_games: Optional[list] = None + no_asset_games: Optional[list] = None + saves: Optional[list] = None def __bool__(self): return ( diff --git a/rare/utils/paths.py b/rare/utils/paths.py index 971d60f0..ef9284d0 100644 --- a/rare/utils/paths.py +++ b/rare/utils/paths.py @@ -1,13 +1,13 @@ -import os -import pathlib +from pathlib import Path + from PyQt5.QtCore import QStandardPaths -resources_path = pathlib.Path(__file__).absolute().parent.parent.joinpath("resources") -data_dir = os.path.join(QStandardPaths.writableLocation(QStandardPaths.DataLocation), "rare") -cache_dir = os.path.join(QStandardPaths.writableLocation(QStandardPaths.CacheLocation), "rare") -image_dir = os.path.join(data_dir, "images") -tmp_dir = os.path.join(cache_dir, "tmp") +resources_path = Path(__file__).absolute().parent.parent.joinpath("resources") +data_dir = Path(QStandardPaths.writableLocation(QStandardPaths.DataLocation), "rare") +cache_dir = Path(QStandardPaths.writableLocation(QStandardPaths.CacheLocation), "rare") +image_dir = data_dir.joinpath("images") +tmp_dir = cache_dir.joinpath("tmp") for path in (data_dir, cache_dir, image_dir, tmp_dir): - if not os.path.exists(path): - os.makedirs(path) + if not path.exists(): + path.mkdir(parents=True) diff --git a/setup.py b/setup.py index 9e2aac2b..7870e58c 100644 --- a/setup.py +++ b/setup.py @@ -13,13 +13,13 @@ requirements = [ "QtAwesome", "psutil", "pypresence", - 'pywin32; platform_system == "Windows"' + 'pywin32; platform_system == "Windows"', ] optional_reqs = dict( webview=[ 'pywebview[gtk]; platform_system == "Linux"', - 'pywebview[cef]; platform_system == "Windows"' + 'pywebview[cef]; platform_system == "Windows"', ] ) From f91b3651ae01458776ea5e0c7b26e6f0e5cc78fe Mon Sep 17 00:00:00 2001 From: Stelios Tsampas Date: Wed, 4 May 2022 23:44:53 +0300 Subject: [PATCH 2/4] Console: Move Environment Variables UI to a file Signed-off-by: Stelios Tsampas --- rare/components/extra/console.py | 62 ++++--------- rare/components/tabs/games/game_utils.py | 4 +- rare/ui/components/extra/__init__.py | 0 rare/ui/components/extra/console_env.py | 67 ++++++++++++++ rare/ui/components/extra/console_env.ui | 110 +++++++++++++++++++++++ 5 files changed, 194 insertions(+), 49 deletions(-) create mode 100644 rare/ui/components/extra/__init__.py create mode 100644 rare/ui/components/extra/console_env.py create mode 100644 rare/ui/components/extra/console_env.ui diff --git a/rare/components/extra/console.py b/rare/components/extra/console.py index cefaf0ec..c24f83a1 100644 --- a/rare/components/extra/console.py +++ b/rare/components/extra/console.py @@ -1,4 +1,4 @@ -from PyQt5.QtCore import Qt, QCoreApplication, QMetaObject, QProcessEnvironment +from PyQt5.QtCore import QProcessEnvironment from PyQt5.QtGui import QTextCursor, QFont from PyQt5.QtWidgets import ( QPlainTextEdit, @@ -8,20 +8,22 @@ from PyQt5.QtWidgets import ( QVBoxLayout, QHBoxLayout, QSpacerItem, - QSizePolicy, QTableWidget, QTableWidgetItem, QAbstractItemView, QDialogButtonBox, QHeaderView, + QSizePolicy, QTableWidgetItem, QHeaderView, ) +from rare.ui.components.extra.console_env import Ui_ConsoleEnv -class ConsoleWindow(QDialog): + +class Console(QDialog): env: QProcessEnvironment def __init__(self, parent=None): - super(ConsoleWindow, self).__init__(parent=parent) - self.setWindowTitle("Rare Console") + super(Console, self).__init__(parent=parent) + self.setWindowTitle("Rare - Console") self.setGeometry(0, 0, 600, 400) layout = QVBoxLayout() - self.console = Console(self) + self.console = ConsoleEdit(self) layout.addWidget(self.console) button_layout = QHBoxLayout() @@ -43,7 +45,7 @@ class ConsoleWindow(QDialog): self.setLayout(layout) - self.env_variables = EnvVariables(self) + self.env_variables = ConsoleEnv(self) self.env_variables.hide() def save(self): @@ -72,46 +74,12 @@ class ConsoleWindow(QDialog): self.console.error(text + end) -class EnvVariables(QDialog): - - class Ui(object): - def __init__(self, container): - layout = QVBoxLayout() - self.table = QTableWidget(container) - self.table.setColumnCount(2) - - self.table.setHorizontalHeaderItem(0, QTableWidgetItem()) - self.table.setHorizontalHeaderItem(1, QTableWidgetItem()) - font = QFont() - font.setFamily(u"Monospace") - self.table.setFont(font) - self.table.setEditTriggers(QAbstractItemView.NoEditTriggers) - self.table.setAlternatingRowColors(True) - self.table.setSelectionBehavior(QAbstractItemView.SelectRows) - self.table.setSortingEnabled(True) - self.table.setCornerButtonEnabled(True) - self.table.horizontalHeader().setVisible(True) - self.table.horizontalHeader().setStretchLastSection(True) - self.table.verticalHeader().setVisible(False) - self.table.horizontalHeaderItem(0).setText(container.tr("Variable")) - self.table.horizontalHeaderItem(1).setText(container.tr("Value")) - layout.addWidget(self.table) - - self.buttons = QDialogButtonBox(container) - self.buttons.setOrientation(Qt.Horizontal) - self.buttons.setStandardButtons(QDialogButtonBox.Close) - self.buttons.accepted.connect(container.accept) - self.buttons.rejected.connect(container.reject) - layout.addWidget(self.buttons) - - container.setLayout(layout) - container.setObjectName(type(self).__name__) - container.setWindowTitle("Rare Console Environment") - container.setGeometry(0, 0, 600, 400) +class ConsoleEnv(QDialog): def __init__(self, parent=None): - super(EnvVariables, self).__init__(parent=parent) - self.ui = EnvVariables.Ui(self) + super(ConsoleEnv, self).__init__(parent=parent) + self.ui = Ui_ConsoleEnv() + self.ui.setupUi(self) def setTable(self, env: QProcessEnvironment): self.ui.table.clearContents() @@ -124,9 +92,9 @@ class EnvVariables(QDialog): self.ui.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents) -class Console(QPlainTextEdit): +class ConsoleEdit(QPlainTextEdit): def __init__(self, parent=None): - super(Console, self).__init__(parent=parent) + super(ConsoleEdit, self).__init__(parent=parent) self.setReadOnly(True) self.setFont(QFont("monospace")) self._cursor_output = self.textCursor() diff --git a/rare/components/tabs/games/game_utils.py b/rare/components/tabs/games/game_utils.py index faafa6cf..b5f22b92 100644 --- a/rare/components/tabs/games/game_utils.py +++ b/rare/components/tabs/games/game_utils.py @@ -11,7 +11,7 @@ from PyQt5.QtWidgets import QMessageBox, QPushButton from legendary.models.game import LaunchParameters, InstalledGame from rare.components.dialogs.uninstall_dialog import UninstallDialog -from rare.components.extra.console import ConsoleWindow +from rare.components.extra.console import Console from rare.components.tabs.games import CloudSaveUtils from rare.shared import LegendaryCoreSingleton, GlobalSignalsSingleton, ArgumentsSingleton from rare.utils import legendary_utils @@ -58,7 +58,7 @@ class GameUtils(QObject): self.signals = GlobalSignalsSingleton() self.args = ArgumentsSingleton() - self.console = ConsoleWindow() + self.console = Console() self.cloud_save_utils = CloudSaveUtils() self.cloud_save_utils.sync_finished.connect(self.sync_finished) self.game_meta = RareGameMeta() diff --git a/rare/ui/components/extra/__init__.py b/rare/ui/components/extra/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/rare/ui/components/extra/console_env.py b/rare/ui/components/extra/console_env.py new file mode 100644 index 00000000..51c88559 --- /dev/null +++ b/rare/ui/components/extra/console_env.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +# Form implementation generated from reading ui file 'rare/ui/components/extra/console_env.ui' +# +# Created by: PyQt5 UI code generator 5.15.6 +# +# 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. + + +from PyQt5 import QtCore, QtGui, QtWidgets + + +class Ui_ConsoleEnv(object): + def setupUi(self, ConsoleEnv): + ConsoleEnv.setObjectName("ConsoleEnv") + ConsoleEnv.resize(600, 400) + self.layout = QtWidgets.QVBoxLayout(ConsoleEnv) + self.layout.setObjectName("layout") + self.table = QtWidgets.QTableWidget(ConsoleEnv) + font = QtGui.QFont() + font.setFamily("Monospace") + self.table.setFont(font) + self.table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) + self.table.setAlternatingRowColors(True) + self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) + self.table.setCornerButtonEnabled(True) + self.table.setColumnCount(2) + self.table.setObjectName("table") + self.table.setRowCount(0) + item = QtWidgets.QTableWidgetItem() + self.table.setHorizontalHeaderItem(0, item) + item = QtWidgets.QTableWidgetItem() + self.table.setHorizontalHeaderItem(1, item) + self.table.horizontalHeader().setVisible(True) + self.table.horizontalHeader().setStretchLastSection(True) + self.table.verticalHeader().setVisible(False) + self.layout.addWidget(self.table) + self.buttons = QtWidgets.QDialogButtonBox(ConsoleEnv) + self.buttons.setOrientation(QtCore.Qt.Horizontal) + self.buttons.setStandardButtons(QtWidgets.QDialogButtonBox.Close) + self.buttons.setObjectName("buttons") + self.layout.addWidget(self.buttons) + + self.retranslateUi(ConsoleEnv) + self.buttons.accepted.connect(ConsoleEnv.accept) # type: ignore + self.buttons.rejected.connect(ConsoleEnv.reject) # type: ignore + QtCore.QMetaObject.connectSlotsByName(ConsoleEnv) + + def retranslateUi(self, ConsoleEnv): + _translate = QtCore.QCoreApplication.translate + ConsoleEnv.setWindowTitle(_translate("ConsoleEnv", "Rare - Console Environment")) + self.table.setSortingEnabled(True) + item = self.table.horizontalHeaderItem(0) + item.setText(_translate("ConsoleEnv", "Variable")) + item = self.table.horizontalHeaderItem(1) + item.setText(_translate("ConsoleEnv", "Value")) + + +if __name__ == "__main__": + import sys + app = QtWidgets.QApplication(sys.argv) + ConsoleEnv = QtWidgets.QDialog() + ui = Ui_ConsoleEnv() + ui.setupUi(ConsoleEnv) + ConsoleEnv.show() + sys.exit(app.exec_()) diff --git a/rare/ui/components/extra/console_env.ui b/rare/ui/components/extra/console_env.ui new file mode 100644 index 00000000..91283242 --- /dev/null +++ b/rare/ui/components/extra/console_env.ui @@ -0,0 +1,110 @@ + + + ConsoleEnv + + + + 0 + 0 + 600 + 400 + + + + Rare - Console Environment + + + + + + + Monospace + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SelectRows + + + true + + + true + + + 2 + + + true + + + true + + + false + + + + Variable + + + + + Value + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close + + + + + + + + + buttons + accepted() + ConsoleEnv + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttons + rejected() + ConsoleEnv + reject() + + + 316 + 260 + + + 286 + 274 + + + + + From 659c01503258a1bbb8c57b72f049247ddab6c95b Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 5 May 2022 10:47:58 +0300 Subject: [PATCH 3/4] Meta: Add pyproject.toml for black Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com> --- pyproject.toml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..a04c5174 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,11 @@ +[tool.black] +line-length = 110 +target-version = ['py39', 'py310'] +include = '\.py$' +force-exclude = ''' +/( + | rare/ui + | rare/legendary + | rare/resources +)/ +''' From 4ec42a170864a8a210ff48aad1e5a53bf42ea7da Mon Sep 17 00:00:00 2001 From: loathingKernel <142770+loathingKernel@users.noreply.github.com> Date: Thu, 5 May 2022 15:25:28 +0300 Subject: [PATCH 4/4] InstallDialog: Move worker signals inside worker class Signed-off-by: loathingKernel <142770+loathingKernel@users.noreply.github.com> --- rare/components/dialogs/install_dialog.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rare/components/dialogs/install_dialog.py b/rare/components/dialogs/install_dialog.py index 00aefdb4..fb99f448 100644 --- a/rare/components/dialogs/install_dialog.py +++ b/rare/components/dialogs/install_dialog.py @@ -304,16 +304,16 @@ class InstallDialog(QDialog, Ui_InstallDialog): self.cancel_clicked() -class InstallInfoSignals(QObject): - result = pyqtSignal(InstallDownloadModel) - failed = pyqtSignal(str) - finished = pyqtSignal() - - class InstallInfoWorker(QRunnable): + + class Signals(QObject): + result = pyqtSignal(InstallDownloadModel) + failed = pyqtSignal(str) + finished = pyqtSignal() + def __init__(self, core: LegendaryCore, dl_item: InstallQueueItemModel, game: Game = None): super(InstallInfoWorker, self).__init__() - self.signals = InstallInfoSignals() + self.signals = InstallInfoWorker.Signals() self.core = core self.dl_item = dl_item self.is_overlay_install = self.dl_item.options.overlay