From db6dc657919923edd1e12830f2782ee08a3ef6b2 Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Mon, 26 May 2025 21:50:04 -0400 Subject: [PATCH 1/3] Work in progress language selection support Still need to add a popup telling you that NUSGet needs to be restarted after changing the language, and having it load the selected language in the selection menu so that it's checked on launch. --- NUSGet.py | 49 ++++++++++------- modules/config.py | 27 ++++++++++ modules/core.py | 34 +++--------- modules/language.py | 64 ++++++++++++++++++++++ qt/py/ui_MainMenu.py | 88 +++++++++++++++++++++++++------ qt/ui/MainMenu.ui | 108 +++++++++++++++++++++++++++++++++++--- resources/style_dark.qss | 2 +- resources/style_light.qss | 2 +- 8 files changed, 303 insertions(+), 71 deletions(-) create mode 100644 modules/config.py create mode 100644 modules/language.py diff --git a/NUSGet.py b/NUSGet.py index f8620a4..9fadf48 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -22,14 +22,16 @@ import sys import webbrowser from importlib.metadata import version -from PySide6.QtGui import QIcon +from PySide6.QtGui import QActionGroup, QIcon from PySide6.QtWidgets import QApplication, QMainWindow, QMessageBox, QFileDialog, QListView -from PySide6.QtCore import QRunnable, Slot, QThreadPool, Signal, QObject, QLibraryInfo, QTranslator, QLocale +from PySide6.QtCore import QRunnable, Slot, QThreadPool, Signal, QObject, QLibraryInfo from qt.py.ui_AboutDialog import AboutNUSGet from qt.py.ui_MainMenu import Ui_MainWindow +from modules.config import * from modules.core import * +from modules.language import * from modules.theme import is_dark_theme from modules.tree import NUSGetTreeModel, TIDFilterProxyModel from modules.download_batch import run_nus_download_batch @@ -81,9 +83,6 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.ui.download_btn.clicked.connect(self.download_btn_pressed) self.ui.script_btn.clicked.connect(self.script_btn_pressed) self.ui.custom_out_dir_btn.clicked.connect(self.choose_output_dir) - # About and About Qt Buttons - self.ui.actionAbout.triggered.connect(self.about_nusget) - self.ui.actionAbout_Qt.triggered.connect(lambda: QMessageBox.aboutQt(self)) self.ui.pack_archive_checkbox.toggled.connect( lambda: connect_is_enabled_to_checkbox([self.ui.archive_file_entry], self.ui.pack_archive_checkbox)) self.ui.custom_out_dir_checkbox.toggled.connect( @@ -126,10 +125,26 @@ class MainWindow(QMainWindow, Ui_MainWindow): dropdown_delegate = ComboBoxItemDelegate() self.ui.console_select_dropdown.setItemDelegate(dropdown_delegate) self.ui.console_select_dropdown.currentIndexChanged.connect(self.selected_console_changed) - # Fix the annoying background on the help menu items. - self.ui.menuHelp.setWindowFlags(self.ui.menuHelp.windowFlags() | Qt.FramelessWindowHint) - self.ui.menuHelp.setWindowFlags(self.ui.menuHelp.windowFlags() | Qt.NoDropShadowWindowHint) - self.ui.menuHelp.setAttribute(Qt.WA_TranslucentBackground) + # ------------ + # Options Menu + # ------------ + # Fix the annoying background on the option menu items and submenus. + fixup_qmenu_background(self.ui.menu_options) + fixup_qmenu_background(self.ui.menu_options_language) + # Build a QActionGroup so that the language options function like radio buttons, because selecting multiple + # languages at once makes no sense. + language_group = QActionGroup(self) + language_group.setExclusive(True) + for action in self.ui.menu_options_language.actions(): + language_group.addAction(action) + language_group.triggered.connect(lambda lang=action: set_language(config_data, lang.text())) + # --------- + # Help Menu + # --------- + # Same fix for help menu items. + fixup_qmenu_background(self.ui.menu_help) + self.ui.action_about.triggered.connect(self.about_nusget) + self.ui.action_about_qt.triggered.connect(lambda: QMessageBox.aboutQt(self)) # Save some light/dark theme values for later, including the appropriately colored info icon. if is_dark_theme(): bg_color = "#2b2b2b" @@ -137,8 +152,8 @@ class MainWindow(QMainWindow, Ui_MainWindow): else: bg_color = "#e3e3e3" icon = QIcon(os.path.join(os.path.dirname(__file__), "resources", "information_black.svg")) - self.ui.actionAbout.setIcon(icon) - self.ui.actionAbout_Qt.setIcon(icon) + self.ui.action_about.setIcon(icon) + self.ui.action_about_qt.setIcon(icon) # Title tree loading code. Now powered by Models:tm: wii_model = NUSGetTreeModel(wii_database, root_name="Wii Titles") vwii_model = NUSGetTreeModel(vwii_database, root_name="vWii Titles") @@ -683,16 +698,10 @@ if __name__ == "__main__": translator = QTranslator(app) if translator.load(QLocale.system(), 'qtbase', '_', path): app.installTranslator(translator) - translator = QTranslator(app) + # Get the translation path, and call get_language() to find the appropriate translations to load based on the + # settings and system language. path = os.path.join(os.path.dirname(__file__), "resources", "translations") - # Unix-likes and Windows handle this differently, apparently. Unix-likes will try `nusget_xx_XX.qm` and then fall - # back on just `nusget_xx.qm` if the region-specific translation for the language can't be found. On Windows, no - # such fallback exists, and so this code manually implements that fallback, since for languages like Spanish NUSGet - # doesn't use region-specific translations. - locale = QLocale.system() - if not translator.load(QLocale.system(), 'nusget', '_', path): - base_locale = QLocale(locale.language()) - translator.load(base_locale, 'nusget', '_', path) + translator = get_language(QTranslator(app), config_data, path) app.installTranslator(translator) window = MainWindow() diff --git a/modules/config.py b/modules/config.py new file mode 100644 index 0000000..5b97bba --- /dev/null +++ b/modules/config.py @@ -0,0 +1,27 @@ +# "modules/config.py", licensed under the MIT license +# Copyright 2024-2025 NinjaCheetah & Contributors + +import os +import json +import pathlib + +def get_config_file() -> pathlib.Path: + config_dir = pathlib.Path(os.path.join( + os.environ.get('APPDATA') or + os.environ.get('XDG_CONFIG_HOME') or + os.path.join(os.environ['HOME'], '.config'), + "NUSGet" + )) + config_dir.mkdir(exist_ok=True) + return config_dir.joinpath("config.json") + + +def save_config(config_data: dict) -> None: + config_file = get_config_file() + print(f"writing data: {config_data}") + open(config_file, "w").write(json.dumps(config_data, indent=4)) + + +def update_setting(config_data: dict, setting: str, value: any) -> None: + config_data[setting] = value + save_config(config_data) diff --git a/modules/core.py b/modules/core.py index d149293..093b2a7 100644 --- a/modules/core.py +++ b/modules/core.py @@ -1,15 +1,12 @@ # "modules/core.py", licensed under the MIT license # Copyright 2024-2025 NinjaCheetah & Contributors -import os -import json -import pathlib import requests from dataclasses import dataclass from typing import List from PySide6.QtCore import Qt, QSize -from PySide6.QtWidgets import QStyledItemDelegate, QSizePolicy +from PySide6.QtWidgets import QStyledItemDelegate # This is required to make the dropdown look correct with the custom styling. A little fuzzy on the why, but it has to @@ -63,6 +60,13 @@ def connect_is_enabled_to_checkbox(items, chkbox): item.setEnabled(False) +def fixup_qmenu_background(menu): + # These fixes are required to not have a square background poking out from behind the rounded corners of a QMenu. + menu.setWindowFlags(menu.windowFlags() | Qt.FramelessWindowHint) + menu.setWindowFlags(menu.windowFlags() | Qt.NoDropShadowWindowHint) + menu.setAttribute(Qt.WA_TranslucentBackground) + + def check_nusget_updates(app, current_version: str, progress_callback=None) -> str | None: # Simple function to make a request to the GitHub API and then check if the latest available version is newer. gh_api_request = requests.get(url="https://api.github.com/repos/NinjaCheetah/NUSGet/releases/latest", stream=True) @@ -81,25 +85,3 @@ def check_nusget_updates(app, current_version: str, progress_callback=None) -> s return new_version progress_callback.emit(app.translate("MainWindow", "\n\nYou're running the latest release of NUSGet.")) return None - - -def get_config_file() -> pathlib.Path: - config_dir = pathlib.Path(os.path.join( - os.environ.get('APPDATA') or - os.environ.get('XDG_CONFIG_HOME') or - os.path.join(os.environ['HOME'], '.config'), - "NUSGet" - )) - config_dir.mkdir(exist_ok=True) - return config_dir.joinpath("config.json") - - -def save_config(config_data: dict) -> None: - config_file = get_config_file() - print(f"writing data: {config_data}") - open(config_file, "w").write(json.dumps(config_data)) - - -def update_setting(config_data: dict, setting: str, value: any) -> None: - config_data[setting] = value - save_config(config_data) diff --git a/modules/language.py b/modules/language.py new file mode 100644 index 0000000..c246b52 --- /dev/null +++ b/modules/language.py @@ -0,0 +1,64 @@ +# "modules/language.py", licensed under the MIT license +# Copyright 2024-2025 NinjaCheetah & Contributors + +from modules.config import update_setting + +from PySide6.QtCore import QLocale, QTranslator + + +def set_language(config_data: dict, lang: str) -> None: + # Match the selected language. These names will NOT be translated since they represent each language in that + # language, but the "System (Default)" option will, so that will match the default case. + match lang: + case "English": + print("setting language to English") + update_setting(config_data, "language", "en") + case "Español": + print("setting language to Spanish") + update_setting(config_data, "language", "es") + case "Deutsch": + print("setting language to German") + update_setting(config_data, "language", "de") + case "Français": + print("setting language to French") + update_setting(config_data, "language", "fr") + case "Italiano": + print("setting language to Italian") + update_setting(config_data, "language", "it") + case "Norsk": + print("setting language to Norwegian") + update_setting(config_data, "language", "no") + case "Română": + print("setting language to Romanian") + update_setting(config_data, "language", "ro") + case "한국어": + print("setting language to Korean") + update_setting(config_data, "language", "ko") + case _: + print("setting language to system (default)") + update_setting(config_data, "language", "") + + +def get_language(translator: QTranslator, config_data: dict, path: str) -> QTranslator: + try: + lang = config_data["language"] + except KeyError: + lang = "" + # A specific language was set in the app's settings. + if lang != "": + if translator.load(QLocale(lang), 'nusget', '_', path): + return translator + else: + # If we get here, then the saved language is invalid, so clear it and run again to use the system language. + update_setting(config_data, "language", "") + return get_language(translator, config_data, path) + else: + # Unix-likes and Windows handle this differently, apparently. Unix-likes will try `nusget_xx_XX.qm` and then + # fall back on just `nusget_xx.qm` if the region-specific translation for the language can't be found. On + # Windows, no such fallback exists, and so this code manually implements that fallback, since for languages like + # Spanish NUSGet doesn't use region-specific translations. + locale = QLocale.system() + if not translator.load(QLocale.system(), 'nusget', '_', path): + base_locale = QLocale(locale.language()) + translator.load(base_locale, 'nusget', '_', path) + return translator diff --git a/qt/py/ui_MainMenu.py b/qt/py/ui_MainMenu.py index 09a3bc8..f731a80 100644 --- a/qt/py/ui_MainMenu.py +++ b/qt/py/ui_MainMenu.py @@ -31,15 +31,43 @@ class Ui_MainWindow(object): MainWindow.resize(1010, 675) MainWindow.setMinimumSize(QSize(1010, 675)) MainWindow.setMaximumSize(QSize(1010, 675)) - self.actionAbout = QAction(MainWindow) - self.actionAbout.setObjectName(u"actionAbout") + self.action_about = QAction(MainWindow) + self.action_about.setObjectName(u"action_about") icon = QIcon(QIcon.fromTheme(QIcon.ThemeIcon.HelpAbout)) - self.actionAbout.setIcon(icon) - self.actionAbout.setMenuRole(QAction.MenuRole.ApplicationSpecificRole) - self.actionAbout_Qt = QAction(MainWindow) - self.actionAbout_Qt.setObjectName(u"actionAbout_Qt") - self.actionAbout_Qt.setIcon(icon) - self.actionAbout_Qt.setMenuRole(QAction.MenuRole.ApplicationSpecificRole) + self.action_about.setIcon(icon) + self.action_about.setMenuRole(QAction.MenuRole.ApplicationSpecificRole) + self.action_about_qt = QAction(MainWindow) + self.action_about_qt.setObjectName(u"action_about_qt") + self.action_about_qt.setIcon(icon) + self.action_about_qt.setMenuRole(QAction.MenuRole.ApplicationSpecificRole) + self.action_language_system = QAction(MainWindow) + self.action_language_system.setObjectName(u"action_language_system") + self.action_language_system.setCheckable(True) + self.action_language_system.setChecked(False) + self.action_language_english = QAction(MainWindow) + self.action_language_english.setObjectName(u"action_language_english") + self.action_language_english.setCheckable(True) + self.action_language_spanish = QAction(MainWindow) + self.action_language_spanish.setObjectName(u"action_language_spanish") + self.action_language_spanish.setCheckable(True) + self.action_language_german = QAction(MainWindow) + self.action_language_german.setObjectName(u"action_language_german") + self.action_language_german.setCheckable(True) + self.action_language_french = QAction(MainWindow) + self.action_language_french.setObjectName(u"action_language_french") + self.action_language_french.setCheckable(True) + self.action_language_italian = QAction(MainWindow) + self.action_language_italian.setObjectName(u"action_language_italian") + self.action_language_italian.setCheckable(True) + self.action_language_norwegian = QAction(MainWindow) + self.action_language_norwegian.setObjectName(u"action_language_norwegian") + self.action_language_norwegian.setCheckable(True) + self.action_language_romanian = QAction(MainWindow) + self.action_language_romanian.setObjectName(u"action_language_romanian") + self.action_language_romanian.setCheckable(True) + self.action_language_korean = QAction(MainWindow) + self.action_language_korean.setObjectName(u"action_language_korean") + self.action_language_korean.setCheckable(True) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(u"centralwidget") self.horizontalLayout_3 = QHBoxLayout(self.centralwidget) @@ -327,14 +355,29 @@ class Ui_MainWindow(object): self.menubar = QMenuBar(MainWindow) self.menubar.setObjectName(u"menubar") self.menubar.setGeometry(QRect(0, 0, 1010, 30)) - self.menuHelp = QMenu(self.menubar) - self.menuHelp.setObjectName(u"menuHelp") + self.menu_help = QMenu(self.menubar) + self.menu_help.setObjectName(u"menu_help") + self.menu_options = QMenu(self.menubar) + self.menu_options.setObjectName(u"menu_options") + self.menu_options_language = QMenu(self.menu_options) + self.menu_options_language.setObjectName(u"menu_options_language") MainWindow.setMenuBar(self.menubar) - self.menubar.addAction(self.menuHelp.menuAction()) - self.menuHelp.addAction(self.actionAbout) - self.menuHelp.addAction(self.actionAbout_Qt) - self.menuHelp.addSeparator() + self.menubar.addAction(self.menu_options.menuAction()) + self.menubar.addAction(self.menu_help.menuAction()) + self.menu_help.addAction(self.action_about) + self.menu_help.addAction(self.action_about_qt) + self.menu_help.addSeparator() + self.menu_options.addAction(self.menu_options_language.menuAction()) + self.menu_options_language.addAction(self.action_language_system) + self.menu_options_language.addAction(self.action_language_english) + self.menu_options_language.addAction(self.action_language_spanish) + self.menu_options_language.addAction(self.action_language_german) + self.menu_options_language.addAction(self.action_language_french) + self.menu_options_language.addAction(self.action_language_italian) + self.menu_options_language.addAction(self.action_language_norwegian) + self.menu_options_language.addAction(self.action_language_romanian) + self.menu_options_language.addAction(self.action_language_korean) self.retranslateUi(MainWindow) @@ -347,8 +390,17 @@ class Ui_MainWindow(object): def retranslateUi(self, MainWindow): MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) - self.actionAbout.setText(QCoreApplication.translate("MainWindow", u"About NUSGet", None)) - self.actionAbout_Qt.setText(QCoreApplication.translate("MainWindow", u"About Qt", None)) + self.action_about.setText(QCoreApplication.translate("MainWindow", u"About NUSGet", None)) + self.action_about_qt.setText(QCoreApplication.translate("MainWindow", u"About Qt", None)) + self.action_language_system.setText(QCoreApplication.translate("MainWindow", u"System (Default)", None)) + self.action_language_english.setText(QCoreApplication.translate("MainWindow", u"English", None)) + self.action_language_spanish.setText(QCoreApplication.translate("MainWindow", u"Espa\u00f1ol", None)) + self.action_language_german.setText(QCoreApplication.translate("MainWindow", u"Deutsch", None)) + self.action_language_french.setText(QCoreApplication.translate("MainWindow", u"Fran\u00e7ais", None)) + self.action_language_italian.setText(QCoreApplication.translate("MainWindow", u"Italiano", None)) + self.action_language_norwegian.setText(QCoreApplication.translate("MainWindow", u"Norsk", None)) + self.action_language_romanian.setText(QCoreApplication.translate("MainWindow", u"Rom\u00e2n\u0103", None)) + self.action_language_korean.setText(QCoreApplication.translate("MainWindow", u"\ud55c\uad6d\uc5b4", None)) self.tree_filter_input.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Search", None)) self.tree_filter_reset_btn.setText(QCoreApplication.translate("MainWindow", u"Clear", None)) self.platform_tabs.setTabText(self.platform_tabs.indexOf(self.wii_tab), QCoreApplication.translate("MainWindow", u"Wii", None)) @@ -377,6 +429,8 @@ class Ui_MainWindow(object): "li.checked::marker { content: \"\\2612\"; }\n" "\n" "


