mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-06-28 01:49:10 +00:00
ui: update about window
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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":
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
BIN
src/tagstudio/resources/qt/images/about_bg.jpg
Normal file
BIN
src/tagstudio/resources/qt/images/about_bg.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 201 KiB |
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user