diff --git a/src/tagstudio/core/global_settings.py b/src/tagstudio/core/global_settings.py index a1d97c6a..7f3add39 100644 --- a/src/tagstudio/core/global_settings.py +++ b/src/tagstudio/core/global_settings.py @@ -3,7 +3,7 @@ import platform from datetime import datetime -from enum import Enum +from enum import Enum, IntEnum, StrEnum from pathlib import Path from typing import override @@ -13,34 +13,41 @@ from pydantic import BaseModel, Field from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption -if platform.system() == "Windows": - DEFAULT_GLOBAL_SETTINGS_PATH = ( - Path.home() / "Appdata" / "Roaming" / "TagStudio" / "settings.toml" - ) -else: - DEFAULT_GLOBAL_SETTINGS_PATH = Path.home() / ".config" / "TagStudio" / "settings.toml" +DEFAULT_GLOBAL_SETTINGS_PATH = ( + Path.home() / "Appdata" / "Roaming" / "TagStudio" / "settings.toml" + if platform.system() == "Windows" + else Path.home() / ".config" / "TagStudio" / "settings.toml" +) logger = structlog.get_logger(__name__) class TomlEnumEncoder(toml.TomlEncoder): @override - def dump_value(self, v): + def dump_value(self, v): # pyright: ignore[reportMissingParameterType] if isinstance(v, Enum): return super().dump_value(v.value) return super().dump_value(v) -class Theme(Enum): +class Theme(IntEnum): DARK = 0 LIGHT = 1 SYSTEM = 2 DEFAULT = SYSTEM +class Splash(StrEnum): + DEFAULT = "default" + RANDOM = "random" + CLASSIC = "classic" + GOO_GEARS = "goo_gears" + NINETY_FIVE = "95" + + # NOTE: pydantic also has a BaseSettings class (from pydantic-settings) that allows any settings -# properties to be overwritten with environment variables. as tagstudio is not currently using -# environment variables, i did not base it on that, but that may be useful in the future. +# properties to be overwritten with environment variables. As TagStudio is not currently using +# environment variables, this was not based on that, but that may be useful in the future. class GlobalSettings(BaseModel): language: str = Field(default="en") open_last_loaded_on_startup: bool = Field(default=True) @@ -50,8 +57,9 @@ class GlobalSettings(BaseModel): show_filenames_in_grid: bool = Field(default=True) page_size: int = Field(default=100) show_filepath: ShowFilepathOption = Field(default=ShowFilepathOption.DEFAULT) - theme: Theme = Field(default=Theme.SYSTEM) tag_click_action: TagClickActionOption = Field(default=TagClickActionOption.DEFAULT) + theme: Theme = Field(default=Theme.SYSTEM) + splash: Splash = Field(default=Splash.DEFAULT) date_format: str = Field(default="%x") hour_format: bool = Field(default=True) diff --git a/src/tagstudio/qt/modals/settings_panel.py b/src/tagstudio/qt/modals/settings_panel.py index 5c533256..25f1b6d4 100644 --- a/src/tagstudio/qt/modals/settings_panel.py +++ b/src/tagstudio/qt/modals/settings_panel.py @@ -3,7 +3,7 @@ # Created for TagStudio: https://github.com/CyanVoxel/TagStudio -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any from PySide6.QtCore import Qt from PySide6.QtWidgets import ( @@ -18,65 +18,65 @@ from PySide6.QtWidgets import ( ) from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption -from tagstudio.core.global_settings import Theme +from tagstudio.core.global_settings import Splash, Theme from tagstudio.qt.translations import DEFAULT_TRANSLATION, LANGUAGES, Translations from tagstudio.qt.widgets.panel import PanelModal, PanelWidget if TYPE_CHECKING: from tagstudio.qt.ts_qt import QtDriver -FILEPATH_OPTION_MAP: dict[ShowFilepathOption, str] = {} - -THEME_MAP: dict[Theme, str] = {} - -TAG_CLICK_ACTION_MAP: dict[TagClickActionOption, str] = {} - -DATE_FORMAT_MAP: dict[str, str] = { - "%d/%m/%y": "21/08/24", - "%d/%m/%Y": "21/08/2024", - "%d.%m.%y": "21.08.24", - "%d.%m.%Y": "21.08.2024", - "%d-%m-%y": "21-08-24", - "%d-%m-%Y": "21-08-2024", - "%x": "08/21/24", - "%m/%d/%Y": "08/21/2024", - "%m-%d-%y": "08-21-24", - "%m-%d-%Y": "08-21-2024", - "%m.%d.%y": "08.21.24", - "%m.%d.%Y": "08.21.2024", - "%Y/%m/%d": "2024/08/21", - "%Y-%m-%d": "2024-08-21", - "%Y.%m.%d": "2024.08.21", -} - class SettingsPanel(PanelWidget): driver: "QtDriver" + filepath_option_map: dict[ShowFilepathOption, str] = { + ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"], + ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations["settings.filepath.option.relative"], + ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"], + } + + theme_map: dict[Theme, str] = { + Theme.SYSTEM: Translations["settings.theme.system"], + Theme.DARK: Translations["settings.theme.dark"], + Theme.LIGHT: Translations["settings.theme.light"], + } + + splash_map: dict[Splash, str] = { + Splash.DEFAULT: Translations["settings.splash.option.default"], + Splash.RANDOM: Translations["settings.splash.option.random"], + Splash.CLASSIC: Translations["settings.splash.option.classic"], + Splash.GOO_GEARS: Translations["settings.splash.option.goo_gears"], + Splash.NINETY_FIVE: Translations["settings.splash.option.ninety_five"], + } + + tag_click_action_map: dict[TagClickActionOption, str] = { + TagClickActionOption.OPEN_EDIT: Translations["settings.tag_click_action.open_edit"], + TagClickActionOption.SET_SEARCH: Translations["settings.tag_click_action.set_search"], + TagClickActionOption.ADD_TO_SEARCH: Translations["settings.tag_click_action.add_to_search"], + } + + date_format_map: dict[str, str] = { + "%d/%m/%y": "21/08/24", + "%d/%m/%Y": "21/08/2024", + "%d.%m.%y": "21.08.24", + "%d.%m.%Y": "21.08.2024", + "%d-%m-%y": "21-08-24", + "%d-%m-%Y": "21-08-2024", + "%x": "08/21/24", + "%m/%d/%Y": "08/21/2024", + "%m-%d-%y": "08-21-24", + "%m-%d-%Y": "08-21-2024", + "%m.%d.%y": "08.21.24", + "%m.%d.%Y": "08.21.2024", + "%Y/%m/%d": "2024/08/21", + "%Y-%m-%d": "2024-08-21", + "%Y.%m.%d": "2024.08.21", + } + def __init__(self, driver: "QtDriver"): super().__init__() # set these "constants" because language will be loaded from config shortly after startup # and we want to use the current language for the dropdowns - global FILEPATH_OPTION_MAP, THEME_MAP, TAG_CLICK_ACTION_MAP - FILEPATH_OPTION_MAP = { - ShowFilepathOption.SHOW_FULL_PATHS: Translations["settings.filepath.option.full"], - ShowFilepathOption.SHOW_RELATIVE_PATHS: Translations[ - "settings.filepath.option.relative" - ], - ShowFilepathOption.SHOW_FILENAMES_ONLY: Translations["settings.filepath.option.name"], - } - THEME_MAP = { - Theme.DARK: Translations["settings.theme.dark"], - Theme.LIGHT: Translations["settings.theme.light"], - Theme.SYSTEM: Translations["settings.theme.system"], - } - TAG_CLICK_ACTION_MAP = { - TagClickActionOption.OPEN_EDIT: Translations["settings.tag_click_action.open_edit"], - TagClickActionOption.SET_SEARCH: Translations["settings.tag_click_action.set_search"], - TagClickActionOption.ADD_TO_SEARCH: Translations[ - "settings.tag_click_action.add_to_search" - ], - } self.driver = driver self.setMinimumSize(400, 300) @@ -84,6 +84,8 @@ class SettingsPanel(PanelWidget): self.root_layout = QVBoxLayout(self) self.root_layout.setContentsMargins(0, 6, 0, 0) + self.library_settings_container = QWidget() + # Tabs self.tab_widget = QTabWidget() @@ -135,6 +137,7 @@ class SettingsPanel(PanelWidget): Translations["settings.open_library_on_start"], self.open_last_lib_checkbox ) + # Generate Thumbnails self.generate_thumbs = QCheckBox() self.generate_thumbs.setChecked(self.driver.settings.generate_thumbs) form_layout.addRow(Translations["settings.generate_thumbs"], self.generate_thumbs) @@ -165,49 +168,61 @@ class SettingsPanel(PanelWidget): # Show Filepath self.filepath_combobox = QComboBox() - for k in FILEPATH_OPTION_MAP: - self.filepath_combobox.addItem(FILEPATH_OPTION_MAP[k], k) + for k in SettingsPanel.filepath_option_map: + self.filepath_combobox.addItem(SettingsPanel.filepath_option_map[k], k) filepath_option: ShowFilepathOption = self.driver.settings.show_filepath - if filepath_option not in FILEPATH_OPTION_MAP: + if filepath_option not in SettingsPanel.filepath_option_map: filepath_option = ShowFilepathOption.DEFAULT self.filepath_combobox.setCurrentIndex( - list(FILEPATH_OPTION_MAP.keys()).index(filepath_option) + list(SettingsPanel.filepath_option_map.keys()).index(filepath_option) ) form_layout.addRow(Translations["settings.filepath.label"], self.filepath_combobox) - # Dark Mode - self.theme_combobox = QComboBox() - for k in THEME_MAP: - self.theme_combobox.addItem(THEME_MAP[k], k) - theme = self.driver.settings.theme - if theme not in THEME_MAP: - theme = Theme.DEFAULT - self.theme_combobox.setCurrentIndex(list(THEME_MAP.keys()).index(theme)) - self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label) - form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox) - # Tag Click Action self.tag_click_action_combobox = QComboBox() - for k in TAG_CLICK_ACTION_MAP: - self.tag_click_action_combobox.addItem(TAG_CLICK_ACTION_MAP[k], k) + for k in SettingsPanel.tag_click_action_map: + self.tag_click_action_combobox.addItem(SettingsPanel.tag_click_action_map[k], k) tag_click_action = self.driver.settings.tag_click_action - if tag_click_action not in TAG_CLICK_ACTION_MAP: + if tag_click_action not in SettingsPanel.tag_click_action_map: tag_click_action = TagClickActionOption.DEFAULT self.tag_click_action_combobox.setCurrentIndex( - list(TAG_CLICK_ACTION_MAP.keys()).index(tag_click_action) + list(SettingsPanel.tag_click_action_map.keys()).index(tag_click_action) ) form_layout.addRow( Translations["settings.tag_click_action.label"], self.tag_click_action_combobox ) + # Dark Mode + self.theme_combobox = QComboBox() + for k in SettingsPanel.theme_map: + self.theme_combobox.addItem(SettingsPanel.theme_map[k], k) + theme = self.driver.settings.theme + if theme not in SettingsPanel.theme_map: + theme = Theme.DEFAULT + self.theme_combobox.setCurrentIndex(list(SettingsPanel.theme_map.keys()).index(theme)) + self.theme_combobox.currentIndexChanged.connect(self.__update_restart_label) + form_layout.addRow(Translations["settings.theme.label"], self.theme_combobox) + + # Splash Screen + self.splash_combobox = QComboBox() + for k in SettingsPanel.splash_map: + self.splash_combobox.addItem(SettingsPanel.splash_map[k], k) + splash = self.driver.settings.splash + if splash not in SettingsPanel.splash_map: + splash = Splash.DEFAULT + self.splash_combobox.setCurrentIndex(list(SettingsPanel.splash_map.keys()).index(splash)) + form_layout.addRow(Translations["settings.splash.label"], self.splash_combobox) + # Date Format self.dateformat_combobox = QComboBox() - for k in DATE_FORMAT_MAP: - self.dateformat_combobox.addItem(DATE_FORMAT_MAP[k], k) + for k in SettingsPanel.date_format_map: + self.dateformat_combobox.addItem(SettingsPanel.date_format_map[k], k) dateformat: str = self.driver.settings.date_format - if dateformat not in DATE_FORMAT_MAP: + if dateformat not in SettingsPanel.date_format_map: dateformat = "%x" - self.dateformat_combobox.setCurrentIndex(list(DATE_FORMAT_MAP.keys()).index(dateformat)) + self.dateformat_combobox.setCurrentIndex( + list(SettingsPanel.date_format_map.keys()).index(dateformat) + ) self.dateformat_combobox.currentIndexChanged.connect(self.__update_restart_label) form_layout.addRow(Translations["settings.dateformat.label"], self.dateformat_combobox) @@ -221,8 +236,8 @@ class SettingsPanel(PanelWidget): self.zeropadding_checkbox.setChecked(self.driver.settings.zero_padding) form_layout.addRow(Translations["settings.zeropadding.label"], self.zeropadding_checkbox) - def __build_library_settings(self): - self.library_settings_container = QWidget() + # TODO: Implement Library Settings + def __build_library_settings(self): # pyright: ignore[reportUnusedFunction] form_layout = QFormLayout(self.library_settings_container) form_layout.setContentsMargins(6, 6, 6, 6) @@ -232,7 +247,7 @@ class SettingsPanel(PanelWidget): def __get_language(self) -> str: return list(LANGUAGES.values())[self.language_combobox.currentIndex()] - def get_settings(self) -> dict: + def get_settings(self) -> dict[str, Any]: # pyright: ignore[reportExplicitAny] return { "language": self.__get_language(), "open_last_loaded_on_startup": self.open_last_lib_checkbox.isChecked(), @@ -246,6 +261,7 @@ class SettingsPanel(PanelWidget): "date_format": self.dateformat_combobox.currentData(), "hour_format": self.hourformat_checkbox.isChecked(), "zero_padding": self.zeropadding_checkbox.isChecked(), + "splash": self.splash_combobox.currentData(), } def update_settings(self, driver: "QtDriver"): @@ -263,6 +279,7 @@ class SettingsPanel(PanelWidget): driver.settings.date_format = settings["date_format"] driver.settings.hour_format = settings["hour_format"] driver.settings.zero_padding = settings["zero_padding"] + driver.settings.splash = settings["splash"] driver.settings.save() diff --git a/src/tagstudio/qt/resources.json b/src/tagstudio/qt/resources.json index 34b46ef7..f7bae77c 100644 --- a/src/tagstudio/qt/resources.json +++ b/src/tagstudio/qt/resources.json @@ -7,6 +7,10 @@ "path": "qt/images/splash/goo_gears.png", "mode": "qpixmap" }, + "splash_95": { + "path": "qt/images/splash/95.png", + "mode": "qpixmap" + }, "icon": { "path": "icon.png", "mode": "pil" diff --git a/src/tagstudio/qt/splash.py b/src/tagstudio/qt/splash.py index c6b778f7..91ae7f99 100644 --- a/src/tagstudio/qt/splash.py +++ b/src/tagstudio/qt/splash.py @@ -4,6 +4,7 @@ import math +import random import structlog from PySide6.QtCore import QRect, Qt @@ -11,12 +12,13 @@ from PySide6.QtGui import QColor, QFont, QPainter, QPen, QPixmap from PySide6.QtWidgets import QSplashScreen, QWidget from tagstudio.core.constants import VERSION, VERSION_BRANCH +from tagstudio.core.global_settings import Splash from tagstudio.qt.resource_manager import ResourceManager logger = structlog.get_logger(__name__) -class Splash: +class SplashScreen: """The custom splash screen widget for TagStudio.""" COPYRIGHT_YEARS: str = "2021-2025" @@ -24,11 +26,7 @@ class Splash: VERSION_STR: str = ( f"Version {VERSION} {(' (' + VERSION_BRANCH + ')') if VERSION_BRANCH else ''}" ) - - SPLASH_CLASSIC: str = "classic" - SPLASH_GOO_GEARS: str = "goo_gears" - SPLASH_95: str = "95" - DEFAULT_SPLASH: str = SPLASH_GOO_GEARS + DEFAULT_SPLASH = Splash.GOO_GEARS def __init__( self, @@ -41,11 +39,19 @@ class Splash: self.screen_width = screen_width self.ratio: float = device_ratio self.splash_screen: QSplashScreen | None = None - self.splash_name: str = splash_name if splash_name else Splash.DEFAULT_SPLASH + if not splash_name or splash_name == Splash.DEFAULT: + self.splash_name: str = SplashScreen.DEFAULT_SPLASH + elif splash_name == Splash.RANDOM: + splash_list = list(Splash) + splash_list.remove(Splash.DEFAULT) + splash_list.remove(Splash.RANDOM) + self.splash_name = random.choice(splash_list) + else: + self.splash_name = splash_name def get_pixmap(self) -> QPixmap: """Get the pixmap used for the splash screen.""" - pixmap: QPixmap | None = self.rm.get(f"splash_{self.splash_name}") + pixmap: QPixmap | None = self.rm.get(f"splash_{self.splash_name}") # pyright: ignore[reportAssignmentType] if not pixmap: logger.error("[Splash] Splash screen not found:", splash_name=self.splash_name) pixmap = QPixmap(960, 540) @@ -55,10 +61,12 @@ class Splash: match painter.font().family(): case "Segoe UI": point_size_scale = 0.75 + case _: + pass # TODO: Store any differing data elsewhere and load dynamically instead of hardcoding. match self.splash_name: - case Splash.SPLASH_CLASSIC: + case Splash.CLASSIC: # Copyright font = painter.font() font.setPointSize(math.floor(22 * point_size_scale)) @@ -68,7 +76,7 @@ class Splash: painter.drawText( QRect(0, -50, 960, 540), int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter), - Splash.COPYRIGHT_STR, + SplashScreen.COPYRIGHT_STR, ) # Version pen = QPen(QColor("#809782ff")) @@ -76,10 +84,10 @@ class Splash: painter.drawText( QRect(0, -25, 960, 540), int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter), - Splash.VERSION_STR, + SplashScreen.VERSION_STR, ) - case Splash.SPLASH_GOO_GEARS: + case Splash.GOO_GEARS: # Copyright font = painter.font() font.setPointSize(math.floor(22 * point_size_scale)) @@ -88,7 +96,7 @@ class Splash: painter.setPen(pen) painter.drawText( QRect(40, 450, 960, 540), - Splash.COPYRIGHT_STR, + SplashScreen.COPYRIGHT_STR, ) # Version font = painter.font() @@ -98,10 +106,10 @@ class Splash: painter.setPen(pen) painter.drawText( QRect(40, 475, 960, 540), - Splash.VERSION_STR, + SplashScreen.VERSION_STR, ) - case Splash.SPLASH_95: + case Splash.NINETY_FIVE: # Copyright font = QFont() font.setFamily("Times") @@ -114,7 +122,7 @@ class Splash: painter.drawText( QRect(88, -25, 960, 540), int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft), - Splash.COPYRIGHT_STR, + SplashScreen.COPYRIGHT_STR, ) # Version font.setPointSize(math.floor(22 * point_size_scale)) @@ -124,7 +132,7 @@ class Splash: painter.drawText( QRect(-30, 25, 960, 540), int(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight), - Splash.VERSION_STR, + SplashScreen.VERSION_STR, ) case _: diff --git a/src/tagstudio/qt/ts_qt.py b/src/tagstudio/qt/ts_qt.py index 7830bcef..1f63d536 100644 --- a/src/tagstudio/qt/ts_qt.py +++ b/src/tagstudio/qt/ts_qt.py @@ -91,7 +91,7 @@ from tagstudio.qt.modals.tag_database import TagDatabasePanel from tagstudio.qt.modals.tag_search import TagSearchModal from tagstudio.qt.platform_strings import trash_term from tagstudio.qt.resource_manager import ResourceManager -from tagstudio.qt.splash import Splash +from tagstudio.qt.splash import SplashScreen from tagstudio.qt.translations import Translations from tagstudio.qt.widgets.item_thumb import BadgeType, ItemThumb from tagstudio.qt.widgets.migration_modal import JsonMigrationModal @@ -327,10 +327,10 @@ class QtDriver(DriverMixin, QObject): self.main_window.dragMoveEvent = self.drag_move_event self.main_window.dropEvent = self.drop_event - self.splash: Splash = Splash( + self.splash: SplashScreen = SplashScreen( resource_manager=self.rm, screen_width=QGuiApplication.primaryScreen().geometry().width(), - splash_name="", # TODO: Get splash name from config + splash_name=self.settings.splash, device_ratio=self.main_window.devicePixelRatio(), ) self.splash.show() diff --git a/src/tagstudio/resources/qt/images/splash/95.png b/src/tagstudio/resources/qt/images/splash/95.png new file mode 100644 index 00000000..072ae8da Binary files /dev/null and b/src/tagstudio/resources/qt/images/splash/95.png differ diff --git a/src/tagstudio/resources/translations/en.json b/src/tagstudio/resources/translations/en.json index 0a9ae6a9..b73512da 100644 --- a/src/tagstudio/resources/translations/en.json +++ b/src/tagstudio/resources/translations/en.json @@ -273,6 +273,12 @@ "settings.restart_required": "Please restart TagStudio for changes to take effect.", "settings.show_filenames_in_grid": "Show Filenames in Grid", "settings.show_recent_libraries": "Show Recent Libraries", + "settings.splash.label": "Splash Screen", + "settings.splash.option.classic": "Classic (9.0)", + "settings.splash.option.default": "Default", + "settings.splash.option.goo_gears": "Open Source (9.4)", + "settings.splash.option.ninety_five": "'95 (9.5)", + "settings.splash.option.random": "Random", "settings.tag_click_action.add_to_search": "Add Tag to Search", "settings.tag_click_action.label": "Tag Click Action", "settings.tag_click_action.open_edit": "Edit Tag",