", None)) - self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) + self.menu_help.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) + self.menu_options.setTitle(QCoreApplication.translate("MainWindow", u"Options", None)) + self.menu_options_language.setTitle(QCoreApplication.translate("MainWindow", u"Language", None)) # retranslateUi diff --git a/qt/ui/MainMenu.ui b/qt/ui/MainMenu.ui index 40bef00..cd7c557 100755 --- a/qt/ui/MainMenu.ui +++ b/qt/ui/MainMenu.ui @@ -472,17 +472,38 @@ li.checked::marker { content: "\2612"; } 30 - + Help - - + + - + + + Options + + + + Language + + + + + + + + + + + + + + + - + @@ -493,7 +514,7 @@ li.checked::marker { content: "\2612"; } QAction::MenuRole::ApplicationSpecificRole - + @@ -504,6 +525,81 @@ li.checked::marker { content: "\2612"; } QAction::MenuRole::ApplicationSpecificRole + + + true + + + false + + + System (Default) + + + + + true + + + English + + + + + true + + + Español + + + + + true + + + Deutsch + + + + + true + + + Français + + + + + true + + + Italiano + + + + + true + + + Norsk + + + + + true + + + Română + + + + + true + + + 한국어 + + diff --git a/resources/style_dark.qss b/resources/style_dark.qss index f2bc95f..96d254f 100644 --- a/resources/style_dark.qss +++ b/resources/style_dark.qss @@ -61,7 +61,7 @@ QMenu { } QMenu::item { - padding: 6px 2px; + padding: 6px 16px 6px 4px; margin: 2px; border-radius: 4px; background-color: transparent; diff --git a/resources/style_light.qss b/resources/style_light.qss index b13d52e..88ccab1 100644 --- a/resources/style_light.qss +++ b/resources/style_light.qss @@ -62,7 +62,7 @@ QMenu { } QMenu::item { - padding: 6px 2px; + padding: 6px 16px 6px 4px; margin: 2px; border-radius: 4px; background-color: transparent; From 109e3dc25ad78dbb289f9b633ba26d8a05a30f8b Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 27 May 2025 14:44:48 -0400 Subject: [PATCH 2/3] Allow setting theme color theme from the menu bar This functions essentially the same as the language selector. The language selector now also checks the currently set option on launch, and both the language and theme selectors spawn a popup prompting the user to restart NUSGet after selecting a new value. --- NUSGet.py | 57 ++++++++++++++++++++++++++++++++++++++++---- modules/language.py | 15 ++++++++++++ modules/theme.py | 40 +++++++++++++++++++++++++++++-- qt/py/ui_MainMenu.py | 23 ++++++++++++++++-- qt/ui/MainMenu.ui | 37 ++++++++++++++++++++++++++-- 5 files changed, 162 insertions(+), 10 deletions(-) diff --git a/NUSGet.py b/NUSGet.py index 9fadf48..7a3371e 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -32,7 +32,7 @@ from qt.py.ui_MainMenu import Ui_MainWindow from modules.config import * from modules.core import * from modules.language import * -from modules.theme import is_dark_theme +from modules.theme import * from modules.tree import NUSGetTreeModel, TIDFilterProxyModel from modules.download_batch import run_nus_download_batch from modules.download_wii import run_nus_download_wii @@ -135,9 +135,38 @@ class MainWindow(QMainWindow, Ui_MainWindow): # languages at once makes no sense. language_group = QActionGroup(self) language_group.setExclusive(True) + current_language = "" + try: + current_language = config_data["language"] + except KeyError: + pass for action in self.ui.menu_options_language.actions(): language_group.addAction(action) - language_group.triggered.connect(lambda lang=action: set_language(config_data, lang.text())) + if current_language != "": + if LANGS[current_language] == action.text(): + action.setChecked(True) + # There is no language set, so check the system language option. + if current_language == "": + self.ui.action_language_system.setChecked(True) + language_group.triggered.connect(lambda lang=action: self.change_language(lang.text())) + # Another QActionGroup used for the theme selector. + theme_group = QActionGroup(self) + theme_group.setExclusive(True) + for action in self.ui.menu_options_theme.actions(): + theme_group.addAction(action) + self.ui.action_theme_system.triggered.connect(lambda: self.change_theme("system")) + self.ui.action_theme_light.triggered.connect(lambda: self.change_theme("light")) + self.ui.action_theme_dark.triggered.connect(lambda: self.change_theme("dark")) + try: + match config_data["theme"]: + case "light": + self.ui.action_theme_light.setChecked(True) + case "dark": + self.ui.action_theme_dark.setChecked(True) + case _: + self.ui.action_theme_system.setChecked(True) + except KeyError: + pass # --------- # Help Menu # --------- @@ -146,7 +175,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): self.ui.action_about.triggered.connect(self.about_nusget) self.ui.action_about_qt.triggered.connect(lambda: QMessageBox.aboutQt(self)) # Save some light/dark theme values for later, including the appropriately colored info icon. - if is_dark_theme(): + if is_dark_theme(config_data): bg_color = "#2b2b2b" icon = QIcon(os.path.join(os.path.dirname(__file__), "resources", "information_white.svg")) else: @@ -622,6 +651,26 @@ class MainWindow(QMainWindow, Ui_MainWindow): about_box = AboutNUSGet([nusget_version, version("libWiiPy"), version("libTWLPy")]) about_box.exec() + def change_language(self, new_lang): + set_language(config_data, new_lang) + msg_box = QMessageBox() + msg_box.setIcon(QMessageBox.Icon.Warning) + msg_box.setStandardButtons(QMessageBox.StandardButton.Ok) + msg_box.setDefaultButton(QMessageBox.StandardButton.Ok) + msg_box.setWindowTitle(app.translate("MainWindow", "Restart Required")) + msg_box.setText(app.translate("MainWindow", "NUSGet must be restarted for the new language to take effect.")) + msg_box.exec() + + def change_theme(self, new_theme): + set_theme(config_data, new_theme) + msg_box = QMessageBox() + msg_box.setIcon(QMessageBox.Icon.Warning) + msg_box.setStandardButtons(QMessageBox.StandardButton.Ok) + msg_box.setDefaultButton(QMessageBox.StandardButton.Ok) + msg_box.setWindowTitle(app.translate("MainWindow", "Restart Required")) + msg_box.setText(app.translate("MainWindow", "NUSGet must be restarted for the new theme to take effect.")) + msg_box.exec() + if __name__ == "__main__": app = QApplication(sys.argv) @@ -684,7 +733,7 @@ if __name__ == "__main__": # NUSGet look nice and pretty. app.setStyle("fusion") theme_sheet = "style_dark.qss" - if is_dark_theme(): + if is_dark_theme(config_data): theme_sheet = "style_dark.qss" else: theme_sheet = "style_light.qss" diff --git a/modules/language.py b/modules/language.py index c246b52..a9259c6 100644 --- a/modules/language.py +++ b/modules/language.py @@ -6,6 +6,18 @@ from modules.config import update_setting from PySide6.QtCore import QLocale, QTranslator +LANGS = { + "en": "English", + "es": "Español", + "de": "Deutsch", + "fr": "Français", + "it": "Italiano", + "no": "Norsk", + "ro": "Românǎ", + "ko": "한국어", +} + + def set_language(config_data: dict, lang: str) -> None: # Match the selected language. These names will NOT be translated since they represent each language in that # language, but the "System (Default)" option will, so that will match the default case. @@ -46,6 +58,9 @@ def get_language(translator: QTranslator, config_data: dict, path: str) -> QTran lang = "" # A specific language was set in the app's settings. if lang != "": + # If the target language is English, then return an empty translator because that's the default. + if lang == "en": + return translator if translator.load(QLocale(lang), 'nusget', '_', path): return translator else: diff --git a/modules/theme.py b/modules/theme.py index b210fac..68357aa 100644 --- a/modules/theme.py +++ b/modules/theme.py @@ -5,6 +5,9 @@ import os import platform import subprocess +from modules.config import update_setting + + def is_dark_theme_windows(): # This has to be here so that Python doesn't try to import it on non-Windows. import winreg @@ -17,6 +20,7 @@ def is_dark_theme_windows(): except Exception: return False + def is_dark_theme_macos(): # macOS is weird. If the dark theme is on, then `defaults read -g AppleInterfaceStyle` returns "Dark". If the light # theme is on, then trying to read this key fails and returns an error instead. @@ -29,6 +33,7 @@ def is_dark_theme_macos(): except Exception: return False + def is_dark_theme_linux(): try: import subprocess @@ -43,7 +48,14 @@ def is_dark_theme_linux(): except Exception: return False -def is_dark_theme(): + +def is_dark_theme(config_data: dict): + # Theming priority order: + # 1. `THEME` environment variable + # 2. Theme saved in config.json + # 3. The system theme + # 4. Light, if all else fails (though personally I'd prefer dark) + # # First, check for an environment variable overriding the theme, and use that if it exists. try: if os.environ["THEME"].lower() == "light": @@ -54,7 +66,18 @@ def is_dark_theme(): print(f"Unknown theme specified: \"{os.environ['THEME']}\"") except KeyError: pass - # If the theme wasn't overridden, then check the current system theme. + # If the theme wasn't overridden, read the user's preference in config.json. + try: + match config_data["theme"]: + case "light": + return False + case "dark": + return True + case _: + pass + except KeyError: + pass + # If a theme wasn't set (or the theme is "system"), then check for the system theme. system = platform.system() if system == "Windows": return is_dark_theme_windows() @@ -62,3 +85,16 @@ def is_dark_theme(): return is_dark_theme_macos() else: return is_dark_theme_linux() + + +def set_theme(config_data: dict, theme: str) -> None: + match theme: + case "light": + print("setting theme to light") + update_setting(config_data, "theme", "light") + case "dark": + print("setting theme to dark") + update_setting(config_data, "theme", "dark") + case _: + print("setting theme to system (default)") + update_setting(config_data, "theme", "") diff --git a/qt/py/ui_MainMenu.py b/qt/py/ui_MainMenu.py index f731a80..3f33184 100644 --- a/qt/py/ui_MainMenu.py +++ b/qt/py/ui_MainMenu.py @@ -68,6 +68,15 @@ class Ui_MainWindow(object): self.action_language_korean = QAction(MainWindow) self.action_language_korean.setObjectName(u"action_language_korean") self.action_language_korean.setCheckable(True) + self.action_theme_system = QAction(MainWindow) + self.action_theme_system.setObjectName(u"action_theme_system") + self.action_theme_system.setCheckable(True) + self.action_theme_light = QAction(MainWindow) + self.action_theme_light.setObjectName(u"action_theme_light") + self.action_theme_light.setCheckable(True) + self.action_theme_dark = QAction(MainWindow) + self.action_theme_dark.setObjectName(u"action_theme_dark") + self.action_theme_dark.setCheckable(True) self.centralwidget = QWidget(MainWindow) self.centralwidget.setObjectName(u"centralwidget") self.horizontalLayout_3 = QHBoxLayout(self.centralwidget) @@ -354,13 +363,15 @@ class Ui_MainWindow(object): MainWindow.setCentralWidget(self.centralwidget) self.menubar = QMenuBar(MainWindow) self.menubar.setObjectName(u"menubar") - self.menubar.setGeometry(QRect(0, 0, 1010, 30)) + self.menubar.setGeometry(QRect(0, 0, 1010, 21)) self.menu_help = QMenu(self.menubar) self.menu_help.setObjectName(u"menu_help") self.menu_options = QMenu(self.menubar) self.menu_options.setObjectName(u"menu_options") self.menu_options_language = QMenu(self.menu_options) self.menu_options_language.setObjectName(u"menu_options_language") + self.menu_options_theme = QMenu(self.menu_options) + self.menu_options_theme.setObjectName(u"menu_options_theme") MainWindow.setMenuBar(self.menubar) self.menubar.addAction(self.menu_options.menuAction()) @@ -369,6 +380,7 @@ class Ui_MainWindow(object): self.menu_help.addAction(self.action_about_qt) self.menu_help.addSeparator() self.menu_options.addAction(self.menu_options_language.menuAction()) + self.menu_options.addAction(self.menu_options_theme.menuAction()) self.menu_options_language.addAction(self.action_language_system) self.menu_options_language.addAction(self.action_language_english) self.menu_options_language.addAction(self.action_language_spanish) @@ -378,6 +390,9 @@ class Ui_MainWindow(object): self.menu_options_language.addAction(self.action_language_norwegian) self.menu_options_language.addAction(self.action_language_romanian) self.menu_options_language.addAction(self.action_language_korean) + self.menu_options_theme.addAction(self.action_theme_system) + self.menu_options_theme.addAction(self.action_theme_light) + self.menu_options_theme.addAction(self.action_theme_dark) self.retranslateUi(MainWindow) @@ -401,6 +416,9 @@ class Ui_MainWindow(object): self.action_language_norwegian.setText(QCoreApplication.translate("MainWindow", u"Norsk", None)) self.action_language_romanian.setText(QCoreApplication.translate("MainWindow", u"Rom\u00e2n\u0103", None)) self.action_language_korean.setText(QCoreApplication.translate("MainWindow", u"\ud55c\uad6d\uc5b4", None)) + self.action_theme_system.setText(QCoreApplication.translate("MainWindow", u"System (Default)", None)) + self.action_theme_light.setText(QCoreApplication.translate("MainWindow", u"Light", None)) + self.action_theme_dark.setText(QCoreApplication.translate("MainWindow", u"Dark", None)) self.tree_filter_input.setPlaceholderText(QCoreApplication.translate("MainWindow", u"Search", None)) self.tree_filter_reset_btn.setText(QCoreApplication.translate("MainWindow", u"Clear", None)) self.platform_tabs.setTabText(self.platform_tabs.indexOf(self.wii_tab), QCoreApplication.translate("MainWindow", u"Wii", None)) @@ -427,10 +445,11 @@ class Ui_MainWindow(object): "hr { height: 1px; border-width: 0; }\n" "li.unchecked::marker { content: \"\\2610\"; }\n" "li.checked::marker { content: \"\\2612\"; }\n" -"\n" +"\n" "


