ui: update about window

This commit is contained in:
Travis Abendshien
2026-06-27 15:49:23 -07:00
parent 5fc682284e
commit af60c98b49
9 changed files with 109 additions and 41 deletions

View File

@@ -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.

View File

@@ -4,10 +4,11 @@
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,
@@ -18,7 +19,7 @@ from PySide6.QtWidgets import (
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
@@ -26,9 +27,15 @@ from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
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):
"""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"])
@@ -46,12 +53,14 @@ class AboutModal(QWidget):
"font-weight: 500;"
"padding: 2px;"
)
self.setStyleSheet("QLabel {color: white}")
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setMinimumSize(420, 500)
self.setMaximumSize(600, 800)
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)
@@ -71,13 +80,19 @@ class AboutModal(QWidget):
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"<h3>{Translations['about.version']} {VERSION} {branch}</h3>")
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# Version --------------------------------------------------------------
self.version_label = QLabel(f"<h3>{AboutModal.VERSION_STR}</h3>")
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)
@@ -86,6 +101,7 @@ class AboutModal(QWidget):
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"]
@@ -101,6 +117,10 @@ class AboutModal(QWidget):
f'<span style="color:{green}">{found}</span> (' + ff_version["ffprobe"] + ")"
)
ripgrep_status = f'<span style="color:{amber}">{missing}</span>'
if which("rg") is not None:
ripgrep_status = f'<span style="color:{green}">{found}</span>'
self.system_info_widget = QWidget()
self.system_info_layout = QFormLayout(self.system_info_widget)
self.system_info_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
@@ -118,34 +138,61 @@ class AboutModal(QWidget):
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"
@@ -156,6 +203,7 @@ class AboutModal(QWidget):
f'<a href="{docs_link}">{Translations["about.documentation"]}</a> | '
f'<a href="{discord_link}">Discord</a></p>'
)
self.links_label.setStyleSheet("QLabel {color: #809782ff}")
self.links_label.setWordWrap(True)
self.links_label.setOpenExternalLinks(True)
self.links_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
@@ -172,12 +220,21 @@ class AboutModal(QWidget):
# 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.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)

View File

@@ -27,6 +27,7 @@ FFMPEG_MACOS_LOCATIONS: list[str] = [
]
# 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":

View File

@@ -18,6 +18,7 @@ class ResourceManager:
_instance: ResourceManager | None
# Resources IDs from "resources.json"
about_bg: QPixmap
adobe_illustrator: Image.Image
adobe_photoshop: Image.Image
affinity_photo: Image.Image

View File

@@ -1,4 +1,8 @@
{
"about_bg": {
"mode": "qpixmap",
"path": "qt/images/about_bg.jpg"
},
"adobe_illustrator": {
"mode": "pil",
"path": "qt/images/file_icons/adobe_illustrator.png"
@@ -91,6 +95,14 @@
"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": {
"mode": "pil",
"path": "qt/images/file_icons/presentation.png"
@@ -158,13 +170,5 @@
"volume_icon": {
"mode": "pil",
"path": "qt/images/bxs-volume-full-solid.png"
},
"mute_icon": {
"mode": "pil",
"path": "qt/images/bxs-volume-mute-solid.png"
},
"pause_icon": {
"mode": "pil",
"path": "qt/images/pause.png"
}
}

View File

@@ -10,7 +10,7 @@ from PySide6.QtCore import QRect, Qt
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
@@ -21,9 +21,6 @@ logger = structlog.get_logger(__name__)
class SplashScreen:
"""The custom splash screen widget for TagStudio."""
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"
VERSION_STR: str = f"{Translations['about.version']} {VERSION} {(' (' + VERSION_BRANCH + ')') if VERSION_BRANCH else ''}" # noqa: E501
DEFAULT_SPLASH = Splash.AURORA
@@ -76,7 +73,7 @@ class SplashScreen:
painter.drawText(
QRect(0, -25, 960, 540),
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
SplashScreen.COPYRIGHT,
COPYRIGHT,
)
# Version
pen = QPen(QColor("#9782ff"))
@@ -96,7 +93,7 @@ class SplashScreen:
painter.setPen(pen)
painter.drawText(
QRect(40, 450, 960, 540),
SplashScreen.COPYRIGHT_COMPACT,
COPYRIGHT_COMPACT,
)
# Version
font = painter.font()
@@ -122,7 +119,7 @@ class SplashScreen:
painter.drawText(
QRect(88, -25, 960, 540),
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignLeft),
SplashScreen.COPYRIGHT,
COPYRIGHT,
)
# Version
font.setPointSize(math.floor(22 * point_size_scale))
@@ -145,7 +142,7 @@ class SplashScreen:
painter.drawText(
QRect(0, -25, 960, 540),
int(Qt.AlignmentFlag.AlignBottom | Qt.AlignmentFlag.AlignHCenter),
SplashScreen.COPYRIGHT,
COPYRIGHT,
)
# Version
pen = QPen(QColor("#7758FF"))

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

@@ -1,8 +1,8 @@
{
"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",

View File

@@ -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)