diff --git a/README.md b/README.md index 2d685ee91..955e4c20a 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ [![Ruff](https://github.com/TagStudioDev/TagStudio/actions/workflows/ruff.yaml/badge.svg)](https://github.com/TagStudioDev/TagStudio/actions/workflows/ruff.yaml)

- +

TagStudio is a photo & file organization application with an underlying tag-based system that focuses on giving freedom and flexibility to the user. No proprietary programs or formats, no sea of sidecar files, and no complete upheaval of your filesystem structure. **Read the documentation and more at [docs.tagstud.io](https://docs.tagstud.io)!** diff --git a/REUSE.toml b/REUSE.toml index 1897ada11..88839d647 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -33,17 +33,18 @@ SPDX-License-Identifier = "GPL-3.0-or-later" [[annotations]] path = [ + "src/tagstudio/resources/qt/images/bxs-clipboard-regular.png", "src/tagstudio/resources/qt/images/bxs-left-arrow.png", + "src/tagstudio/resources/qt/images/bxs-pencil-solid.png", "src/tagstudio/resources/qt/images/bxs-right-arrow.png", + "src/tagstudio/resources/qt/images/bxs-trash-solid.png", + "src/tagstudio/resources/qt/images/bxs-volume-full-solid.png", "src/tagstudio/resources/qt/images/file_icons/database.png", ] SPDX-FileCopyrightText = "(c) 2026 Boxicons" SPDX-License-Identifier = "MIT" [[annotations]] -path = [ - "src/tagstudio/resources/qt/images/volume.svg", - "src/tagstudio/resources/qt/images/volume_mute.svg", -] +path = ["src/tagstudio/resources/qt/images/dupe_file_stat.png"] SPDX-FileCopyrightText = "(c) github:google/material-design-icons Contributors" SPDX-License-Identifier = "Apache-2.0" diff --git a/docs/assets/favicon.ico b/docs/assets/favicon.ico new file mode 120000 index 000000000..5296dbb4d --- /dev/null +++ b/docs/assets/favicon.ico @@ -0,0 +1 @@ +/Users/cyanvoxel/Local/Dev/TagStudio/src/tagstudio/resources/icon.ico \ No newline at end of file diff --git a/docs/assets/github_header.png b/docs/assets/github_header.png deleted file mode 100644 index 28132f659..000000000 Binary files a/docs/assets/github_header.png and /dev/null differ diff --git a/docs/assets/icon.ico b/docs/assets/icon.ico deleted file mode 100644 index 71335bd51..000000000 Binary files a/docs/assets/icon.ico and /dev/null differ diff --git a/docs/assets/icon.png b/docs/assets/icon.png deleted file mode 100644 index dd3d2772e..000000000 Binary files a/docs/assets/icon.png and /dev/null differ diff --git a/docs/assets/icon_mono.svg b/docs/assets/icon_mono.svg index 67060eb03..db328eab3 100644 --- a/docs/assets/icon_mono.svg +++ b/docs/assets/icon_mono.svg @@ -1,10 +1,7 @@ - - - - - - + + + diff --git a/docs/assets/tag_bubbles.png b/docs/assets/tag_bubbles.png index c7e7a96ca..263e562a1 100644 Binary files a/docs/assets/tag_bubbles.png and b/docs/assets/tag_bubbles.png differ diff --git a/docs/assets/tagstudio_logo-text_color.png b/docs/assets/tagstudio_logo-text_color.png deleted file mode 100644 index b62861862..000000000 Binary files a/docs/assets/tagstudio_logo-text_color.png and /dev/null differ diff --git a/docs/assets/tagstudio_logo-text_color.png b/docs/assets/tagstudio_logo-text_color.png new file mode 120000 index 000000000..37ad1503f --- /dev/null +++ b/docs/assets/tagstudio_logo-text_color.png @@ -0,0 +1 @@ +/Users/cyanvoxel/Local/Dev/TagStudio/src/tagstudio/resources/qt/images/tagstudio_logo-text_color.png \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index 583e0cd56..b28d82356 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -85,7 +85,7 @@ theme: icon: material/lightbulb-night-outline name: Switch to System Preference logo: assets/icon_mono.svg - favicon: assets/icon.ico + favicon: assets/favicon.ico font: code: Jetbrains Mono language: en diff --git a/src/tagstudio/core/constants.py b/src/tagstudio/core/constants.py index 4cd6f5829..d0e4d6f3d 100644 --- a/src/tagstudio/core/constants.py +++ b/src/tagstudio/core/constants.py @@ -4,6 +4,10 @@ VERSION: str = "9.5.7" # Major.Minor.Patch VERSION_BRANCH: str = "" # Usually "" or "Pre-Release" +COPYRIGHT_YEARS: str = "2021-2026" +COPYRIGHT: str = f"© {COPYRIGHT_YEARS} Travis Abendshien & TagStudio Contributors" +COPYRIGHT_COMPACT: str = f"© {COPYRIGHT_YEARS} Travis Abendshien\n& TagStudio Contributors" + GITHUB_RELEASE_URL = "https://github.com/TagStudioDev/TagStudio/releases/latest" # The folder & file names where TagStudio keeps its data relative to a library. diff --git a/src/tagstudio/core/library/alchemy/enums.py b/src/tagstudio/core/library/alchemy/enums.py index 349d0e9e2..7e16199c9 100644 --- a/src/tagstudio/core/library/alchemy/enums.py +++ b/src/tagstudio/core/library/alchemy/enums.py @@ -66,7 +66,7 @@ def get_color_from_str(color_name: str) -> "TagColorEnum": class ItemType(enum.Enum): ENTRY = 0 - COLLATION = 1 + ENTRY_GROUP = 1 TAG_GROUP = 2 diff --git a/src/tagstudio/qt/global_settings.py b/src/tagstudio/qt/global_settings.py index 94be1f076..e82e96480 100644 --- a/src/tagstudio/qt/global_settings.py +++ b/src/tagstudio/qt/global_settings.py @@ -43,6 +43,7 @@ class Splash(StrEnum): CLASSIC = "classic" GOO_GEARS = "goo_gears" NINETY_FIVE = "95" + AURORA = "aurora" class TomlEnumEncoder(toml.TomlEncoder): diff --git a/src/tagstudio/qt/helpers/color_overlay.py b/src/tagstudio/qt/helpers/color_overlay.py index 0d276210d..3a45aee8b 100644 --- a/src/tagstudio/qt/helpers/color_overlay.py +++ b/src/tagstudio/qt/helpers/color_overlay.py @@ -16,11 +16,14 @@ _THEME_LIGHT_BG: str = "#FFFFFF55" -def theme_fg_overlay(image: Image.Image, use_alpha: bool = True) -> Image.Image: +def theme_fg_overlay( + image: Image.Image, inverse: bool = False, use_alpha: bool = True +) -> Image.Image: """Overlay the foreground theme color onto an image. Args: image (Image): The PIL Image object to apply an overlay to. + inverse (bool): Option inverse the overlay color relative to the current theme. use_alpha (bool): Option to retain the base image's alpha value when applying the overlay. """ dark_fg: str = _THEME_DARK_FG[:-2] if not use_alpha else _THEME_DARK_FG @@ -29,6 +32,8 @@ def theme_fg_overlay(image: Image.Image, use_alpha: bool = True) -> Image.Image: overlay_color = ( dark_fg if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark else light_fg ) + if inverse: + overlay_color = light_fg if overlay_color == dark_fg else dark_fg im = Image.new(mode="RGBA", size=image.size, color=overlay_color) return _apply_overlay(image, im) diff --git a/src/tagstudio/qt/mixed/about_modal.py b/src/tagstudio/qt/mixed/about_modal.py index 3d1092f1d..7429d35f0 100644 --- a/src/tagstudio/qt/mixed/about_modal.py +++ b/src/tagstudio/qt/mixed/about_modal.py @@ -3,10 +3,12 @@ import math +from pathlib import Path +from shutil import which from PIL import ImageQt -from PySide6.QtCore import Qt -from PySide6.QtGui import QGuiApplication, QPixmap +from PySide6.QtCore import QSize, Qt +from PySide6.QtGui import QGuiApplication, QPalette, QPixmap from PySide6.QtWidgets import ( QFormLayout, QHBoxLayout, @@ -17,7 +19,7 @@ QWidget, ) -from tagstudio.core.constants import VERSION, VERSION_BRANCH +from tagstudio.core.constants import COPYRIGHT, VERSION, VERSION_BRANCH from tagstudio.core.enums import Theme from tagstudio.core.ts_core import TagStudioCore from tagstudio.core.utils.types import unwrap @@ -25,10 +27,16 @@ from tagstudio.qt.previews.vendored import ffmpeg from tagstudio.qt.resource_manager import ResourceManager from tagstudio.qt.translations import Translations +from tagstudio.qt.utils.file_opener import open_file +from tagstudio.qt.views.clickable_label import ClickableLabel class AboutModal(QWidget): - def __init__(self, config_path): + """Modal window showing information about the TagStudio application.""" + + VERSION_STR: str = f"{Translations['about.version']} {VERSION} {(' (' + VERSION_BRANCH + ')') if VERSION_BRANCH else ''}" # noqa: E501 + + def __init__(self, config_path: Path | str): super().__init__() self.setWindowTitle(Translations["about.title"]) @@ -45,12 +53,14 @@ def __init__(self, config_path): "font-weight: 500;" "padding: 2px;" ) + self.setStyleSheet("QLabel {color: white}") self.setWindowModality(Qt.WindowModality.ApplicationModal) - self.setMinimumSize(360, 540) - self.setMaximumSize(600, 600) + self.setFixedWidth(600) + self.setMinimumHeight(600) + self.setMaximumHeight(900) self.root_layout = QVBoxLayout(self) - self.root_layout.setContentsMargins(0, 12, 0, 0) + self.root_layout.setContentsMargins(0, 100, 0, 0) self.root_layout.setSpacing(0) self.root_layout.setAlignment(Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignCenter) @@ -59,24 +69,30 @@ def __init__(self, config_path): self.content_layout.setContentsMargins(12, 12, 12, 12) self.content_layout.setSpacing(12) - # TagStudio Icon Logo -------------------------------------------------- + # TagStudio Logo ------------------------------------------------------- self.logo_widget = QLabel() - self.logo_pixmap = QPixmap.fromImage(ImageQt.ImageQt(self.rm.get("icon"))) + self.logo_pixmap = QPixmap.fromImage(ImageQt.ImageQt(self.rm.ts_logo_text_color)) self.logo_pixmap.setDevicePixelRatio(self.devicePixelRatio()) self.logo_pixmap = self.logo_pixmap.scaledToWidth( - math.floor(128 * self.devicePixelRatio()), Qt.TransformationMode.SmoothTransformation + math.floor(384 * self.devicePixelRatio()), Qt.TransformationMode.SmoothTransformation ) self.logo_widget.setPixmap(self.logo_pixmap) self.logo_widget.setContentsMargins(0, 0, 0, 0) self.logo_widget.setAlignment(Qt.AlignmentFlag.AlignCenter) - # Title ---------------------------------------------------------------- - branch: str = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else "" - self.title_label = QLabel(f"

TagStudio Alpha {VERSION}{branch}

") - self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + # Version -------------------------------------------------------------- + self.version_label = QLabel(f"

{AboutModal.VERSION_STR}

") + self.version_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + # self.version_label.setStyleSheet("QLabel {color: #9782ff}") + + # Copyright ------------------------------------------------------------ + self.copyright_label = QLabel(COPYRIGHT) + self.copyright_label.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.copyright_label.setStyleSheet("QLabel {color: #809782ff}") # Description ---------------------------------------------------------- self.desc_label = QLabel(Translations["about.description"]) + self.desc_label.setMaximumWidth(500) self.desc_label.setAlignment(Qt.AlignmentFlag.AlignCenter) self.desc_label.setWordWrap(True) self.desc_label.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) @@ -85,6 +101,7 @@ def __init__(self, config_path): ff_version = ffmpeg.version() red = get_ui_color(ColorType.PRIMARY, UiColor.RED) green = get_ui_color(ColorType.PRIMARY, UiColor.GREEN) + amber = get_ui_color(ColorType.PRIMARY, UiColor.AMBER) missing = Translations["generic.missing"] found = Translations["about.module.found"] @@ -100,12 +117,16 @@ def __init__(self, config_path): f'{found} (' + ff_version["ffprobe"] + ")" ) + ripgrep_status = f'{missing}' + if which("rg") is not None: + ripgrep_status = f'{found}' + self.system_info_widget = QWidget() self.system_info_layout = QFormLayout(self.system_info_widget) self.system_info_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight) # Version - version_title = QLabel("Version") + version_title = QLabel(Translations["about.version"]) most_recent_release = unwrap(TagStudioCore.get_most_recent_release_version(), "UNKNOWN") version_content_style = self.form_content_style if most_recent_release == VERSION: @@ -117,34 +138,61 @@ def __init__(self, config_path): version_content.setMaximumWidth(version_content.sizeHint().width()) self.system_info_layout.addRow(version_title, version_content) - # License - license_title = QLabel(f"{Translations['about.license']}") - license_content = QLabel("GPLv3") - license_content.setStyleSheet(self.form_content_style) - license_content.setMaximumWidth(license_content.sizeHint().width()) - self.system_info_layout.addRow(license_title, license_content) - # Config Path config_path_title = QLabel(f"{Translations['about.config_path']}") - config_path_content = QLabel(f"{config_path}") + config_path_content = ClickableLabel() + config_path_content.setText(f"{config_path}") # TODO: Pass in constructor after #1386 + config_path_content.clicked.connect(lambda: open_file(config_path, file_manager=True)) + config_path_content.setCursor(Qt.CursorShape.PointingHandCursor) config_path_content.setStyleSheet(self.form_content_style) config_path_content.setWordWrap(True) self.system_info_layout.addRow(config_path_title, config_path_content) + # TODO: Add row for "App Cache Path" (currently that TagStudio.ini file) + # FFmpeg Status ffmpeg_path_title = QLabel("FFmpeg") - ffmpeg_path_content = QLabel(f"{ffmpeg_status}") + ffmpeg_path_content = ClickableLabel() + ffmpeg_path_content.setText(f"{ffmpeg_status}") # TODO: Pass in constructor after #1386 + ffmpeg_location = which(ffmpeg._get_ffmpeg_location()) # pyright: ignore[reportPrivateUsage] + if ffmpeg_location: + ffmpeg_path_content.clicked.connect( + lambda: open_file(ffmpeg_location, file_manager=True) + ) + ffmpeg_path_content.setCursor(Qt.CursorShape.PointingHandCursor) ffmpeg_path_content.setStyleSheet(self.form_content_style) ffmpeg_path_content.setMaximumWidth(ffmpeg_path_content.sizeHint().width()) self.system_info_layout.addRow(ffmpeg_path_title, ffmpeg_path_content) # FFprobe Status ffprobe_path_title = QLabel("FFprobe") - ffprobe_path_content = QLabel(f"{ffprobe_status}") + ffprobe_path_content = ClickableLabel() + ffprobe_path_content.setText(f"{ffprobe_status}") # TODO: Pass in constructor after #1386 + ffprobe_location = which(ffmpeg._get_ffprobe_location()) # pyright: ignore[reportPrivateUsage] + if ffprobe_location: + ffprobe_path_content.clicked.connect( + lambda: open_file(ffprobe_location, file_manager=True) + ) + ffprobe_path_content.setCursor(Qt.CursorShape.PointingHandCursor) ffprobe_path_content.setStyleSheet(self.form_content_style) ffprobe_path_content.setMaximumWidth(ffprobe_path_content.sizeHint().width()) self.system_info_layout.addRow(ffprobe_path_title, ffprobe_path_content) + # ripgrep Status + # TODO: Add a central class to find ripgrep info, similar to ffmpeg + ripgrep_path_title = QLabel("ripgrep") # NOTE: Don't localize + ripgrep_path_content = ClickableLabel() + ripgrep_path_content.setText(f"{ripgrep_status}") # TODO: Pass in constructor after #1386 + ripgrep_location = which("rg") + if ripgrep_location: + ripgrep_path_content.clicked.connect( + lambda: open_file(ripgrep_location, file_manager=True) + ) + ripgrep_path_content.setCursor(Qt.CursorShape.PointingHandCursor) + ripgrep_path_content.setStyleSheet(self.form_content_style) + ripgrep_path_content.setMaximumWidth(ripgrep_path_content.sizeHint().width()) + self.system_info_layout.addRow(ripgrep_path_title, ripgrep_path_content) + # Links ---------------------------------------------------------------- repo_link = "https://github.com/TagStudioDev/TagStudio" docs_link = "https://docs.tagstud.io" @@ -155,6 +203,7 @@ def __init__(self, config_path): f'{Translations["about.documentation"]} | ' f'Discord

' ) + self.links_label.setStyleSheet("QLabel {color: #809782ff}") self.links_label.setWordWrap(True) self.links_label.setOpenExternalLinks(True) self.links_label.setAlignment(Qt.AlignmentFlag.AlignCenter) @@ -171,12 +220,21 @@ def __init__(self, config_path): # Add Widgets to Layouts ----------------------------------------------- self.content_layout.addWidget(self.logo_widget) - self.content_layout.addWidget(self.title_label) + self.content_layout.addWidget(self.version_label) self.content_layout.addWidget(self.desc_label) self.content_layout.addWidget(self.system_info_widget) - self.content_layout.addWidget(self.links_label) self.content_layout.addStretch(1) + self.content_layout.addWidget(self.links_label) + self.content_layout.addWidget(self.copyright_label) self.content_layout.setAlignment(Qt.AlignmentFlag.AlignCenter) + self.bg_image = self.rm.about_bg + self.bg_image = self.bg_image.scaled( + QSize(self.width(), self.maximumHeight()), Qt.AspectRatioMode.IgnoreAspectRatio + ) + palette = QPalette() + palette.setBrush(QPalette.ColorRole.Window, self.bg_image) + self.setPalette(palette) + self.root_layout.addWidget(self.content_widget) self.root_layout.addWidget(self.button_widget) diff --git a/src/tagstudio/qt/mixed/field_widget.py b/src/tagstudio/qt/mixed/field_widget.py index 04ee3c37f..60face983 100644 --- a/src/tagstudio/qt/mixed/field_widget.py +++ b/src/tagstudio/qt/mixed/field_widget.py @@ -2,39 +2,28 @@ # SPDX-License-Identifier: GPL-3.0-only -import math from collections.abc import Callable -from pathlib import Path from typing import override from warnings import catch_warnings import structlog -from PIL import Image, ImageQt -from PySide6.QtCore import QEvent, Qt +from PIL import ImageQt +from PySide6.QtCore import QEvent, QSize, Qt from PySide6.QtGui import QEnterEvent, QPixmap, QResizeEvent from PySide6.QtWidgets import QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget from tagstudio.core.enums import Theme +from tagstudio.qt.helpers.color_overlay import theme_fg_overlay +from tagstudio.qt.resource_manager import ResourceManager logger = structlog.get_logger(__name__) class FieldContainer(QWidget): - # TODO: reference a resources folder rather than path.parents[2]? - clipboard_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[2] / "resources/qt/images/clipboard_icon_128.png") - ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) - clipboard_icon_128.load() - - edit_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[2] / "resources/qt/images/edit_icon_128.png") - ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) - edit_icon_128.load() - - trash_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[2] / "resources/qt/images/trash_icon_128.png") - ).resize((math.floor(24 * 1.25), math.floor(24 * 1.25))) - trash_icon_128.load() + rm: ResourceManager = ResourceManager() + copy_icon = theme_fg_overlay(rm.copy, inverse=True) + edit_icon = theme_fg_overlay(rm.edit, inverse=True) + trash_icon = theme_fg_overlay(rm.trash, inverse=True) # TODO: There should be a global button theme somewhere. container_style = ( @@ -93,7 +82,8 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.copy_button.setMinimumSize(button_size, button_size) self.copy_button.setMaximumSize(button_size, button_size) self.copy_button.setFlat(True) - self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.clipboard_icon_128))) + self.copy_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(FieldContainer.copy_icon))) + self.copy_button.setIconSize(QSize(20, 20)) self.copy_button.setCursor(Qt.CursorShape.PointingHandCursor) self.title_layout.addWidget(self.copy_button) self.copy_button.setHidden(True) @@ -103,7 +93,8 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.edit_button.setMinimumSize(button_size, button_size) self.edit_button.setMaximumSize(button_size, button_size) self.edit_button.setFlat(True) - self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.edit_icon_128))) + self.edit_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(FieldContainer.edit_icon))) + self.edit_button.setIconSize(QSize(20, 20)) self.edit_button.setCursor(Qt.CursorShape.PointingHandCursor) self.title_layout.addWidget(self.edit_button) self.edit_button.setHidden(True) @@ -113,7 +104,8 @@ def __init__(self, title: str = "Field", inline: bool = True) -> None: self.remove_button.setMinimumSize(button_size, button_size) self.remove_button.setMaximumSize(button_size, button_size) self.remove_button.setFlat(True) - self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(self.trash_icon_128))) + self.remove_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(FieldContainer.trash_icon))) + self.remove_button.setIconSize(QSize(20, 20)) self.remove_button.setCursor(Qt.CursorShape.PointingHandCursor) self.title_layout.addWidget(self.remove_button) self.remove_button.setHidden(True) diff --git a/src/tagstudio/qt/mixed/item_thumb.py b/src/tagstudio/qt/mixed/item_thumb.py index 81223316d..8cbd361ca 100644 --- a/src/tagstudio/qt/mixed/item_thumb.py +++ b/src/tagstudio/qt/mixed/item_thumb.py @@ -8,7 +8,6 @@ from typing import TYPE_CHECKING, override import structlog -from PIL import Image, ImageQt from PySide6.QtCore import QEvent, QMimeData, QSize, Qt, QUrl from PySide6.QtGui import QAction, QDrag, QEnterEvent, QGuiApplication, QMouseEvent, QPixmap from PySide6.QtWidgets import QBoxLayout, QCheckBox, QHBoxLayout, QLabel, QVBoxLayout, QWidget @@ -62,17 +61,7 @@ def wrapper(self, *args, **kwargs): class ItemThumb(FlowWidget): - """The thumbnail widget for a library item (Entry, Collation, Tag Group, etc.).""" - - collation_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[2] / "resources/qt/images/collation_icon_128.png") - ) - collation_icon_128.load() - - tag_group_icon_128: Image.Image = Image.open( - str(Path(__file__).parents[2] / "resources/qt/images/tag_group_icon_128.png") - ) - tag_group_icon_128.load() + """The thumbnail widget for a library item (Entry, Entry Group, etc.).""" small_text_style = ( "background-color:rgba(0, 0, 0, 192);" @@ -141,8 +130,8 @@ def __init__( # | ARC FAV| Top Right: Favorite & Archived Badges # | | # | | - # |EXT #| Lower Left: File Type, Tag Group Icon, or Collation Icon - # +----------+ Lower Right: Collation Count, Video Length, or Word Count + # |EXT #| Lower Left: File Type or Entry Group Icon + # +----------+ Lower Right: Entry Group Count, Video Length, or Word Count # # Filename Underneath: (Optional) Filename @@ -221,19 +210,19 @@ def _on_delete(): # Static Badges ======================================================== # Item Type Badge ------------------------------------------------------ - # Used for showing the Tag Group / Collation icons. + # Used for showing the Entry Group icons. # Mutually exclusive with the File Extension Badge. self.item_type_badge = QLabel() self.item_type_badge.setObjectName("itemBadge") - self.item_type_badge.setPixmap( - QPixmap.fromImage( - ImageQt.ImageQt( - ItemThumb.collation_icon_128.resize( - (check_size, check_size), Image.Resampling.BILINEAR - ) - ) - ) - ) + # self.item_type_badge.setPixmap( + # QPixmap.fromImage( + # ImageQt.ImageQt( + # ItemThumb.collation_icon_128.resize( + # (check_size, check_size), Image.Resampling.BILINEAR + # ) + # ) + # ) + # ) self.item_type_badge.setMinimumSize(check_size, check_size) self.item_type_badge.setMaximumSize(check_size, check_size) self.bottom_layout.addWidget(self.item_type_badge) @@ -247,7 +236,7 @@ def _on_delete(): self.bottom_layout.addStretch(2) # Count Badge ---------------------------------------------------------- - # Used for Tag Group + Collation counts, video length, word count, etc. + # Used for Entry Group counts, video length, word count, etc. self.count_badge = QLabel() self.count_badge.setObjectName("countBadge") self.count_badge.setText("-:--") @@ -343,7 +332,7 @@ def set_mode(self, mode: ItemType | None) -> None: self.count_badge.setStyleSheet(ItemThumb.small_text_style) self.count_badge.setHidden(True) self.ext_badge.setHidden(True) - elif mode == ItemType.COLLATION: + elif mode == ItemType.ENTRY_GROUP: self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False) self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor) self.thumb_button.setHidden(False) @@ -352,13 +341,6 @@ def set_mode(self, mode: ItemType | None) -> None: self.count_badge.setStyleSheet(ItemThumb.med_text_style) self.count_badge.setHidden(False) self.item_type_badge.setHidden(False) - elif mode == ItemType.TAG_GROUP: - self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents, on=False) - self.thumb_button.setCursor(Qt.CursorShape.PointingHandCursor) - self.thumb_button.setHidden(False) - self.ext_badge.setHidden(True) - self.count_badge.setHidden(False) - self.item_type_badge.setHidden(False) self.mode = mode def set_extension(self, filename: Path) -> None: diff --git a/src/tagstudio/qt/mixed/landing.py b/src/tagstudio/qt/mixed/landing.py index 7f5f6ba72..f35a9b470 100644 --- a/src/tagstudio/qt/mixed/landing.py +++ b/src/tagstudio/qt/mixed/landing.py @@ -4,7 +4,6 @@ import sys import typing -from pathlib import Path import structlog from PIL import Image, ImageQt @@ -12,7 +11,8 @@ from PySide6.QtGui import QPixmap from PySide6.QtWidgets import QLabel, QPushButton, QVBoxLayout, QWidget -from tagstudio.qt.helpers.color_overlay import gradient_overlay, theme_fg_overlay +from tagstudio.qt.helpers.color_overlay import theme_fg_overlay +from tagstudio.qt.resource_manager import ResourceManager from tagstudio.qt.translations import Translations from tagstudio.qt.views.clickable_label import ClickableLabel @@ -24,6 +24,10 @@ class LandingWidget(QWidget): + rm: ResourceManager = ResourceManager() + mono_logo: Image.Image = rm.ts_logo_text_mono + color_logo: Image.Image = rm.ts_logo_text_color + def __init__(self, driver: "QtDriver", pixel_ratio: float): super().__init__() self.driver = driver @@ -39,10 +43,6 @@ def __init__(self, driver: "QtDriver", pixel_ratio: float): self.setLayout(self.landing_layout) # Create landing logo -------------------------------------------------- - # self.landing_logo_pixmap = QPixmap(":/images/tagstudio_logo_text_mono.png") - self.logo_raw: Image.Image = Image.open( - Path(__file__).parents[2] / "resources/qt/images/tagstudio_logo_text_mono.png" - ) self.landing_pixmap: QPixmap = QPixmap() self.update_logo_color() self.logo_label.clicked.connect(self._update_special_click) @@ -58,9 +58,9 @@ def __init__(self, driver: "QtDriver", pixel_ratio: float): # Create "Open/Create Library" button ---------------------------------- if sys.platform == "darwin": - open_shortcut_text = "(⌘+O)" + open_shortcut_text = "(⌘ + O)" else: - open_shortcut_text = "(Ctrl+O)" + open_shortcut_text = "(Ctrl + O)" self.open_button: QPushButton = QPushButton( Translations.format("landing.open_create_library", shortcut=open_shortcut_text) ) @@ -83,21 +83,22 @@ def __init__(self, driver: "QtDriver", pixel_ratio: float): self.landing_layout.addWidget(self.open_button, alignment=Qt.AlignmentFlag.AlignCenter) self.landing_layout.addWidget(self.status_label, alignment=Qt.AlignmentFlag.AlignCenter) - def update_logo_color(self, style: typing.Literal["mono", "gradient"] = "mono"): + def update_logo_color(self, style: typing.Literal["mono", "color"] = "mono"): """Update the color of the TagStudio logo. Args: - style (str): = The style of the logo. Either "mono" or "gradient". + style (str): = The style of the logo. Either "mono" or "color". """ if style == "mono": - logo_im = theme_fg_overlay(self.logo_raw) - elif style == "gradient": - gradient_colors: list[str] = ["#d27bf4", "#7992f5", "#63c6e3", "#63f5cf"] - logo_im = gradient_overlay(self.logo_raw, gradient_colors) + logo_im = theme_fg_overlay(LandingWidget.mono_logo) + elif style == "color": + logo_im = LandingWidget.color_logo - logo_final: Image.Image = Image.new(mode="RGBA", size=self.logo_raw.size, color="#00000000") + logo_final: Image.Image = Image.new( + mode="RGBA", size=LandingWidget.mono_logo.size, color="#00000000" + ) - logo_final.paste(logo_im, (0, 0), mask=self.logo_raw) + logo_final.paste(logo_im, (0, 0), mask=LandingWidget.mono_logo) self.landing_pixmap = QPixmap.fromImage(ImageQt.ImageQt(logo_im)) self.landing_pixmap.setDevicePixelRatio(self._pixel_ratio) @@ -105,7 +106,10 @@ def update_logo_color(self, style: typing.Literal["mono", "gradient"] = "mono"): self._logo_width, Qt.TransformationMode.SmoothTransformation ) self.logo_label.setMaximumHeight( - int(self.logo_raw.size[1] * (self.logo_raw.size[0] / self._logo_width)) + int( + LandingWidget.mono_logo.size[1] + * (LandingWidget.mono_logo.size[0] / self._logo_width) + ) ) self.logo_label.setMaximumWidth(self._logo_width) self.logo_label.setPixmap(self.landing_pixmap) @@ -119,17 +123,13 @@ def _update_special_click(self): if self._special_click_count >= 0: self._special_click_count += 1 if self._special_click_count >= 10: - self.update_logo_color("gradient") + self.update_logo_color("color") self.animate_logo_pop() self._special_click_count = -1 def animate_logo_in(self): - """Animate in the TagStudio logo.""" - # NOTE: Sometimes, mostly on startup without a library open, the - # y position of logo_label is something like 10. I'm not sure what - # the cause of this is, so I've just done this workaround to disable - # the animation if the y position is too incorrect. - if self.logo_label.y() > 50: + """Animate the TagStudio logo in, if not opening a library on start.""" + if not self.driver.settings.open_last_loaded_on_startup and not self.driver.args.open: self.logo_pos_anim.setStartValue(QPoint(self.logo_label.x(), self.logo_label.y() - 100)) self.logo_pos_anim.setEndValue(self.logo_label.pos()) self.logo_pos_anim.start() diff --git a/src/tagstudio/qt/mixed/media_player.py b/src/tagstudio/qt/mixed/media_player.py index c180964a3..60731d562 100644 --- a/src/tagstudio/qt/mixed/media_player.py +++ b/src/tagstudio/qt/mixed/media_player.py @@ -5,10 +5,10 @@ import typing from pathlib import Path from time import gmtime -from typing import cast, override +from typing import override import structlog -from PIL import Image, ImageDraw +from PIL import Image, ImageDraw, ImageQt from PySide6.QtCore import QEvent, QObject, QRectF, QSize, Qt, QUrl, QVariantAnimation from PySide6.QtGui import ( QAction, @@ -18,12 +18,12 @@ QLinearGradient, QMouseEvent, QPen, + QPixmap, QRegion, QResizeEvent, ) from PySide6.QtMultimedia import QAudioOutput, QMediaDevices, QMediaPlayer from PySide6.QtMultimediaWidgets import QGraphicsVideoItem -from PySide6.QtSvgWidgets import QSvgWidget from PySide6.QtWidgets import ( QGraphicsScene, QGraphicsView, @@ -31,10 +31,12 @@ QLabel, QSizePolicy, QSlider, + QToolButton, QVBoxLayout, QWidget, ) +from tagstudio.qt.helpers.color_overlay import theme_fg_overlay from tagstudio.qt.translations import Translations from tagstudio.qt.views.clickable_slider import ClickableSlider @@ -55,6 +57,18 @@ class MediaPlayer(QGraphicsView): def __init__(self, driver: "QtDriver") -> None: super().__init__() self.driver = driver + self.play_icon = QPixmap.fromImage( + ImageQt.ImageQt(theme_fg_overlay(self.driver.rm.bxs_right_arrow, use_alpha=False)) + ) + self.pause_icon = QPixmap.fromImage( + ImageQt.ImageQt(theme_fg_overlay(self.driver.rm.pause_icon, use_alpha=False)) + ) + self.mute_icon = QPixmap.fromImage( + ImageQt.ImageQt(theme_fg_overlay(self.driver.rm.mute_icon, use_alpha=False)) + ) + self.volume_icon = QPixmap.fromImage( + ImageQt.ImageQt(theme_fg_overlay(self.driver.rm.volume_icon, use_alpha=False)) + ) slider_style = """ QSlider { @@ -169,27 +183,27 @@ def __init__(self, driver: "QtDriver") -> None: self.sub_controls.setStyleSheet("background: transparent;") self.sub_controls.setMinimumHeight(16) - self.play_pause = QSvgWidget() + self.play_pause = QToolButton() + self.play_pause.setStyleSheet("QToolButton { border: none; background: transparent; }") self.play_pause.setCursor(Qt.CursorShape.PointingHandCursor) - self.play_pause.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, on=True) - self.play_pause.setMouseTracking(True) - self.play_pause.installEventFilter(self) + self.play_pause.clicked.connect(self.toggle_play) self.load_toggle_play_icon(playing=False) - self.play_pause.resize(16, 16) + self.play_pause.setIconSize(QSize(20, 20)) + self.play_pause.resize(20, 20) self.play_pause.setSizePolicy(fixed_policy) - self.play_pause.setStyleSheet("background: transparent;") self.play_pause.hide() sub_layout.addWidget(self.play_pause) sub_layout.setAlignment(self.play_pause, Qt.AlignmentFlag.AlignLeft) - self.mute_unmute = QSvgWidget() + self.mute_unmute = QToolButton() + self.mute_unmute.setStyleSheet("QToolButton { border: none; background: transparent; }") self.mute_unmute.setCursor(Qt.CursorShape.PointingHandCursor) - self.mute_unmute.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, on=True) - self.mute_unmute.setMouseTracking(True) + self.mute_unmute.clicked.connect(self.toggle_mute) self.mute_unmute.installEventFilter(self) self.load_mute_unmute_icon(muted=False) - self.mute_unmute.resize(16, 16) + self.mute_unmute.setIconSize(QSize(20, 20)) + self.mute_unmute.resize(20, 20) self.mute_unmute.setSizePolicy(fixed_policy) self.mute_unmute.hide() @@ -210,7 +224,7 @@ def __init__(self, driver: "QtDriver") -> None: sub_layout.addStretch() self.position_label = QLabel("0:00") - self.position_label.setStyleSheet("color: white;") + self.position_label.setStyleSheet("color: white; font-family: Oxanium; font-weight:bold;") sub_layout.addWidget(self.position_label) root_layout.setAlignment(self.position_label, Qt.AlignmentFlag.AlignRight) self.position_label.hide() @@ -253,21 +267,10 @@ def apply_rounded_corners(self) -> None: """Apply a rounded corner effect to the video player.""" width: int = int(max(self.contentsRect().size().width(), 0)) height: int = int(max(self.contentsRect().size().height(), 0)) - mask = Image.new( - "RGBA", - ( - width, - height, - ), - (0, 0, 0, 255), - ) + mask = Image.new("RGBA", (width, height), (0, 0, 0, 255)) draw = ImageDraw.Draw(mask) - draw.rounded_rectangle( - (0, 0) + (width, height), - radius=8, - fill=(0, 0, 0, 0), - ) - final_mask = mask.getchannel("A").toqpixmap() + draw.rounded_rectangle((0, 0) + (width, height), radius=8, fill=(0, 0, 0, 0)) + final_mask: QPixmap = mask.getchannel("A").toqpixmap() # pyright: ignore[reportUnknownVariableType] self.setMask(QRegion(QBitmap(final_mask))) def set_tint_opacity(self, opacity: int) -> None: @@ -322,15 +325,7 @@ def mousePressEvent(self, event: QMouseEvent) -> None: @override def eventFilter(self, arg__1: QObject, arg__2: QEvent) -> bool: """Manage events for the media player.""" - if ( - arg__2.type() == QEvent.Type.MouseButtonPress - and arg__2.button() == Qt.MouseButton.LeftButton # pyright: ignore[reportAttributeAccessIssue] - ): - if arg__1 == self.play_pause: - self.toggle_play() - elif arg__1 == self.mute_unmute: - self.toggle_mute() - elif arg__2.type() is QEvent.Type.Enter: + if arg__2.type() is QEvent.Type.Enter: if arg__1 == self or arg__1 == self.video_preview: self.underMouse() elif arg__1 == self.mute_unmute: @@ -414,12 +409,10 @@ def play(self, filepath: Path) -> None: self.player.play() def load_toggle_play_icon(self, playing: bool) -> None: - icon = cast(bytes, self.driver.rm.pause_icon if playing else self.driver.rm.play_icon) - self.play_pause.load(icon) + self.play_pause.setIcon(self.pause_icon if playing else self.play_icon) def load_mute_unmute_icon(self, muted: bool) -> None: - icon = cast(bytes, self.driver.rm.volume_mute_icon if muted else self.driver.rm.volume_icon) - self.mute_unmute.load(icon) + self.mute_unmute.setIcon(self.mute_icon if muted else self.volume_icon) def slider_value_changed(self, value: int) -> None: if self.timeline_slider.isSliderDown(): diff --git a/src/tagstudio/qt/mixed/pagination.py b/src/tagstudio/qt/mixed/pagination.py index a1e77c1cb..c03d2b57f 100644 --- a/src/tagstudio/qt/mixed/pagination.py +++ b/src/tagstudio/qt/mixed/pagination.py @@ -44,7 +44,7 @@ def __init__(self, parent=None) -> None: # [<] ---------------------------------- self.prev_button = QPushButtonWrapper() - prev_icon: Image.Image = self.rm.get("bxs-left-arrow") # pyright: ignore[reportAssignmentType] + prev_icon: Image.Image = self.rm.bxs_left_arrow prev_icon = theme_fg_overlay(prev_icon, use_alpha=False) self.prev_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(prev_icon))) self.prev_button.setIconSize(QSize(12, 12)) @@ -97,7 +97,7 @@ def __init__(self, parent=None) -> None: # ---------------------------------- [>] self.next_button = QPushButtonWrapper() - next_icon: Image.Image = self.rm.get("bxs-right-arrow") # pyright: ignore[reportAssignmentType] + next_icon: Image.Image = self.rm.bxs_right_arrow next_icon = theme_fg_overlay(next_icon, use_alpha=False) self.next_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(next_icon))) self.next_button.setIconSize(QSize(12, 12)) diff --git a/src/tagstudio/qt/mixed/settings_panel.py b/src/tagstudio/qt/mixed/settings_panel.py index 5e8c022db..fe76f83d0 100644 --- a/src/tagstudio/qt/mixed/settings_panel.py +++ b/src/tagstudio/qt/mixed/settings_panel.py @@ -56,6 +56,7 @@ class SettingsPanel(PanelWidget): 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"], + Splash.AURORA: Translations["settings.splash.option.aurora"], } tag_click_action_map: dict[TagClickActionOption, str] = { diff --git a/src/tagstudio/qt/previews/renderer.py b/src/tagstudio/qt/previews/renderer.py index 866e3df31..492247edd 100644 --- a/src/tagstudio/qt/previews/renderer.py +++ b/src/tagstudio/qt/previews/renderer.py @@ -449,11 +449,10 @@ def _render_center_icon( ) # Get icon by name - icon: Image.Image | None = self.rm.get(name) # pyright: ignore[reportAssignmentType] + icon = self.rm.get(name) + assert isinstance(icon, Image.Image) or icon is None if not icon: - icon = self.rm.get("file_generic") # pyright: ignore[reportAssignmentType] - if not icon: - icon = Image.new(mode="RGBA", size=(32, 32), color="magenta") + icon = self.rm.file_generic # Resize icon to fit icon_ratio icon = icon.resize((math.ceil(size[0] // icon_ratio), math.ceil(size[1] // icon_ratio))) @@ -547,11 +546,10 @@ def _render_corner_icon( ) # Get icon by name - icon: Image.Image | None = self.rm.get(name) # pyright: ignore[reportAssignmentType] + icon = self.rm.get(name) + assert isinstance(icon, Image.Image) if not icon: - icon = self.rm.get("file_generic") # pyright: ignore[reportAssignmentType] - if not icon: - icon = Image.new(mode="RGBA", size=(32, 32), color="magenta") + icon = self.rm.file_generic # Resize icon to fit icon_ratio icon = icon.resize( @@ -1597,7 +1595,7 @@ def render_ignored( padding_factor = 18 im_ = im - icon: Image.Image = self.rm.get("ignored") # pyright: ignore[reportAssignmentType] + icon: Image.Image = self.rm.ignored icon = icon.resize( ( diff --git a/src/tagstudio/qt/previews/vendored/ffmpeg.py b/src/tagstudio/qt/previews/vendored/ffmpeg.py index 84d6ba196..e3b3d57b2 100644 --- a/src/tagstudio/qt/previews/vendored/ffmpeg.py +++ b/src/tagstudio/qt/previews/vendored/ffmpeg.py @@ -27,6 +27,7 @@ ] +# TODO: Make this more intuitive to use in other classes def _get_ffprobe_location() -> str: cmd: str = "ffprobe" if platform.system() == "Darwin": @@ -40,6 +41,7 @@ def _get_ffprobe_location() -> str: return cmd +# TODO: Make this more intuitive to use in other classes def _get_ffmpeg_location() -> str: cmd: str = "ffmpeg" if platform.system() == "Darwin": diff --git a/src/tagstudio/qt/resource_manager.py b/src/tagstudio/qt/resource_manager.py index f1d387ab6..d769a4ce9 100644 --- a/src/tagstudio/qt/resource_manager.py +++ b/src/tagstudio/qt/resource_manager.py @@ -3,31 +3,23 @@ from pathlib import Path -from typing import Literal, TypedDict import structlog import ujson -from PIL import Image, ImageFile +from PIL import Image from PySide6.QtGui import QPixmap logger = structlog.get_logger(__name__) -class TResourceJsonAttrDict(TypedDict): - path: str - mode: Literal["qpixmap", "pil", "rb", "r"] - - -TData = bytes | str | ImageFile.ImageFile | QPixmap - RESOURCE_FOLDER: Path = Path(__file__).parents[1] class ResourceManager: """A resource manager for retrieving resources.""" - _map: dict[str, TResourceJsonAttrDict] = {} - _cache: dict[str, TData] = {} + _map: dict[str, dict[str, str]] = {} + _cache: dict[str, bytes | str | Image.Image | QPixmap] = {} _instance: "ResourceManager | None" = None def __new__(cls): @@ -43,7 +35,7 @@ def __new__(cls): return ResourceManager._instance @staticmethod - def get_path(id: str) -> Path | None: + def get_path(id: str): """Get a resource's path from the ResourceManager. Args: @@ -52,12 +44,21 @@ def get_path(id: str) -> Path | None: Returns: Path: The resource path if found, else None. """ - res: TResourceJsonAttrDict | None = ResourceManager._map.get(id) - if res is not None: - return RESOURCE_FOLDER / "resources" / res.get("path") - return None + try: + res = ResourceManager._map.get(id) + if res is None: + raise AttributeError + resource_path = res.get("path") + if resource_path is None: + raise FileNotFoundError - def get(self, id: str) -> TData | None: + except (FileNotFoundError, AttributeError) as e: + logger.error("[ResourceManager]: Could not find path for resource: ", id=str, error=e) + return None + + return RESOURCE_FOLDER / "resources" / resource_path + + def get(self, id: str): """Get a resource from the ResourceManager. Args: @@ -70,42 +71,51 @@ def get(self, id: str) -> TData | None: QPixmap: When the data is in PySide6.QtGui.QPixmap format. None: If resource couldn't load. """ - cached_res: TData | None = ResourceManager._cache.get(id) + cached_res = ResourceManager._cache.get(id) if cached_res is not None: return cached_res else: - res: TResourceJsonAttrDict | None = ResourceManager._map.get(id) - if res is None: + res: dict[str, str] | None = ResourceManager._map.get(id) + + try: + if res is None: + raise AttributeError + resource_path = res.get("path") + if resource_path is None: + raise FileNotFoundError + except (FileNotFoundError, AttributeError) as e: + logger.error("[ResourceManager]: Could not find resource", id=id, error=e) return None - file_path: Path = RESOURCE_FOLDER / "resources" / res.get("path") + file_path = RESOURCE_FOLDER / "resources" / resource_path mode = res.get("mode") - data: TData | None = None + data = None try: match mode: case "r": data = file_path.read_text() - case "rb": data = file_path.read_bytes() - case "pil": data = Image.open(file_path) data.load() - case "qpixmap": data = QPixmap(file_path.as_posix()) + case _: + raise AttributeError - except FileNotFoundError: - logger.error("[ResourceManager][ERROR]: Could not find resource: ", path=file_path) + except (FileNotFoundError, AttributeError) as e: + logger.error( + "[ResourceManager]: Could not find resource", path=file_path, id=id, error=e + ) + return None - if data is not None: - ResourceManager._cache[id] = data + ResourceManager._cache[id] = data return data - def __getattr__(self, __name: str) -> TData: + def __getattr__(self, __name: str): attr = self.get(__name) if attr is not None: return attr diff --git a/src/tagstudio/qt/resource_manager.pyi b/src/tagstudio/qt/resource_manager.pyi new file mode 100644 index 000000000..be327a0ff --- /dev/null +++ b/src/tagstudio/qt/resource_manager.pyi @@ -0,0 +1,63 @@ +# SPDX-FileCopyrightText: (c) TagStudio Contributors +# SPDX-License-Identifier: GPL-3.0-only + +from collections.abc import Callable +from pathlib import Path + +from PIL import Image +from PySide6.QtGui import QPixmap + +class ResourceManager: + # Methods + get: Callable[..., Image.Image | bytes | None] + get_path: Callable[..., Path | None] + + # Attributes + _map: dict[str, dict[str, str]] + _cache: dict[str, bytes | str | Image.Image | QPixmap] + _instance: ResourceManager | None + + # Resources IDs from "resources.json" + about_bg: QPixmap + adobe_illustrator: Image.Image + adobe_photoshop: Image.Image + affinity_photo: Image.Image + archive: Image.Image + audio: Image.Image + broken_link_icon: Image.Image + bxs_left_arrow: Image.Image + bxs_right_arrow: Image.Image + copy: Image.Image + database: Image.Image + document: Image.Image + dupe_file_stat: Image.Image + ebook: Image.Image + edit: Image.Image + file_generic: Image.Image + font: Image.Image + icon: Image.Image + ignored_stat: Image.Image + ignored: Image.Image + image_vector: Image.Image + image: Image.Image + material: Image.Image + model: Image.Image + mute_icon: Image.Image + pause_icon: Image.Image + presentation: Image.Image + program: Image.Image + shader: Image.Image + shortcut: Image.Image + splash_95: QPixmap + splash_aurora: QPixmap + splash_classic: QPixmap + splash_goo_gears: QPixmap + spreadsheet: Image.Image + text: Image.Image + thumb_loading: Image.Image + trash: Image.Image + ts_logo_text_color: Image.Image + ts_logo_text_mono: Image.Image + unlinked_stat: Image.Image + video: Image.Image + volume_icon: Image.Image diff --git a/src/tagstudio/qt/resources.json b/src/tagstudio/qt/resources.json index f7bae77c9..48b6f8f32 100644 --- a/src/tagstudio/qt/resources.json +++ b/src/tagstudio/qt/resources.json @@ -1,150 +1,174 @@ { - "splash_classic": { - "path": "qt/images/splash/classic.png", - "mode": "qpixmap" - }, - "splash_goo_gears": { - "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" - }, - "play_icon": { - "path": "qt/images/play.svg", - "mode": "rb" - }, - "pause_icon": { - "path": "qt/images/pause.svg", - "mode": "rb" - }, - "volume_icon": { - "path": "qt/images/volume.svg", - "mode": "rb" - }, - "volume_mute_icon": { - "path": "qt/images/volume_mute.svg", - "mode": "rb" - }, - "broken_link_icon": { - "path": "qt/images/broken_link_icon.png", - "mode": "pil" - }, - "ignored": { - "path": "qt/images/ignored_128.png", - "mode": "pil" + "about_bg": { + "mode": "qpixmap", + "path": "qt/images/about_bg.jpg" }, "adobe_illustrator": { - "path": "qt/images/file_icons/adobe_illustrator.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/adobe_illustrator.png" }, "adobe_photoshop": { - "path": "qt/images/file_icons/adobe_photoshop.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/adobe_photoshop.png" }, "affinity_photo": { - "path": "qt/images/file_icons/affinity_photo.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/affinity_photo.png" }, "archive": { - "path": "qt/images/file_icons/archive.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/archive.png" }, "audio": { - "path": "qt/images/file_icons/audio.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/audio.png" + }, + "broken_link_icon": { + "mode": "pil", + "path": "qt/images/broken_link_icon.png" + }, + "bxs_left_arrow": { + "mode": "pil", + "path": "qt/images/bxs-left-arrow.png" + }, + "bxs_right_arrow": { + "mode": "pil", + "path": "qt/images/bxs-right-arrow.png" + }, + "copy": { + "mode": "pil", + "path": "qt/images/bxs-clipboard-regular.png" }, "database": { - "path": "qt/images/file_icons/database.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/database.png" }, "document": { - "path": "qt/images/file_icons/document.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/document.png" + }, + "dupe_file_stat": { + "mode": "pil", + "path": "qt/images/dupe_file_stat.png" }, "ebook": { - "path": "qt/images/file_icons/ebook.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/ebook.png" + }, + "edit": { + "mode": "pil", + "path": "qt/images/bxs-pencil-solid.png" }, "file_generic": { - "path": "qt/images/file_icons/file_generic.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/file_generic.png" }, "font": { - "path": "qt/images/file_icons/font.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/font.png" + }, + "icon": { + "mode": "pil", + "path": "icon.png" + }, + "ignored": { + "mode": "pil", + "path": "qt/images/ignored_128.png" + }, + "ignored_stat": { + "mode": "pil", + "path": "qt/images/ignored_stat.png" }, "image": { - "path": "qt/images/file_icons/image.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/image.png" }, "image_vector": { - "path": "qt/images/file_icons/image_vector.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/image_vector.png" }, "material": { - "path": "qt/images/file_icons/material.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/material.png" }, "model": { - "path": "qt/images/file_icons/model.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/model.png" + }, + "mute_icon": { + "mode": "pil", + "path": "qt/images/bxs-volume-mute-solid.png" + }, + "pause_icon": { + "mode": "pil", + "path": "qt/images/pause.png" }, "presentation": { - "path": "qt/images/file_icons/presentation.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/presentation.png" }, "program": { - "path": "qt/images/file_icons/program.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/program.png" }, "shader": { - "path": "qt/images/file_icons/shader.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/shader.png" }, "shortcut": { - "path": "qt/images/file_icons/shortcut.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/shortcut.png" + }, + "splash_95": { + "mode": "qpixmap", + "path": "qt/images/splash/95.png" + }, + "splash_aurora": { + "mode": "qpixmap", + "path": "qt/images/splash/aurora.png" + }, + "splash_classic": { + "mode": "qpixmap", + "path": "qt/images/splash/classic.png" + }, + "splash_goo_gears": { + "mode": "qpixmap", + "path": "qt/images/splash/goo_gears.png" }, "spreadsheet": { - "path": "qt/images/file_icons/spreadsheet.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/spreadsheet.png" }, "text": { - "path": "qt/images/file_icons/text.png", - "mode": "pil" - }, - "video": { - "path": "qt/images/file_icons/video.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/file_icons/text.png" }, "thumb_loading": { - "path": "qt/images/thumb_loading.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/thumb_loading.png" + }, + "trash": { + "mode": "pil", + "path": "qt/images/bxs-trash-solid.png" }, - "bxs-left-arrow": { - "path": "qt/images/bxs-left-arrow.png", - "mode": "pil" + "ts_logo_text_color": { + "mode": "pil", + "path": "qt/images/tagstudio_logo-text_color.png" }, - "bxs-right-arrow": { - "path": "qt/images/bxs-right-arrow.png", - "mode": "pil" + "ts_logo_text_mono": { + "mode": "pil", + "path": "qt/images/tagstudio_logo-text_mono.png" }, "unlinked_stat": { - "path": "qt/images/unlinked_stat.png", - "mode": "pil" + "mode": "pil", + "path": "qt/images/unlinked_stat.png" }, - "ignored_stat": { - "path": "qt/images/ignored_stat.png", - "mode": "pil" + "video": { + "mode": "pil", + "path": "qt/images/file_icons/video.png" }, - "dupe_file_stat": { - "path": "qt/images/dupe_file_stat.png", - "mode": "pil" + "volume_icon": { + "mode": "pil", + "path": "qt/images/bxs-volume-full-solid.png" } } diff --git a/src/tagstudio/qt/resources.qrc b/src/tagstudio/qt/resources.qrc index fcaf44c88..3d5b5ab2f 100644 --- a/src/tagstudio/qt/resources.qrc +++ b/src/tagstudio/qt/resources.qrc @@ -7,8 +7,5 @@ ../../resources/qt/images/star_icon_filled_128.png ../../resources/qt/images/box_icon_empty_128.png ../../resources/qt/images/box_icon_filled_128.png - - - diff --git a/src/tagstudio/qt/views/library_info_window_view.py b/src/tagstudio/qt/views/library_info_window_view.py index bfd06c0ac..8d72fb923 100644 --- a/src/tagstudio/qt/views/library_info_window_view.py +++ b/src/tagstudio/qt/views/library_info_window_view.py @@ -268,7 +268,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): ) self.unlinked_icon = QLabel() - unlinked_image: Image.Image = self.driver.rm.get("unlinked_stat") # pyright: ignore[reportAssignmentType] + unlinked_image: Image.Image = self.driver.rm.unlinked_stat unlinked_pixmap = QPixmap.fromImage(ImageQt.ImageQt(unlinked_image)) unlinked_pixmap.setDevicePixelRatio(self.devicePixelRatio()) unlinked_pixmap = unlinked_pixmap.scaledToWidth( @@ -278,7 +278,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.unlinked_icon.setPixmap(unlinked_pixmap) self.ignored_icon = QLabel() - ignored_image: Image.Image = self.driver.rm.get("ignored_stat") # pyright: ignore[reportAssignmentType] + ignored_image: Image.Image = self.driver.rm.ignored_stat ignored_pixmap = QPixmap.fromImage(ImageQt.ImageQt(ignored_image)) ignored_pixmap.setDevicePixelRatio(self.devicePixelRatio()) ignored_pixmap = ignored_pixmap.scaledToWidth( @@ -288,7 +288,7 @@ def __init__(self, library: "Library", driver: "QtDriver"): self.ignored_icon.setPixmap(ignored_pixmap) self.dupe_file_icon = QLabel() - dupe_file_image: Image.Image = self.driver.rm.get("dupe_file_stat") # pyright: ignore[reportAssignmentType] + dupe_file_image: Image.Image = self.driver.rm.dupe_file_stat dupe_file_pixmap = QPixmap.fromImage( ImageQt.ImageQt(theme_fg_overlay(dupe_file_image, use_alpha=False)) ) diff --git a/src/tagstudio/qt/views/main_window.py b/src/tagstudio/qt/views/main_window.py index b9229fdd5..ad991c073 100644 --- a/src/tagstudio/qt/views/main_window.py +++ b/src/tagstudio/qt/views/main_window.py @@ -547,7 +547,7 @@ def setup_search_bar(self): self.search_bar_layout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize) self.back_button = QPushButton(self.central_widget) - back_icon: Image.Image = self.rm.get("bxs-left-arrow") # pyright: ignore[reportAssignmentType] + back_icon: Image.Image = self.rm.bxs_left_arrow back_icon = theme_fg_overlay(back_icon, use_alpha=False) self.back_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(back_icon))) self.back_button.setObjectName("back_button") @@ -556,7 +556,7 @@ def setup_search_bar(self): self.search_bar_layout.addWidget(self.back_button) self.forward_button = QPushButton(self.central_widget) - forward_icon: Image.Image = self.rm.get("bxs-right-arrow") # pyright: ignore[reportAssignmentType] + forward_icon: Image.Image = self.rm.bxs_right_arrow forward_icon = theme_fg_overlay(forward_icon, use_alpha=False) self.forward_button.setIcon(QPixmap.fromImage(ImageQt.ImageQt(forward_icon))) self.forward_button.setIconSize(QSize(16, 16)) diff --git a/src/tagstudio/qt/views/splash.py b/src/tagstudio/qt/views/splash.py index 220c4c5c4..8f63c78b4 100644 --- a/src/tagstudio/qt/views/splash.py +++ b/src/tagstudio/qt/views/splash.py @@ -10,9 +10,10 @@ 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.constants import COPYRIGHT, COPYRIGHT_COMPACT, VERSION, VERSION_BRANCH from tagstudio.qt.global_settings import Splash from tagstudio.qt.resource_manager import ResourceManager +from tagstudio.qt.translations import Translations logger = structlog.get_logger(__name__) @@ -20,12 +21,8 @@ class SplashScreen: """The custom splash screen widget for TagStudio.""" - COPYRIGHT_YEARS: str = "2021-2025" - COPYRIGHT_STR: str = f"© {COPYRIGHT_YEARS} Travis Abendshien (CyanVoxel)" - VERSION_STR: str = ( - f"Version {VERSION} {(' (' + VERSION_BRANCH + ')') if VERSION_BRANCH else ''}" - ) - DEFAULT_SPLASH = Splash.GOO_GEARS + VERSION_STR: str = f"{Translations['about.version']} {VERSION} {(' (' + VERSION_BRANCH + ')') if VERSION_BRANCH else ''}" # noqa: E501 + DEFAULT_SPLASH = Splash.AURORA def __init__( self, @@ -50,7 +47,8 @@ def __init__( def get_pixmap(self) -> QPixmap: """Get the pixmap used for the splash screen.""" - pixmap: QPixmap | None = self.rm.get(f"splash_{self.splash_name}") # pyright: ignore[reportAssignmentType] + pixmap = self.rm.get(f"splash_{self.splash_name}") + assert isinstance(pixmap, QPixmap) if not pixmap: logger.error("[Splash] Splash screen not found:", splash_name=self.splash_name) pixmap = QPixmap(960, 540) @@ -70,18 +68,18 @@ def get_pixmap(self) -> QPixmap: font = painter.font() font.setPointSize(math.floor(22 * point_size_scale)) painter.setFont(font) - pen = QPen(QColor("#9782ff")) + pen = QPen(QColor("#809782ff")) painter.setPen(pen) painter.drawText( - QRect(0, -50, 960, 540), + QRect(0, -25, 960, 540), int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter), - SplashScreen.COPYRIGHT_STR, + COPYRIGHT, ) # Version - pen = QPen(QColor("#809782ff")) + pen = QPen(QColor("#9782ff")) painter.setPen(pen) painter.drawText( - QRect(0, -25, 960, 540), + QRect(0, -50, 960, 540), int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter), SplashScreen.VERSION_STR, ) @@ -91,20 +89,20 @@ def get_pixmap(self) -> QPixmap: font = painter.font() font.setPointSize(math.floor(22 * point_size_scale)) painter.setFont(font) - pen = QPen(QColor("#9782ff")) + pen = QPen(QColor("#809782ff")) painter.setPen(pen) painter.drawText( QRect(40, 450, 960, 540), - SplashScreen.COPYRIGHT_STR, + COPYRIGHT_COMPACT, ) # Version font = painter.font() font.setPointSize(math.floor(22 * point_size_scale)) painter.setFont(font) - pen = QPen(QColor("#809782ff")) + pen = QPen(QColor("#9782ff")) painter.setPen(pen) painter.drawText( - QRect(40, 475, 960, 540), + QRect(40, 420, 960, 540), SplashScreen.VERSION_STR, ) @@ -121,7 +119,7 @@ def get_pixmap(self) -> QPixmap: painter.drawText( QRect(88, -25, 960, 540), int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft), - SplashScreen.COPYRIGHT_STR, + COPYRIGHT, ) # Version font.setPointSize(math.floor(22 * point_size_scale)) @@ -134,17 +132,33 @@ def get_pixmap(self) -> QPixmap: SplashScreen.VERSION_STR, ) + case Splash.AURORA: + # Copyright + font = painter.font() + font.setPointSize(math.floor(22 * point_size_scale)) + painter.setFont(font) + pen = QPen(QColor("#907758FF")) + painter.setPen(pen) + painter.drawText( + QRect(0, -25, 960, 540), + int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter), + COPYRIGHT, + ) + # Version + pen = QPen(QColor("#7758FF")) + painter.setPen(pen) + painter.drawText( + QRect(0, -50, 960, 540), + int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter), + SplashScreen.VERSION_STR, + ) + case _: pass pixmap.setDevicePixelRatio(self.ratio) pixmap = pixmap.scaledToWidth( - math.floor( - min( - (self.screen_width * self.ratio) / 4, - pixmap.width(), - ) - ), + math.floor(min((self.screen_width * self.ratio) / 4, pixmap.width())), # pyright: ignore[reportCallIssue] Qt.TransformationMode.SmoothTransformation, ) diff --git a/src/tagstudio/resources/icon.icns b/src/tagstudio/resources/icon.icns index 2c84be073..100f53099 100644 Binary files a/src/tagstudio/resources/icon.icns and b/src/tagstudio/resources/icon.icns differ diff --git a/src/tagstudio/resources/icon.ico b/src/tagstudio/resources/icon.ico index 71335bd51..e011c3ace 100644 Binary files a/src/tagstudio/resources/icon.ico and b/src/tagstudio/resources/icon.ico differ diff --git a/src/tagstudio/resources/icon.png b/src/tagstudio/resources/icon.png index dd3d2772e..be9e7a822 100644 Binary files a/src/tagstudio/resources/icon.png and b/src/tagstudio/resources/icon.png differ diff --git a/src/tagstudio/resources/qt/images/about_bg.jpg b/src/tagstudio/resources/qt/images/about_bg.jpg new file mode 100644 index 000000000..8ff0a6d55 Binary files /dev/null and b/src/tagstudio/resources/qt/images/about_bg.jpg differ diff --git a/src/tagstudio/resources/qt/images/box_icon_empty_128 - Copy.png b/src/tagstudio/resources/qt/images/box_icon_empty_128 - Copy.png deleted file mode 100644 index 4fc14bc68..000000000 Binary files a/src/tagstudio/resources/qt/images/box_icon_empty_128 - Copy.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/box_icon_filled_128 - Copy.png b/src/tagstudio/resources/qt/images/box_icon_filled_128 - Copy.png deleted file mode 100644 index 67dd6d5b6..000000000 Binary files a/src/tagstudio/resources/qt/images/box_icon_filled_128 - Copy.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/bxs-clipboard-regular.png b/src/tagstudio/resources/qt/images/bxs-clipboard-regular.png new file mode 100644 index 000000000..1166820aa Binary files /dev/null and b/src/tagstudio/resources/qt/images/bxs-clipboard-regular.png differ diff --git a/src/tagstudio/resources/qt/images/bxs-pencil-solid.png b/src/tagstudio/resources/qt/images/bxs-pencil-solid.png new file mode 100644 index 000000000..9841f739f Binary files /dev/null and b/src/tagstudio/resources/qt/images/bxs-pencil-solid.png differ diff --git a/src/tagstudio/resources/qt/images/bxs-trash-solid.png b/src/tagstudio/resources/qt/images/bxs-trash-solid.png new file mode 100644 index 000000000..f1c0c0781 Binary files /dev/null and b/src/tagstudio/resources/qt/images/bxs-trash-solid.png differ diff --git a/src/tagstudio/resources/qt/images/bxs-volume-full-solid.png b/src/tagstudio/resources/qt/images/bxs-volume-full-solid.png new file mode 100644 index 000000000..f24ede9e1 Binary files /dev/null and b/src/tagstudio/resources/qt/images/bxs-volume-full-solid.png differ diff --git a/src/tagstudio/resources/qt/images/bxs-volume-mute-solid.png b/src/tagstudio/resources/qt/images/bxs-volume-mute-solid.png new file mode 100644 index 000000000..1e8bca80f Binary files /dev/null and b/src/tagstudio/resources/qt/images/bxs-volume-mute-solid.png differ diff --git a/src/tagstudio/resources/qt/images/clipboard_icon_128.png b/src/tagstudio/resources/qt/images/clipboard_icon_128.png deleted file mode 100644 index 9261b476a..000000000 Binary files a/src/tagstudio/resources/qt/images/clipboard_icon_128.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/edit_icon_128.png b/src/tagstudio/resources/qt/images/edit_icon_128.png deleted file mode 100644 index 85f85d337..000000000 Binary files a/src/tagstudio/resources/qt/images/edit_icon_128.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/edit_icon_dark_128.png b/src/tagstudio/resources/qt/images/edit_icon_dark_128.png deleted file mode 100644 index 249da5123..000000000 Binary files a/src/tagstudio/resources/qt/images/edit_icon_dark_128.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/pause.png b/src/tagstudio/resources/qt/images/pause.png new file mode 100644 index 000000000..7e70585ec Binary files /dev/null and b/src/tagstudio/resources/qt/images/pause.png differ diff --git a/src/tagstudio/resources/qt/images/pause.svg b/src/tagstudio/resources/qt/images/pause.svg deleted file mode 100644 index f7777470c..000000000 --- a/src/tagstudio/resources/qt/images/pause.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/tagstudio/resources/qt/images/play.svg b/src/tagstudio/resources/qt/images/play.svg deleted file mode 100644 index 3d5f65063..000000000 --- a/src/tagstudio/resources/qt/images/play.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/tagstudio/resources/qt/images/splash/aurora.png b/src/tagstudio/resources/qt/images/splash/aurora.png new file mode 100644 index 000000000..c1902c999 Binary files /dev/null and b/src/tagstudio/resources/qt/images/splash/aurora.png differ diff --git a/src/tagstudio/resources/qt/images/splitter_handle_128.png b/src/tagstudio/resources/qt/images/splitter_handle_128.png deleted file mode 100644 index 328a0dd5f..000000000 Binary files a/src/tagstudio/resources/qt/images/splitter_handle_128.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/star_icon_empty_128 - Copy.png b/src/tagstudio/resources/qt/images/star_icon_empty_128 - Copy.png deleted file mode 100644 index ab1ef1ff7..000000000 Binary files a/src/tagstudio/resources/qt/images/star_icon_empty_128 - Copy.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/star_icon_filled_128 - Copy.png b/src/tagstudio/resources/qt/images/star_icon_filled_128 - Copy.png deleted file mode 100644 index 0bfcf2b18..000000000 Binary files a/src/tagstudio/resources/qt/images/star_icon_filled_128 - Copy.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/tag_group_icon_128.png b/src/tagstudio/resources/qt/images/tag_group_icon_128.png deleted file mode 100644 index 18510d5d6..000000000 Binary files a/src/tagstudio/resources/qt/images/tag_group_icon_128.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/tag_group_icon_dark_128.png b/src/tagstudio/resources/qt/images/tag_group_icon_dark_128.png deleted file mode 100644 index a698c0302..000000000 Binary files a/src/tagstudio/resources/qt/images/tag_group_icon_dark_128.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/tagstudio_logo-text_color.png b/src/tagstudio/resources/qt/images/tagstudio_logo-text_color.png new file mode 100644 index 000000000..440b5837a Binary files /dev/null and b/src/tagstudio/resources/qt/images/tagstudio_logo-text_color.png differ diff --git a/src/tagstudio/resources/qt/images/tagstudio_logo-text_mono.png b/src/tagstudio/resources/qt/images/tagstudio_logo-text_mono.png new file mode 100644 index 000000000..a464e7a7d Binary files /dev/null and b/src/tagstudio/resources/qt/images/tagstudio_logo-text_mono.png differ diff --git a/src/tagstudio/resources/qt/images/tagstudio_logo_text_mono.png b/src/tagstudio/resources/qt/images/tagstudio_logo_text_mono.png deleted file mode 100644 index a5d333474..000000000 Binary files a/src/tagstudio/resources/qt/images/tagstudio_logo_text_mono.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/trash_icon_128.png b/src/tagstudio/resources/qt/images/trash_icon_128.png deleted file mode 100644 index a1d11843e..000000000 Binary files a/src/tagstudio/resources/qt/images/trash_icon_128.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/trash_icon_dark_128.png b/src/tagstudio/resources/qt/images/trash_icon_dark_128.png deleted file mode 100644 index 775b55f11..000000000 Binary files a/src/tagstudio/resources/qt/images/trash_icon_dark_128.png and /dev/null differ diff --git a/src/tagstudio/resources/qt/images/volume.svg b/src/tagstudio/resources/qt/images/volume.svg deleted file mode 100644 index d240d7ae2..000000000 --- a/src/tagstudio/resources/qt/images/volume.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/tagstudio/resources/qt/images/volume_mute.svg b/src/tagstudio/resources/qt/images/volume_mute.svg deleted file mode 100644 index 5ce8f0a4b..000000000 --- a/src/tagstudio/resources/qt/images/volume_mute.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/tagstudio/resources/translations/en.json b/src/tagstudio/resources/translations/en.json index 1035c1512..80b279895 100644 --- a/src/tagstudio/resources/translations/en.json +++ b/src/tagstudio/resources/translations/en.json @@ -1,14 +1,16 @@ { "about.config_path": "Config Path", + "about.app_cache_path": "App Cache Path", "about.description": "TagStudio is a photo and file organization application with an underlying tag-based system that focuses on giving freedom and flexibility to the user. No proprietary programs or formats, no sea of sidecar files, and no complete upheaval of your filesystem structure.", "about.documentation": "Documentation", - "about.license": "License", "about.module.found": "Found", "about.title": "About TagStudio", + "about.version": "Version", "about.website": "Website", "app.git": "Git Commit", "app.pre_release": "Pre-Release", "app.title": "{base_title} - Library '{library_dir}'", + "color_manager.title": "Manage Tag Colors", "color.color_border": "Use Secondary Color for Border", "color.confirm_delete": "Are you sure you want to delete the color \"{color_name}\"?", "color.delete": "Delete Tag", @@ -22,7 +24,6 @@ "color.primary_required": "Primary Color (Required)", "color.secondary": "Secondary Color", "color.title.no_color": "No Color", - "color_manager.title": "Manage Tag Colors", "dependency.missing.title": "{dependency} Not Found", "drop_import.description": "The following files match file paths that already exist in the library", "drop_import.duplicates_choice.plural": "The following {count} files match file paths that already exist in the library.", @@ -72,6 +73,13 @@ "entries.unlinked.unlinked_count": "Unlinked Entries: {count}", "ffmpeg.missing.description": "FFmpeg and/or FFprobe were not found. FFmpeg is required for multimedia playback and thumbnails.", "ffmpeg.missing.status": "{ffmpeg}: {ffmpeg_status}
{ffprobe}: {ffprobe_status}", + "field_template_manager.title": "Library Field Templates", + "field_template.all_field_templates": "All Field Templates", + "field_template.create": "Create Field Template", + "field_template.create_add": "Create && Add \"{query}\"", + "field_type.datetime": "Datetime", + "field_type.text": "Text", + "field_type.unknown": "Unknown Type", "field.add": "Add Field", "field.add.plural": "Add Fields", "field.confirm_remove": "Are you sure you want to remove this \"{name}\" field?", @@ -80,13 +88,6 @@ "field.mixed_data": "Mixed Data", "field.paste": "Paste Field", "field.remove": "Remove Field", - "field_template.all_field_templates": "All Field Templates", - "field_template.create": "Create Field Template", - "field_template.create_add": "Create && Add \"{query}\"", - "field_template_manager.title": "Library Field Templates", - "field_type.datetime": "Datetime", - "field_type.text": "Text", - "field_type.unknown": "Unknown Type", "file.date_added": "Date Added", "file.date_created": "Date Created", "file.date_modified": "Date Modified", @@ -100,8 +101,8 @@ "file.duplicates.fix": "Fix Duplicate Files", "file.duplicates.matches": "Duplicate File Matches: {count}", "file.duplicates.matches_uninitialized": "Duplicate File Matches: N/A", - "file.duplicates.mirror.description": "Mirror the Entry data across each duplicate match set, combining all data while not removing or duplicating fields. This operation will not delete any files or data.", "file.duplicates.mirror_entries": "&Mirror Entries", + "file.duplicates.mirror.description": "Mirror the Entry data across each duplicate match set, combining all data while not removing or duplicating fields. This operation will not delete any files or data.", "file.duration": "Length", "file.not_found": "File Not Found", "file.open_file": "Open file", @@ -150,11 +151,11 @@ "generic.skip_alt": "&Skip", "generic.yes": "Yes", "home.search": "Search", - "home.search.view_limit": "View Limit:", "home.search_entries": "Search Entries", "home.search_field_templates": "Search Field Templates", "home.search_library": "Search Library", "home.search_tags": "Search Tags", + "home.search.view_limit": "View Limit:", "home.show_hidden_entries": "Show Hidden Entries", "home.thumbnail_size": "Thumbnail Size", "home.thumbnail_size.extra_large": "Extra Large Thumbnails", @@ -187,13 +188,6 @@ "json_migration.title.new_lib": "

v9.5+ Library

", "json_migration.title.old_lib": "

v9.4 Library

", "landing.open_create_library": "Open/Create Library {shortcut}", - "library.missing": "Library Location is Missing", - "library.name": "Library", - "library.refresh.scanning.plural": "Scanning Directories for New Files...\n{searched_count} Files Searched, {found_count} New Files Found", - "library.refresh.scanning.singular": "Scanning Directories for New Files...\n{searched_count} File Searched, {found_count} New Files Found", - "library.refresh.scanning_preparing": "Scanning Directories for New Files...\nPreparing...", - "library.refresh.title": "Refreshing Directories", - "library.scan_library.title": "Scanning Library", "library_info.cleanup": "Cleanup", "library_info.cleanup.backups": "Library Backups:", "library_info.cleanup.dupe_files": "Duplicate Files:", @@ -213,6 +207,13 @@ "library_object.name_required": "Name (Required)", "library_object.slug": "ID Slug", "library_object.slug_required": "ID Slug (Required)", + "library.missing": "Library Location is Missing", + "library.name": "Library", + "library.refresh.scanning_preparing": "Scanning Directories for New Files...\nPreparing...", + "library.refresh.scanning.plural": "Scanning Directories for New Files...\n{searched_count} Files Searched, {found_count} New Files Found", + "library.refresh.scanning.singular": "Scanning Directories for New Files...\n{searched_count} File Searched, {found_count} New Files Found", + "library.refresh.title": "Refreshing Directories", + "library.scan_library.title": "Scanning Library", "macros.running.dialog.new_entries": "Running Configured Macros on {count}/{total} New File Entries...", "macros.running.dialog.title": "Running Macros on New Entries", "media_player.autoplay": "Autoplay", @@ -288,6 +289,7 @@ "settings.show_filenames_in_grid": "Show Filenames in Grid", "settings.show_recent_libraries": "Show Recent Libraries", "settings.splash.label": "Splash Screen", + "settings.splash.option.aurora": "Aurora (9.6)", "settings.splash.option.classic": "Classic (9.0)", "settings.splash.option.default": "Default", "settings.splash.option.goo_gears": "Open Source (9.4)", @@ -323,11 +325,12 @@ "status.library_version_found": "Found:", "status.library_version_mismatch": "Library Version Mismatch!", "status.results": "Results", - "status.results.invalid_syntax": "Invalid Search Syntax:", "status.results_found": "{count} Results Found ({time_span})", + "status.results.invalid_syntax": "Invalid Search Syntax:", + "tag_manager.title": "Library Tags", "tag.add": "Add Tag", - "tag.add.plural": "Add Tags", "tag.add_to_search": "Add to Search", + "tag.add.plural": "Add Tags", "tag.aliases": "Aliases", "tag.all_tags": "All Tags", "tag.choose_color": "Choose Tag Color", @@ -348,7 +351,6 @@ "tag.search_for_tag": "Search for Tag", "tag.shorthand": "Shorthand", "tag.tag_name_required": "Tag Name (Required)", - "tag_manager.title": "Library Tags", "trash.context.ambiguous": "Move file(s) to {trash_term}", "trash.context.plural": "Move files to {trash_term}", "trash.context.singular": "Move file to {trash_term}", diff --git a/tests/qt/test_resource_manager.py b/tests/qt/test_resource_manager.py index 5a6d72df5..dd6afbb43 100644 --- a/tests/qt/test_resource_manager.py +++ b/tests/qt/test_resource_manager.py @@ -13,6 +13,6 @@ def test_get(): rm = ResourceManager() - for res in rm._map: # pyright: ignore[reportPrivateUsage] + for res in rm._map: assert rm.get(res), f"Could not get resource '{res}'" assert unwrap(rm.get_path(res)).exists(), f"Filepath for resource '{res}' does not exist" diff --git a/tests/test_translations.py b/tests/test_translations.py index 8d0533f37..5a699de5e 100644 --- a/tests/test_translations.py +++ b/tests/test_translations.py @@ -3,6 +3,7 @@ import string +import warnings from pathlib import Path import pytest @@ -57,6 +58,8 @@ def test_format_key_validity(translation_filename: str): def test_for_unnecessary_translations(translation_filename: str): default_translation = load_translation("en.json") translation = load_translation(translation_filename) - assert set(default_translation.keys()).issuperset(translation.keys()), ( - f"Translation {translation_filename} has unnecessary keys ({set(translation.keys()).difference(default_translation.keys())})" # noqa: E501 - ) + if not set(default_translation.keys()).issuperset(translation.keys()): + message = str( + f"Translation {translation_filename} has unnecessary keys ({set(translation.keys()).difference(default_translation.keys())})", # noqa: E501 + ) + warnings.warn(message, stacklevel=1)