", None)) self.menu_help.setTitle(QCoreApplication.translate("MainWindow", u"Help", None)) self.menu_options.setTitle(QCoreApplication.translate("MainWindow", u"Options", None)) self.menu_options_language.setTitle(QCoreApplication.translate("MainWindow", u"Language", None)) + self.menu_options_theme.setTitle(QCoreApplication.translate("MainWindow", u"Theme", None)) # retranslateUi diff --git a/qt/ui/MainMenu.ui b/qt/ui/MainMenu.ui index cd7c557..e8f38ce 100755 --- a/qt/ui/MainMenu.ui +++ b/qt/ui/MainMenu.ui @@ -435,7 +435,7 @@ p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } -</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> +</style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;"> <p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;"><br /></p></body></html> @@ -469,7 +469,7 @@ li.checked::marker { content: "\2612"; } 0 0 1010 - 30 + 21 @@ -498,7 +498,16 @@ li.checked::marker { content: "\2612"; } + + + Theme + + + + + + @@ -600,6 +609,30 @@ li.checked::marker { content: "\2612"; } 한국어
+ + + true + + + System (Default) + + + + + true + + + Light + + + + + true + + + Dark + + From cffa8f79ffb4731df4d71ff3b170d38ced40233e Mon Sep 17 00:00:00 2001 From: NinjaCheetah <58050615+NinjaCheetah@users.noreply.github.com> Date: Tue, 27 May 2025 21:08:24 -0400 Subject: [PATCH 3/3] Fix background of theme options QMenu --- NUSGet.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/NUSGet.py b/NUSGet.py index 7a3371e..c78122b 100644 --- a/NUSGet.py +++ b/NUSGet.py @@ -131,6 +131,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): # Fix the annoying background on the option menu items and submenus. fixup_qmenu_background(self.ui.menu_options) fixup_qmenu_background(self.ui.menu_options_language) + fixup_qmenu_background(self.ui.menu_options_theme) # Build a QActionGroup so that the language options function like radio buttons, because selecting multiple # languages at once makes no sense. language_group = QActionGroup(self) @@ -166,7 +167,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): case _: self.ui.action_theme_system.setChecked(True) except KeyError: - pass + self.ui.action_theme_system.setChecked(True) # --------- # Help Menu # --------- @@ -658,7 +659,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): msg_box.setStandardButtons(QMessageBox.StandardButton.Ok) msg_box.setDefaultButton(QMessageBox.StandardButton.Ok) msg_box.setWindowTitle(app.translate("MainWindow", "Restart Required")) - msg_box.setText(app.translate("MainWindow", "NUSGet must be restarted for the new language to take effect.")) + msg_box.setText(app.translate("MainWindow", "NUSGet must be restarted for the selected language to take effect.")) msg_box.exec() def change_theme(self, new_theme): @@ -668,7 +669,7 @@ class MainWindow(QMainWindow, Ui_MainWindow): msg_box.setStandardButtons(QMessageBox.StandardButton.Ok) msg_box.setDefaultButton(QMessageBox.StandardButton.Ok) msg_box.setWindowTitle(app.translate("MainWindow", "Restart Required")) - msg_box.setText(app.translate("MainWindow", "NUSGet must be restarted for the new theme to take effect.")) + msg_box.setText(app.translate("MainWindow", "NUSGet must be restarted for the selected theme to take effect.")) msg_box.exec()