From 8989bedeb594a09d203adeef58136627af8fead4 Mon Sep 17 00:00:00 2001
From: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Date: Thu, 25 Jun 2026 17:55:15 -0700
Subject: [PATCH] refactor: move stylesheets to central file
---
src/tagstudio/core/constants.py | 8 +-
.../controllers/edit_field_template_modal.py | 8 +-
.../fix_ignored_modal_controller.py | 3 +-
.../library_info_window_controller.py | 3 +-
.../qt/controllers/paged_panel_controller.py | 5 +-
src/tagstudio/qt/mixed/about_modal.py | 63 ++-
src/tagstudio/qt/mixed/add_field.py | 4 +-
src/tagstudio/qt/mixed/build_color.py | 116 +----
src/tagstudio/qt/mixed/build_namespace.py | 16 +-
src/tagstudio/qt/mixed/build_tag.py | 62 +--
src/tagstudio/qt/mixed/color_box.py | 33 +-
src/tagstudio/qt/mixed/datetime_picker.py | 3 +-
src/tagstudio/qt/mixed/field_containers.py | 2 +-
src/tagstudio/qt/mixed/field_widget.py | 17 +-
src/tagstudio/qt/mixed/file_attributes.py | 28 +-
src/tagstudio/qt/mixed/fix_dupe_files.py | 14 +-
src/tagstudio/qt/mixed/fix_unlinked.py | 3 +-
src/tagstudio/qt/mixed/folders_to_tags.py | 6 +-
src/tagstudio/qt/mixed/migration_modal.py | 9 +-
src/tagstudio/qt/mixed/tag_color_label.py | 79 +--
src/tagstudio/qt/mixed/tag_color_manager.py | 11 +-
src/tagstudio/qt/mixed/tag_color_preview.py | 37 +-
src/tagstudio/qt/mixed/tag_color_selection.py | 91 +---
src/tagstudio/qt/mixed/tag_widget.py | 110 +----
src/tagstudio/qt/ts_qt.py | 18 +-
src/tagstudio/qt/views/edit_text_view.py | 4 +-
.../qt/views/field_template_widget_view.py | 44 +-
.../qt/views/library_info_window_view.py | 5 +-
src/tagstudio/qt/views/preview_panel_view.py | 31 +-
src/tagstudio/qt/views/search_panel_view.py | 36 +-
.../qt/views/stylesheets/stylesheets.py | 452 ++++++++++++++++--
src/tagstudio/resources/translations/en.json | 3 +
32 files changed, 638 insertions(+), 686 deletions(-)
diff --git a/src/tagstudio/core/constants.py b/src/tagstudio/core/constants.py
index 4cd6f582..6c1cd65f 100644
--- a/src/tagstudio/core/constants.py
+++ b/src/tagstudio/core/constants.py
@@ -4,7 +4,10 @@
VERSION: str = "9.5.7" # Major.Minor.Patch
VERSION_BRANCH: str = "" # Usually "" or "Pre-Release"
+GITHUB_REPO_URL = "https://github.com/TagStudioDev/TagStudio"
GITHUB_RELEASE_URL = "https://github.com/TagStudioDev/TagStudio/releases/latest"
+DOCS_URL = "https://docs.tagstud.io"
+DISCORD_URL = "https://discord.com/invite/hRNnVKhF2G"
# The folder & file names where TagStudio keeps its data relative to a library.
TS_FOLDER_NAME: str = ".TagStudio"
@@ -13,9 +16,7 @@ COLLAGE_FOLDER_NAME: str = "collages"
IGNORE_NAME: str = ".ts_ignore"
THUMB_CACHE_NAME: str = "thumbs"
-FONT_SAMPLE_TEXT: str = (
- """ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?@$%(){}[]"""
-)
+FONT_SAMPLE_TEXT: str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!?@$%(){}[]"
FONT_SAMPLE_SIZES: list[int] = [10, 15, 20]
# NOTE: These were the field IDs used for the "Tags", "Content Tags", and "Meta Tags" fields inside
@@ -27,5 +28,4 @@ TAG_FAVORITE = 1
TAG_META = 2
RESERVED_TAG_START = 0
RESERVED_TAG_END = 999
-
RESERVED_NAMESPACE_PREFIX = "tagstudio"
diff --git a/src/tagstudio/qt/controllers/edit_field_template_modal.py b/src/tagstudio/qt/controllers/edit_field_template_modal.py
index 662649b7..5ac7cf30 100644
--- a/src/tagstudio/qt/controllers/edit_field_template_modal.py
+++ b/src/tagstudio/qt/controllers/edit_field_template_modal.py
@@ -9,9 +9,9 @@ from tagstudio.core.library.alchemy.fields import (
DatetimeFieldTemplate,
TextFieldTemplate,
)
-from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.edit_field_template_modal_view import EditFieldTemplateModalView
+from tagstudio.qt.views.stylesheets.stylesheets import line_edit_style
logger = structlog.get_logger(__name__)
@@ -71,11 +71,7 @@ class EditFieldTemplateModal(EditFieldTemplateModalView):
def __on_name_changed(self):
is_empty = not self.name_field.text().strip()
- self.name_field.setStyleSheet(
- f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px"
- if is_empty
- else ""
- )
+ self.name_field.setStyleSheet(line_edit_style() if is_empty else "")
if self.panel_save_button is not None:
self.panel_save_button.setDisabled(is_empty)
diff --git a/src/tagstudio/qt/controllers/fix_ignored_modal_controller.py b/src/tagstudio/qt/controllers/fix_ignored_modal_controller.py
index e841f61d..cad8181c 100644
--- a/src/tagstudio/qt/controllers/fix_ignored_modal_controller.py
+++ b/src/tagstudio/qt/controllers/fix_ignored_modal_controller.py
@@ -13,6 +13,7 @@ from tagstudio.qt.mixed.progress_bar import ProgressWidget
from tagstudio.qt.mixed.remove_ignored_modal import RemoveIgnoredModal
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.fix_ignored_modal_view import FixIgnoredEntriesModalView
+from tagstudio.qt.views.stylesheets.stylesheets import header
# Only import for type checking/autocompletion, will not be imported at runtime.
if TYPE_CHECKING:
@@ -78,7 +79,7 @@ class FixIgnoredEntriesModal(FixIgnoredEntriesModalView):
count_text: str = Translations.format(
"entries.ignored.ignored_count", count=count if count >= 0 else "—"
)
- self.ignored_count_label.setText(f"
{count_text}
")
+ self.ignored_count_label.setText(header(count_text, 3))
def update_driver_widgets(self):
if (
diff --git a/src/tagstudio/qt/controllers/library_info_window_controller.py b/src/tagstudio/qt/controllers/library_info_window_controller.py
index 426e1edd..d0828dcb 100644
--- a/src/tagstudio/qt/controllers/library_info_window_controller.py
+++ b/src/tagstudio/qt/controllers/library_info_window_controller.py
@@ -22,6 +22,7 @@ from tagstudio.core.utils.types import unwrap
from tagstudio.qt.translations import Translations
from tagstudio.qt.utils import file_opener
from tagstudio.qt.views.library_info_window_view import LibraryInfoWindowView
+from tagstudio.qt.views.stylesheets.stylesheets import header
# Only import for type checking/autocompletion, will not be imported at runtime.
if TYPE_CHECKING:
@@ -61,7 +62,7 @@ class LibraryInfoWindow(LibraryInfoWindowView):
title: str = Translations.format(
"library_info.title", library_dir=self.lib.library_dir.stem
)
- self.title_label.setText(f"{title}
")
+ self.title_label.setText(header(title, 2))
def update_stats(self):
self.entry_count_label.setText(f"{self.lib.entries_count}")
diff --git a/src/tagstudio/qt/controllers/paged_panel_controller.py b/src/tagstudio/qt/controllers/paged_panel_controller.py
index f4a7e8e8..53cc146f 100644
--- a/src/tagstudio/qt/controllers/paged_panel_controller.py
+++ b/src/tagstudio/qt/controllers/paged_panel_controller.py
@@ -10,6 +10,7 @@ from PySide6.QtCore import Qt
from PySide6.QtWidgets import QHBoxLayout, QLabel, QVBoxLayout, QWidget
from tagstudio.qt.controllers.paged_panel_state import PagedPanelState
+from tagstudio.qt.views.stylesheets.stylesheets import header
logger = structlog.get_logger(__name__)
@@ -89,7 +90,7 @@ class PagedPanel(QWidget):
# Update Title
self.setWindowTitle(frame.title)
- self.title_label.setText(f"{frame.title}
")
+ self.title_label.setText(header(frame.title, 1))
# Update Body Widget
if self.body_layout.itemAt(0):
@@ -107,7 +108,7 @@ class PagedPanel(QWidget):
if isinstance(item, QWidget):
self.button_nav_layout.addWidget(item)
item.setHidden(False)
- elif isinstance(item, int):
+ elif isinstance(item, int): # pyright: ignore[reportUnnecessaryIsInstance]
self.button_nav_layout.addStretch(item)
@override
diff --git a/src/tagstudio/qt/mixed/about_modal.py b/src/tagstudio/qt/mixed/about_modal.py
index 3d1092f1..61033973 100644
--- a/src/tagstudio/qt/mixed/about_modal.py
+++ b/src/tagstudio/qt/mixed/about_modal.py
@@ -3,10 +3,11 @@
import math
+from pathlib import Path
from PIL import ImageQt
from PySide6.QtCore import Qt
-from PySide6.QtGui import QGuiApplication, QPixmap
+from PySide6.QtGui import QPixmap
from PySide6.QtWidgets import (
QFormLayout,
QHBoxLayout,
@@ -17,35 +18,29 @@ from PySide6.QtWidgets import (
QWidget,
)
-from tagstudio.core.constants import VERSION, VERSION_BRANCH
-from tagstudio.core.enums import Theme
+from tagstudio.core.constants import (
+ DISCORD_URL,
+ DOCS_URL,
+ GITHUB_REPO_URL,
+ VERSION,
+ VERSION_BRANCH,
+)
from tagstudio.core.ts_core import TagStudioCore
from tagstudio.core.utils.types import unwrap
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.views.stylesheets.stylesheets import form_content_style, header
class AboutModal(QWidget):
- def __init__(self, config_path):
+ def __init__(self, config_path: Path | str):
super().__init__()
self.setWindowTitle(Translations["about.title"])
self.rm: ResourceManager = ResourceManager()
- # TODO: There should be a global button theme somewhere.
- self.form_content_style = (
- f"background-color:{
- Theme.COLOR_BG.value
- if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
- else Theme.COLOR_BG_LIGHT.value
- };"
- "border-radius:3px;"
- "font-weight: 500;"
- "padding: 2px;"
- )
-
self.setWindowModality(Qt.WindowModality.ApplicationModal)
self.setMinimumSize(360, 540)
self.setMaximumSize(600, 600)
@@ -72,7 +67,8 @@ class AboutModal(QWidget):
# Title ----------------------------------------------------------------
branch: str = (" (" + VERSION_BRANCH + ")") if VERSION_BRANCH else ""
- self.title_label = QLabel(f"TagStudio Alpha {VERSION}{branch}
")
+ # NOTE: Do not localize program name.
+ self.title_label = QLabel(header(f"TagStudio Alpha {VERSION}{branch}", 2))
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
# Description ----------------------------------------------------------
@@ -105,14 +101,18 @@ class AboutModal(QWidget):
self.system_info_layout.setLabelAlignment(Qt.AlignmentFlag.AlignRight)
# Version
- version_title = QLabel("Version")
- most_recent_release = unwrap(TagStudioCore.get_most_recent_release_version(), "UNKNOWN")
- version_content_style = self.form_content_style
- if most_recent_release == VERSION:
+ version_title = QLabel(Translations["about.version"])
+ latest_version = unwrap(TagStudioCore.get_most_recent_release_version(), "?")
+ version_content_style = form_content_style()
+ if latest_version == VERSION:
version_content = QLabel(f"{VERSION}")
else:
- version_content = QLabel(f"{VERSION} (Latest Release: {most_recent_release})")
- version_content_style += "color: #d9534f;"
+ version_content = QLabel(
+ Translations.format(
+ "about.version.latest", built_version=VERSION, latest_version=latest_version
+ )
+ )
+ version_content_style += f"color: {red};"
version_content.setStyleSheet(version_content_style)
version_content.setMaximumWidth(version_content.sizeHint().width())
self.system_info_layout.addRow(version_title, version_content)
@@ -120,40 +120,37 @@ class AboutModal(QWidget):
# License
license_title = QLabel(f"{Translations['about.license']}")
license_content = QLabel("GPLv3")
- license_content.setStyleSheet(self.form_content_style)
+ license_content.setStyleSheet(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.setStyleSheet(self.form_content_style)
+ config_path_content.setStyleSheet(form_content_style())
config_path_content.setWordWrap(True)
self.system_info_layout.addRow(config_path_title, config_path_content)
# FFmpeg Status
ffmpeg_path_title = QLabel("FFmpeg")
ffmpeg_path_content = QLabel(f"{ffmpeg_status}")
- ffmpeg_path_content.setStyleSheet(self.form_content_style)
+ ffmpeg_path_content.setStyleSheet(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.setStyleSheet(self.form_content_style)
+ ffprobe_path_content.setStyleSheet(form_content_style())
ffprobe_path_content.setMaximumWidth(ffprobe_path_content.sizeHint().width())
self.system_info_layout.addRow(ffprobe_path_title, ffprobe_path_content)
# Links ----------------------------------------------------------------
- repo_link = "https://github.com/TagStudioDev/TagStudio"
- docs_link = "https://docs.tagstud.io"
- discord_link = "https://discord.com/invite/hRNnVKhF2G"
self.links_label = QLabel(
- f'GitHub | '
- f'{Translations["about.documentation"]} | '
- f'Discord
'
+ f'GitHub | '
+ f'{Translations["about.documentation"]} | '
+ f'Discord
'
)
self.links_label.setWordWrap(True)
self.links_label.setOpenExternalLinks(True)
diff --git a/src/tagstudio/qt/mixed/add_field.py b/src/tagstudio/qt/mixed/add_field.py
index 653b9788..946f4d74 100644
--- a/src/tagstudio/qt/mixed/add_field.py
+++ b/src/tagstudio/qt/mixed/add_field.py
@@ -19,6 +19,7 @@ from PySide6.QtWidgets import (
from tagstudio.core.library.alchemy.library import Library
from tagstudio.qt.translations import FIELD_TYPE_KEYS, Translations
+from tagstudio.qt.views.stylesheets.stylesheets import header
logger = structlog.get_logger(__name__)
@@ -39,10 +40,9 @@ class AddFieldModal(QWidget):
self.root_layout = QVBoxLayout(self)
self.root_layout.setContentsMargins(6, 6, 6, 6)
- self.title_widget = QLabel(Translations["field.add"])
+ self.title_widget = QLabel(header(Translations["field.add"], 3))
self.title_widget.setObjectName("fieldTitle")
self.title_widget.setWordWrap(True)
- self.title_widget.setStyleSheet("font-weight:bold;font-size:14px;padding-top: 6px;")
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.list_widget = QListWidget()
diff --git a/src/tagstudio/qt/mixed/build_color.py b/src/tagstudio/qt/mixed/build_color.py
index 1c16f036..9791aaa7 100644
--- a/src/tagstudio/qt/mixed/build_color.py
+++ b/src/tagstudio/qt/mixed/build_color.py
@@ -3,6 +3,7 @@
import contextlib
+from typing import override
import structlog
from PySide6.QtCore import Qt, Signal
@@ -24,15 +25,14 @@ from tagstudio.core.library.alchemy.library import Library, slugify
from tagstudio.core.library.alchemy.models import TagColorGroup
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.mixed.tag_color_preview import TagColorPreview
-from tagstudio.qt.mixed.tag_widget import (
- get_border_color,
- get_highlight_color,
- get_text_color,
-)
-from tagstudio.qt.models.palette import ColorType, UiColor, get_tag_color, get_ui_color
+from tagstudio.qt.models.palette import ColorType, get_tag_color
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.panel_modal import PanelWidget
-from tagstudio.qt.views.stylesheets.stylesheets import checkbox_style
+from tagstudio.qt.views.stylesheets.stylesheets import (
+ checkbox_style,
+ line_edit_style,
+ list_button_style,
+)
logger = structlog.get_logger(__name__)
@@ -130,7 +130,6 @@ class BuildColorPanel(PanelWidget):
color=QColor(unwrap(self.preview_button.tag_color_group).secondary)
if unwrap(self.preview_button.tag_color_group).secondary
else None,
- color_border=checked,
)
)
self.border_layout.addWidget(self.border_checkbox)
@@ -193,89 +192,20 @@ class BuildColorPanel(PanelWidget):
def update_primary(self, color: QColor):
logger.info("[BuildColorPanel] Updating Primary", primary_color=color)
- highlight_color = get_highlight_color(color)
- text_color = get_text_color(color, highlight_color)
- border_color = get_border_color(color)
-
hex_code = color.name().upper()
+
self.primary_button.setText(hex_code)
- self.primary_button.setStyleSheet(
- f"QPushButton{{"
- f"background: rgba{color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"font-weight: 600;"
- f"border-color: rgba{border_color.toTuple()};"
- f"border-radius: 6px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"padding-right: 4px;"
- f"padding-bottom: 1px;"
- f"padding-left: 4px;"
- f"font-size: 13px"
- f"}}"
- f"QPushButton::hover{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QPushButton::pressed{{"
- f"background: rgba{highlight_color.toTuple()};"
- f"color: rgba{color.toTuple()};"
- f"border-color: rgba{color.toTuple()};"
- f"}}"
- f"QPushButton::focus{{"
- f"padding-right: 0px;"
- f"padding-left: 0px;"
- f"outline-style: solid;"
- f"outline-width: 1px;"
- f"outline-radius: 4px;"
- f"outline-color: rgba{text_color.toTuple()};"
- f"}}"
- )
+ self.primary_button.setStyleSheet(list_button_style(color))
self.preview_button.set_tag_color_group(self.build_color()[1])
- def update_secondary(self, color: QColor | None = None, color_border: bool = False):
+ def update_secondary(self, color: QColor | None = None):
logger.info("[BuildColorPanel] Updating Secondary", color=color)
color_ = color or QColor(get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT))
-
- highlight_color = get_highlight_color(color_)
- text_color = get_text_color(color_, highlight_color)
- border_color = get_border_color(color_)
-
hex_code = "" if not color else color.name().upper()
- self.secondary_button.setText(
- Translations["color.title.no_color"] if not color else hex_code
- )
- self.secondary_button.setStyleSheet(
- f"QPushButton{{"
- f"background: rgba{color_.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"font-weight: 600;"
- f"border-color: rgba{border_color.toTuple()};"
- f"border-radius: 6px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"padding-right: 4px;"
- f"padding-bottom: 1px;"
- f"padding-left: 4px;"
- f"font-size: 13px"
- f"}}"
- f"QPushButton::hover{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QPushButton::pressed{{"
- f"background: rgba{highlight_color.toTuple()};"
- f"color: rgba{color_.toTuple()};"
- f"border-color: rgba{color_.toTuple()};"
- f"}}"
- f"QPushButton::focus{{"
- f"padding-right: 0px;"
- f"padding-left: 0px;"
- f"outline-style: solid;"
- f"outline-width: 1px;"
- f"outline-radius: 4px;"
- f"outline-color: rgba{text_color.toTuple()};"
- f"}}"
- )
+
+ self.secondary_button.setText(hex_code if color else Translations["color.title.no_color"])
+ self.secondary_button.setStyleSheet(list_button_style(color_))
self.preview_button.set_tag_color_group(self.build_color()[1])
def update_known_colors(self):
@@ -317,17 +247,9 @@ class BuildColorPanel(PanelWidget):
is_slug_empty = not slug
is_invalid = False
- self.name_field.setStyleSheet(
- f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px"
- if is_name_empty
- else ""
- )
+ self.name_field.setStyleSheet(line_edit_style() if is_name_empty else "")
- self.slug_field.setStyleSheet(
- f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px"
- if is_slug_empty or is_invalid
- else ""
- )
+ self.slug_field.setStyleSheet(line_edit_style() if is_slug_empty or is_invalid else "")
self.slug_field.setText(slug)
self.update_preview_text()
@@ -364,13 +286,7 @@ class BuildColorPanel(PanelWidget):
)
return (self.color_group, new_color)
+ @override
def parent_post_init(self):
- # self.setTabOrder(self.name_field, self.shorthand_field)
- # self.setTabOrder(self.shorthand_field, self.aliases_add_button)
- # self.setTabOrder(self.aliases_add_button, self.parent_tags_add_button)
- # self.setTabOrder(self.parent_tags_add_button, self.color_button)
- # self.setTabOrder(self.color_button, self.panel_cancel_button)
- # self.setTabOrder(self.panel_cancel_button, self.panel_save_button)
- # self.setTabOrder(self.panel_save_button, self.aliases_table.cellWidget(0, 1))
self.name_field.selectAll()
self.name_field.setFocus()
diff --git a/src/tagstudio/qt/mixed/build_namespace.py b/src/tagstudio/qt/mixed/build_namespace.py
index 5b91a700..36487a7b 100644
--- a/src/tagstudio/qt/mixed/build_namespace.py
+++ b/src/tagstudio/qt/mixed/build_namespace.py
@@ -3,6 +3,7 @@
import contextlib
+from typing import override
from uuid import uuid4
import structlog
@@ -12,9 +13,9 @@ from PySide6.QtWidgets import QLabel, QLineEdit, QVBoxLayout, QWidget
from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX
from tagstudio.core.library.alchemy.library import Library, ReservedNamespaceError, slugify
from tagstudio.core.library.alchemy.models import Namespace
-from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.panel_modal import PanelWidget
+from tagstudio.qt.views.stylesheets.stylesheets import line_edit_style
logger = structlog.get_logger(__name__)
@@ -111,17 +112,9 @@ class BuildNamespacePanel(PanelWidget):
is_slug_empty = not slug
is_invalid = False
- self.name_field.setStyleSheet(
- f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px"
- if is_name_empty
- else ""
- )
+ self.name_field.setStyleSheet(line_edit_style() if is_name_empty else "")
- self.slug_field.setStyleSheet(
- f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px"
- if is_slug_empty or is_invalid
- else ""
- )
+ self.slug_field.setStyleSheet(line_edit_style() if is_slug_empty or is_invalid else "")
self.slug_field.setText(slug)
@@ -156,6 +149,7 @@ class BuildNamespacePanel(PanelWidget):
logger.info("[BuildNamespacePanel] Built Namespace", slug=slug, name=name)
return namespace
+ @override
def parent_post_init(self):
self.setTabOrder(self.name_field, self.slug_field)
self.name_field.selectAll()
diff --git a/src/tagstudio/qt/mixed/build_tag.py b/src/tagstudio/qt/mixed/build_tag.py
index eb17024c..4c81a968 100644
--- a/src/tagstudio/qt/mixed/build_tag.py
+++ b/src/tagstudio/qt/mixed/build_tag.py
@@ -33,15 +33,19 @@ from tagstudio.qt.mixed.tag_color_preview import TagColorPreview
from tagstudio.qt.mixed.tag_color_selection import TagColorSelection
from tagstudio.qt.mixed.tag_widget import (
TagWidget,
- get_border_color,
- get_highlight_color,
- get_primary_color,
- get_text_color,
+ get_tag_border_color,
+ get_tag_highlight_color,
+ get_tag_primary_color,
+ get_tag_text_color,
)
-from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.panel_modal import PanelModal, PanelWidget
-from tagstudio.qt.views.stylesheets.stylesheets import checkbox_style
+from tagstudio.qt.views.stylesheets.stylesheets import (
+ checkbox_style,
+ colored_radio_button_style,
+ header,
+ line_edit_style,
+)
from tagstudio.qt.views.tag_search_panel_view import TagSearchPanelView
logger = structlog.get_logger(__name__)
@@ -244,7 +248,7 @@ class BuildTagPanel(PanelWidget):
self.root_layout.addWidget(self.aliases_add_button)
self.root_layout.addWidget(self.parent_tags_widget)
self.root_layout.addWidget(self.color_widget)
- self.root_layout.addWidget(QLabel("Properties
"))
+ self.root_layout.addWidget(QLabel(header(Translations["tag.properties"], 3)))
self.root_layout.addWidget(self.cat_widget)
self.root_layout.addWidget(self.hidden_widget)
@@ -357,13 +361,13 @@ class BuildTagPanel(PanelWidget):
row.setSpacing(3)
# Init Colors
- primary_color = get_primary_color(tag)
+ primary_color = get_tag_primary_color(tag)
border_color = (
- get_border_color(primary_color)
+ get_tag_border_color(primary_color)
if not (tag.color and tag.color.secondary and tag.color.color_border)
else (QColor(tag.color.secondary))
)
- highlight_color = get_highlight_color(
+ highlight_color = get_tag_highlight_color(
primary_color
if not (tag.color and tag.color.secondary)
else QColor(tag.color.secondary)
@@ -372,7 +376,7 @@ class BuildTagPanel(PanelWidget):
if tag.color and tag.color.secondary:
text_color = QColor(tag.color.secondary)
else:
- text_color = get_text_color(primary_color, highlight_color)
+ text_color = get_tag_text_color(primary_color, highlight_color)
# Add Tag Widget
tag_widget = TagWidget(
@@ -395,35 +399,7 @@ class BuildTagPanel(PanelWidget):
disam_button.setFixedSize(22, 22)
disam_button.setToolTip(Translations["tag.disambiguation.tooltip"])
disam_button.setStyleSheet(
- f"QRadioButton{{"
- f"background: rgba{primary_color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"border-color: rgba{border_color.toTuple()};"
- f"border-radius: 6px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"}}"
- f"QRadioButton::indicator{{"
- f"width: 10px;"
- f"height: 10px;"
- f"border-radius: 2px;"
- f"margin: 4px;"
- f"}}"
- f"QRadioButton::indicator:checked{{"
- f"background: rgba{text_color.toTuple()};"
- f"}}"
- f"QRadioButton::hover{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QRadioButton::pressed{{"
- f"background: rgba{border_color.toTuple()};"
- f"color: rgba{primary_color.toTuple()};"
- f"border-color: rgba{primary_color.toTuple()};"
- f"}}"
- f"QRadioButton::focus{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"outline:none;"
- f"}}"
+ colored_radio_button_style(primary_color, text_color, border_color, highlight_color)
)
self.disam_button_group.addButton(disam_button)
@@ -543,11 +519,7 @@ class BuildTagPanel(PanelWidget):
def on_name_changed(self):
is_empty = not self.name_field.text().strip()
- self.name_field.setStyleSheet(
- f"border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)}; border-radius: 2px"
- if is_empty
- else ""
- )
+ self.name_field.setStyleSheet(line_edit_style() if is_empty else "")
if self.panel_save_button is not None:
self.panel_save_button.setDisabled(is_empty)
diff --git a/src/tagstudio/qt/mixed/color_box.py b/src/tagstudio/qt/mixed/color_box.py
index d4d7703e..88771bbd 100644
--- a/src/tagstudio/qt/mixed/color_box.py
+++ b/src/tagstudio/qt/mixed/color_box.py
@@ -10,16 +10,15 @@ from PySide6.QtCore import Signal
from PySide6.QtWidgets import QMessageBox, QPushButton
from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX
-from tagstudio.core.library.alchemy.enums import TagColorEnum
from tagstudio.core.library.alchemy.models import TagColorGroup
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.mixed.build_color import BuildColorPanel
from tagstudio.qt.mixed.field_widget import FieldWidget
from tagstudio.qt.mixed.tag_color_label import TagColorLabel
-from tagstudio.qt.models.palette import ColorType, get_tag_color
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.layouts.flow_layout import FlowLayout
from tagstudio.qt.views.panel_modal import PanelModal
+from tagstudio.qt.views.stylesheets.stylesheets import add_button_style
if typing.TYPE_CHECKING:
from tagstudio.core.library.alchemy.library import Library
@@ -43,34 +42,6 @@ class ColorBoxWidget(FieldWidget):
title = "" if not self.lib.engine else self.lib.get_namespace_name(group)
super().__init__(title)
- self.add_button_stylesheet = (
- f"QPushButton{{"
- f"background: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};"
- f"color: {get_tag_color(ColorType.TEXT, TagColorEnum.DEFAULT)};"
- f"font-weight: 600;"
- f"border-color:{get_tag_color(ColorType.BORDER, TagColorEnum.DEFAULT)};"
- f"border-radius: 6px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"padding-right: 4px;"
- f"padding-bottom: 2px;"
- f"padding-left: 4px;"
- f"font-size: 15px"
- f"}}"
- f"QPushButton::hover{{"
- f"border-color:{get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};"
- f"}}"
- f"QPushButton::pressed{{"
- f"background: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};"
- f"color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};"
- f"border-color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};"
- f"}}"
- f"QPushButton::focus{{"
- f"border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};"
- f"outline:none;"
- f"}}"
- )
-
self.setObjectName("colorBox")
self.base_layout = FlowLayout()
self.base_layout.enable_grid_optimizations(value=True)
@@ -114,7 +85,7 @@ class ColorBoxWidget(FieldWidget):
add_button.setText("+")
add_button.setFlat(True)
add_button.setFixedSize(22, 22)
- add_button.setStyleSheet(self.add_button_stylesheet)
+ add_button.setStyleSheet(add_button_style())
add_button.clicked.connect(
lambda: self.edit_color(
TagColorGroup(
diff --git a/src/tagstudio/qt/mixed/datetime_picker.py b/src/tagstudio/qt/mixed/datetime_picker.py
index d28bfb1b..1706eaa5 100644
--- a/src/tagstudio/qt/mixed/datetime_picker.py
+++ b/src/tagstudio/qt/mixed/datetime_picker.py
@@ -10,6 +10,7 @@ from PySide6.QtCore import QDateTime
from PySide6.QtWidgets import QDateTimeEdit, QLineEdit, QVBoxLayout
from tagstudio.qt.views.panel_modal import PanelWidget
+from tagstudio.qt.views.stylesheets.stylesheets import title_line_edit_style
if typing.TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
@@ -46,7 +47,7 @@ class DatetimePicker(PanelWidget):
self.root_layout.setContentsMargins(6, 0, 6, 0)
self.name_field = QLineEdit()
- self.name_field.setStyleSheet("font-weight:bold;font-size:14px;padding-top:6px")
+ self.name_field.setStyleSheet(title_line_edit_style())
self.name_field.setText(name)
if isinstance(datetime, str):
diff --git a/src/tagstudio/qt/mixed/field_containers.py b/src/tagstudio/qt/mixed/field_containers.py
index 5a60a64e..6a7d5555 100644
--- a/src/tagstudio/qt/mixed/field_containers.py
+++ b/src/tagstudio/qt/mixed/field_containers.py
@@ -434,7 +434,7 @@ class FieldContainers(QWidget):
)
else:
text = f"{Translations['field.mixed_data']}"
- inner_widget = TextContainerWidget("Mixed Tags", text)
+ inner_widget = TextContainerWidget("Mixed Tags", text) # NOTE: Unlocalized but unused
container.set_inner_widget(inner_widget)
container.set_edit_callback()
diff --git a/src/tagstudio/qt/mixed/field_widget.py b/src/tagstudio/qt/mixed/field_widget.py
index 845ab504..4bc9acc6 100644
--- a/src/tagstudio/qt/mixed/field_widget.py
+++ b/src/tagstudio/qt/mixed/field_widget.py
@@ -14,7 +14,7 @@ from PySide6.QtCore import QEvent, 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.views.stylesheets.stylesheets import container_style, header
logger = structlog.get_logger(__name__)
@@ -37,17 +37,6 @@ class FieldContainer(QWidget):
trash_icon_128.load()
# TODO: There should be a global button theme somewhere.
- container_style = (
- f"QWidget#fieldContainer{{"
- "border-radius:4px;"
- f"}}"
- f"QWidget#fieldContainer::hover{{"
- f"background-color:{Theme.COLOR_HOVER.value};"
- f"}}"
- f"QWidget#fieldContainer::pressed{{"
- f"background-color:{Theme.COLOR_PRESSED.value};"
- f"}}"
- )
def __init__(self, title: str = "Field", inline: bool = True) -> None:
super().__init__()
@@ -126,7 +115,7 @@ class FieldContainer(QWidget):
self.inner_layout.addWidget(self.field)
self.set_title(title)
- self.setStyleSheet(FieldContainer.container_style)
+ self.setStyleSheet(container_style())
def set_copy_callback(self, callback: Callable[[], None] | None = None) -> None:
with catch_warnings(record=True):
@@ -166,7 +155,7 @@ class FieldContainer(QWidget):
return None
def set_title(self, title: str) -> None:
- self.title = self.title = f"{title}
"
+ self.title = header(title, 4)
self.title_widget.setText(self.title)
@override
diff --git a/src/tagstudio/qt/mixed/file_attributes.py b/src/tagstudio/qt/mixed/file_attributes.py
index 38dfd3f0..3193aee0 100644
--- a/src/tagstudio/qt/mixed/file_attributes.py
+++ b/src/tagstudio/qt/mixed/file_attributes.py
@@ -11,13 +11,12 @@ from datetime import timedelta
from pathlib import Path
import structlog
-from humanfriendly import format_size
+from humanfriendly import format_size # pyright: ignore[reportUnknownVariableType]
from PIL import ImageFont
from PySide6.QtCore import Qt
-from PySide6.QtGui import QGuiApplication
from PySide6.QtWidgets import QLabel, QVBoxLayout, QWidget
-from tagstudio.core.enums import ShowFilepathOption, Theme
+from tagstudio.core.enums import ShowFilepathOption
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.ignore import Ignore
from tagstudio.core.media_types import MediaCategories
@@ -25,6 +24,7 @@ from tagstudio.core.utils.types import unwrap
from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.translations import Translations
from tagstudio.qt.utils.file_opener import FileOpenerHelper, FileOpenerLabel
+from tagstudio.qt.views.stylesheets.stylesheets import properties_style
if typing.TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
@@ -48,26 +48,8 @@ class FileAttributes(QWidget):
root_layout.setContentsMargins(0, 0, 0, 0)
root_layout.setSpacing(0)
- label_bg_color = (
- Theme.COLOR_BG_DARK.value
- if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
- else Theme.COLOR_DARK_LABEL.value
- )
-
- self.date_style = "font-size:12px;"
+ self.date_style = "font-size: 12px;"
self.file_label_style = "font-size: 12px"
- self.properties_style = (
- f"background-color:{label_bg_color};"
- "color:#FFFFFF;"
- "font-family:Oxanium;"
- "font-weight:bold;"
- "font-size:12px;"
- "border-radius:3px;"
- "padding-top: 4px;"
- "padding-right: 1px;"
- "padding-bottom: 1px;"
- "padding-left: 1px;"
- )
self.file_label = FileOpenerLabel()
self.file_label.setObjectName("filenameLabel")
@@ -93,7 +75,7 @@ class FileAttributes(QWidget):
self.dimensions_label = QLabel()
self.dimensions_label.setObjectName("dimensionsLabel")
self.dimensions_label.setWordWrap(True)
- self.dimensions_label.setStyleSheet(self.properties_style)
+ self.dimensions_label.setStyleSheet(properties_style())
self.dimensions_label.setHidden(True)
self.date_container = QWidget()
diff --git a/src/tagstudio/qt/mixed/fix_dupe_files.py b/src/tagstudio/qt/mixed/fix_dupe_files.py
index c7c3165b..9b102dfa 100644
--- a/src/tagstudio/qt/mixed/fix_dupe_files.py
+++ b/src/tagstudio/qt/mixed/fix_dupe_files.py
@@ -19,6 +19,7 @@ from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.registries.dupe_files_registry import DupeFilesRegistry
from tagstudio.qt.mixed.mirror_entries_modal import MirrorEntriesModal
from tagstudio.qt.translations import Translations
+from tagstudio.qt.views.stylesheets.stylesheets import header
# Only import for type checking/autocompletion, will not be imported at runtime.
if TYPE_CHECKING:
@@ -49,7 +50,6 @@ class FixDupeFilesModal(QWidget):
self.dupe_count = QLabel()
self.dupe_count.setObjectName("dupeCountLabel")
- self.dupe_count.setStyleSheet("font-weight:bold;font-size:14px;")
self.dupe_count.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.file_label = QLabel(Translations["file.duplicates.dupeguru.no_file"])
@@ -119,13 +119,19 @@ class FixDupeFilesModal(QWidget):
def set_dupe_count(self, count: int):
if count < 0:
self.mirror_button.setDisabled(True)
- self.dupe_count.setText(Translations["file.duplicates.matches_uninitialized"])
+ self.dupe_count.setText(
+ header(Translations["file.duplicates.matches_uninitialized"], 4)
+ )
elif count == 0:
self.mirror_button.setDisabled(True)
- self.dupe_count.setText(Translations.format("file.duplicates.matches", count=count))
+ self.dupe_count.setText(
+ header(Translations.format("file.duplicates.matches", count=count), 4)
+ )
else:
self.mirror_button.setDisabled(False)
- self.dupe_count.setText(Translations.format("file.duplicates.matches", count=count))
+ self.dupe_count.setText(
+ header(Translations.format("file.duplicates.matches", count=count), 4)
+ )
@override
def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa N802
diff --git a/src/tagstudio/qt/mixed/fix_unlinked.py b/src/tagstudio/qt/mixed/fix_unlinked.py
index 59474c1f..f96bf2d0 100644
--- a/src/tagstudio/qt/mixed/fix_unlinked.py
+++ b/src/tagstudio/qt/mixed/fix_unlinked.py
@@ -15,6 +15,7 @@ from tagstudio.qt.mixed.progress_bar import ProgressWidget
from tagstudio.qt.mixed.relink_entries_modal import RelinkUnlinkedEntries
from tagstudio.qt.mixed.remove_unlinked_modal import RemoveUnlinkedEntriesModal
from tagstudio.qt.translations import Translations
+from tagstudio.qt.views.stylesheets.stylesheets import header
# Only import for type checking/autocompletion, will not be imported at runtime.
if TYPE_CHECKING:
@@ -148,7 +149,7 @@ class FixUnlinkedEntriesModal(QWidget):
count_text: str = Translations.format(
"entries.unlinked.unlinked_count", count=count if count >= 0 else "—"
)
- self.unlinked_count_label.setText(f"{count_text}
")
+ self.unlinked_count_label.setText(header(count_text, 3))
@override
def showEvent(self, event: QtGui.QShowEvent) -> None:
diff --git a/src/tagstudio/qt/mixed/folders_to_tags.py b/src/tagstudio/qt/mixed/folders_to_tags.py
index 45cdbc0a..0f9742e2 100644
--- a/src/tagstudio/qt/mixed/folders_to_tags.py
+++ b/src/tagstudio/qt/mixed/folders_to_tags.py
@@ -29,6 +29,7 @@ from tagstudio.core.utils.types import unwrap
from tagstudio.qt.models.palette import ColorType, get_tag_color
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.layouts.flow_layout import FlowLayout
+from tagstudio.qt.views.stylesheets.stylesheets import header
if TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
@@ -56,7 +57,7 @@ def add_folders_to_tree(library: Library, tree: BranchData, items: tuple[str, ..
return branch
-@deprecated("Will be replaced with upcoming 'Macros' feature before v9.6")
+@deprecated("Will be replaced with upcoming 'Macros' feature.")
def folders_to_tags(library: Library):
logger.info("Converting folders to Tags")
tree = BranchData()
@@ -177,10 +178,9 @@ class FoldersToTagsModal(QWidget):
self.root_layout = QVBoxLayout(self)
self.root_layout.setContentsMargins(6, 6, 6, 6)
- self.title_widget = QLabel(Translations["folders_to_tags.title"])
+ self.title_widget = QLabel(header(Translations["folders_to_tags.title"], 3))
self.title_widget.setObjectName("title")
self.title_widget.setWordWrap(True)
- self.title_widget.setStyleSheet("font-weight:bold;font-size:14px;padding-top: 6px")
self.title_widget.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.desc_widget = QLabel()
diff --git a/src/tagstudio/qt/mixed/migration_modal.py b/src/tagstudio/qt/mixed/migration_modal.py
index 0859b0ec..7f21f825 100644
--- a/src/tagstudio/qt/mixed/migration_modal.py
+++ b/src/tagstudio/qt/mixed/migration_modal.py
@@ -48,6 +48,7 @@ from tagstudio.qt.utils.custom_runnable import CustomRunnable
from tagstudio.qt.utils.function_iterator import FunctionIterator
from tagstudio.qt.views.paged_body_wrapper import PagedBodyWrapper
from tagstudio.qt.views.qbutton_wrapper import QPushButtonWrapper
+from tagstudio.qt.views.stylesheets.stylesheets import header
logger = structlog.get_logger(__name__)
@@ -364,7 +365,7 @@ class JsonMigrationModal(QObject):
iterator = FunctionIterator(self.migration_iterator)
iterator.value.connect(
lambda x: (
- pb.setLabelText(f"{x}
"),
+ pb.setLabelText(header(x, 4)),
self.update_sql_value_ui(show_msg_box=False)
if x == Translations["json_migration.checking_for_parity"]
else (),
@@ -386,7 +387,7 @@ class JsonMigrationModal(QObject):
QThreadPool.globalInstance().start(r)
except Exception as e:
logger.error("[MigrationModal][Iterator] Error:", error=e)
- pb.setLabelText(f"{type(e).__name__}
")
+ pb.setLabelText(header(type(e).__name__, 4))
pb.setMinimum(1)
pb.setValue(1)
@@ -410,7 +411,7 @@ class JsonMigrationModal(QObject):
)
self.sql_lib.migrate_json_to_sqlite(self.json_lib)
yield Translations["json_migration.checking_for_parity"]
- check_set = set()
+ check_set: set[bool] = set()
check_set.add(self.check_field_parity())
check_set.add(self.check_path_parity())
check_set.add(self.check_name_parity())
@@ -522,7 +523,7 @@ class JsonMigrationModal(QObject):
def assert_ignore_parity(self) -> None:
compiled_pats = fnmatch.compile(
ignore_to_glob(
- Ignore._load_ignore_file(
+ Ignore._load_ignore_file( # pyright: ignore[reportPrivateUsage]
unwrap(self.json_lib.library_dir) / TS_FOLDER_NAME / IGNORE_NAME
)
),
diff --git a/src/tagstudio/qt/mixed/tag_color_label.py b/src/tagstudio/qt/mixed/tag_color_label.py
index 8052f59d..90d797b7 100644
--- a/src/tagstudio/qt/mixed/tag_color_label.py
+++ b/src/tagstudio/qt/mixed/tag_color_label.py
@@ -11,12 +11,14 @@ from PySide6.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QWidget
from tagstudio.core.library.alchemy.models import TagColorGroup
from tagstudio.qt.helpers.escape_text import escape_text
-from tagstudio.qt.mixed.tag_widget import (
- get_border_color,
- get_highlight_color,
- get_text_color,
-)
from tagstudio.qt.translations import Translations
+from tagstudio.qt.views.stylesheets.stylesheets import (
+ get_tag_border_color,
+ get_tag_highlight_color,
+ get_tag_text_color,
+ tag_remove_button_style,
+ tag_style,
+)
logger = structlog.get_logger(__name__)
@@ -101,76 +103,25 @@ class TagColorLabel(QWidget):
primary_color = self._get_primary_color(color)
border_color = (
- get_border_color(primary_color)
+ get_tag_border_color(primary_color)
if not (color and color.secondary and color.color_border)
else (QColor(color.secondary))
)
- highlight_color = get_highlight_color(
+ highlight_color = get_tag_highlight_color(
primary_color if not (color and color.secondary) else QColor(color.secondary)
)
text_color: QColor
if color and color.secondary:
text_color = QColor(color.secondary)
else:
- text_color = get_text_color(primary_color, highlight_color)
+ text_color = get_tag_text_color(primary_color, highlight_color)
self.bg_button.setStyleSheet(
- f"QPushButton{{"
- f"background: rgba{primary_color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"font-weight: 600;"
- f"border-color: rgba{border_color.toTuple()};"
- f"border-radius: 6px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"padding-right: 4px;"
- f"padding-left: 4px;"
- f"font-size: 13px"
- f"}}"
- f"QPushButton::hover{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QPushButton::pressed{{"
- f"background: rgba{highlight_color.toTuple()};"
- f"color: rgba{primary_color.toTuple()};"
- f"border-color: rgba{primary_color.toTuple()};"
- f"}}"
- f"QPushButton::focus{{"
- f"padding-right: 0px;"
- f"padding-left: 0px;"
- f"outline-style: solid;"
- f"outline-width: 1px;"
- f"outline-radius: 4px;"
- f"outline-color: rgba{text_color.toTuple()};"
- f"}}"
+ tag_style(primary_color, text_color, border_color, highlight_color)
)
self.remove_button.setStyleSheet(
- f"QPushButton{{"
- f"color: rgba{primary_color.toTuple()};"
- f"background: rgba{text_color.toTuple()};"
- f"font-weight: 800;"
- f"border-radius: 5px;"
- f"border-width: 4;"
- f"border-color: rgba(0,0,0,0);"
- f"padding-bottom: 4px;"
- f"font-size: 14px"
- f"}}"
- f"QPushButton::hover{{"
- f"background: rgba{primary_color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"border-width: 2;"
- f"border-radius: 6px;"
- f"}}"
- f"QPushButton::pressed{{"
- f"background: rgba{border_color.toTuple()};"
- f"color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QPushButton::focus{{"
- f"background: rgba{border_color.toTuple()};"
- f"outline:none;"
- f"}}"
+ tag_remove_button_style(primary_color, text_color, border_color, highlight_color)
)
self.bg_button.setText(escape_text(color.name))
@@ -183,13 +134,15 @@ class TagColorLabel(QWidget):
def set_has_remove(self, has_remove: bool):
self.has_remove = has_remove
- def enterEvent(self, event: QEnterEvent) -> None: # noqa: N802
+ @typing.override
+ def enterEvent(self, event: QEnterEvent) -> None:
if self.has_remove:
self.remove_button.setHidden(False)
self.update()
return super().enterEvent(event)
- def leaveEvent(self, event: QEvent) -> None: # noqa: N802
+ @typing.override
+ def leaveEvent(self, event: QEvent) -> None:
if self.has_remove:
self.remove_button.setHidden(True)
self.update()
diff --git a/src/tagstudio/qt/mixed/tag_color_manager.py b/src/tagstudio/qt/mixed/tag_color_manager.py
index ad1b72fe..99f5b47c 100644
--- a/src/tagstudio/qt/mixed/tag_color_manager.py
+++ b/src/tagstudio/qt/mixed/tag_color_manager.py
@@ -3,7 +3,7 @@
from collections.abc import Callable
-from typing import TYPE_CHECKING, override
+from typing import TYPE_CHECKING, Any, override
import structlog
from PySide6 import QtCore, QtGui
@@ -28,6 +28,7 @@ from tagstudio.qt.mixed.color_box import ColorBoxWidget
from tagstudio.qt.mixed.field_widget import FieldContainer
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.panel_modal import PanelModal
+from tagstudio.qt.views.stylesheets.stylesheets import header
logger = structlog.get_logger(__name__)
@@ -62,7 +63,7 @@ class TagColorManager(QWidget):
self.title_label = QLabel()
self.title_label.setObjectName("titleLabel")
self.title_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
- self.title_label.setText(f"{Translations['color_manager.title']}
")
+ self.title_label.setText(header(Translations["color_manager.title"], 3))
self.scroll_layout = QVBoxLayout()
self.scroll_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
@@ -189,7 +190,7 @@ class TagColorManager(QWidget):
self.create_namespace_modal.show()
- def delete_namespace_dialog(self, prompt: str, callback: Callable) -> None:
+ def delete_namespace_dialog(self, prompt: str, callback: Callable[..., Any]) -> None: # pyright: ignore[reportExplicitAny]
message_box = QMessageBox()
message_box.setText(prompt)
message_box.setWindowTitle(Translations["color.namespace.delete.title"])
@@ -207,13 +208,13 @@ class TagColorManager(QWidget):
callback()
@override
- def showEvent(self, event: QtGui.QShowEvent) -> None: # noqa N802
+ def showEvent(self, event: QtGui.QShowEvent) -> None:
if not self.is_initialized:
self.setup_color_groups()
return super().showEvent(event)
@override
- def keyPressEvent(self, event: QtGui.QKeyEvent) -> None: # noqa N802
+ def keyPressEvent(self, event: QtGui.QKeyEvent) -> None:
if event.key() == QtCore.Qt.Key.Key_Escape: # noqa SIM114
self.done_button.click()
elif event.key() == Qt.Key.Key_Return or event.key() == Qt.Key.Key_Enter:
diff --git a/src/tagstudio/qt/mixed/tag_color_preview.py b/src/tagstudio/qt/mixed/tag_color_preview.py
index c73571d4..965a823a 100644
--- a/src/tagstudio/qt/mixed/tag_color_preview.py
+++ b/src/tagstudio/qt/mixed/tag_color_preview.py
@@ -11,9 +11,14 @@ from PySide6.QtWidgets import QPushButton, QVBoxLayout, QWidget
from tagstudio.core.library.alchemy.enums import TagColorEnum
from tagstudio.core.library.alchemy.models import TagColorGroup
-from tagstudio.qt.mixed.tag_widget import get_border_color, get_highlight_color, get_text_color
from tagstudio.qt.models.palette import ColorType, get_tag_color
from tagstudio.qt.translations import Translations
+from tagstudio.qt.views.stylesheets.stylesheets import (
+ get_tag_border_color,
+ get_tag_highlight_color,
+ get_tag_text_color,
+ tag_style,
+)
if typing.TYPE_CHECKING:
from tagstudio.core.library.alchemy.library import Library
@@ -66,11 +71,11 @@ class TagColorPreview(QWidget):
primary_color = self._get_primary_color(color_group)
border_color = (
- get_border_color(primary_color)
+ get_tag_border_color(primary_color)
if not (color_group and color_group.secondary and color_group.color_border)
else (QColor(color_group.secondary))
)
- highlight_color = get_highlight_color(
+ highlight_color = get_tag_highlight_color(
primary_color
if not (color_group and color_group.secondary)
else QColor(color_group.secondary)
@@ -79,32 +84,10 @@ class TagColorPreview(QWidget):
if color_group and color_group.secondary:
text_color = QColor(color_group.secondary)
else:
- text_color = get_text_color(primary_color, highlight_color)
+ text_color = get_tag_text_color(primary_color, highlight_color)
self.button.setStyleSheet(
- f"QPushButton{{"
- f"background: rgba{primary_color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"font-weight: 600;"
- f"border-color: rgba{border_color.toTuple()};"
- f"border-radius: 6px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"padding-right: 8px;"
- f"padding-left: 8px;"
- f"font-size: 14px"
- f"}}"
- f"QPushButton::hover{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QPushButton::focus{{"
- f"padding-right: 0px;"
- f"padding-left: 0px;"
- f"outline-style: solid;"
- f"outline-width: 1px;"
- f"outline-radius: 4px;"
- f"outline-color: rgba{text_color.toTuple()};"
- f"}}"
+ tag_style(primary_color, text_color, border_color, highlight_color)
)
# Add back the padding if the hint is generated while the button has focus (no padding)
self.button.setMinimumWidth(
diff --git a/src/tagstudio/qt/mixed/tag_color_selection.py b/src/tagstudio/qt/mixed/tag_color_selection.py
index a58d3c06..35bc2c2e 100644
--- a/src/tagstudio/qt/mixed/tag_color_selection.py
+++ b/src/tagstudio/qt/mixed/tag_color_selection.py
@@ -20,11 +20,16 @@ from PySide6.QtWidgets import (
from tagstudio.core.library.alchemy.enums import TagColorEnum
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import TagColorGroup
-from tagstudio.qt.mixed.tag_widget import get_border_color, get_highlight_color, get_text_color
+from tagstudio.qt.mixed.tag_widget import (
+ get_tag_border_color,
+ get_tag_highlight_color,
+ get_tag_text_color,
+)
from tagstudio.qt.models.palette import ColorType, get_tag_color
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.layouts.flow_layout import FlowLayout
from tagstudio.qt.views.panel_modal import PanelWidget
+from tagstudio.qt.views.stylesheets.stylesheets import color_swatch_style, header
logger = structlog.get_logger(__name__)
@@ -67,9 +72,7 @@ class TagColorSelection(PanelWidget):
self.scroll_layout.addSpacerItem(QSpacerItem(1, 6))
for group, colors in tag_color_groups.items():
display_name: str = self.lib.get_namespace_name(group)
- self.scroll_layout.addWidget(
- QLabel(f"{display_name if display_name else group}
")
- )
+ self.scroll_layout.addWidget(QLabel(header(display_name if display_name else group, 4)))
color_box_widget = QWidget()
color_group_layout = FlowLayout()
color_group_layout.setSpacing(4)
@@ -79,54 +82,31 @@ class TagColorSelection(PanelWidget):
for color in colors:
primary_color = self._get_primary_color(color)
border_color = (
- get_border_color(primary_color)
+ get_tag_border_color(primary_color)
if not (color and color.secondary and color.color_border)
else (QColor(color.secondary))
)
- highlight_color = get_highlight_color(
+ highlight_color = get_tag_highlight_color(
primary_color if not (color and color.secondary) else QColor(color.secondary)
)
text_color: QColor
if color and color.secondary:
text_color = QColor(color.secondary)
else:
- text_color = get_text_color(primary_color, highlight_color)
+ text_color = get_tag_text_color(primary_color, highlight_color)
radio_button = QRadioButton()
radio_button.setObjectName(f"{color.namespace}.{color.slug}")
radio_button.setToolTip(color.name)
radio_button.setFixedSize(24, 24)
- bottom_color: str = (
- f"border-bottom-color: rgba{text_color.toTuple()};" if color.secondary else ""
- )
radio_button.setStyleSheet(
- f"QRadioButton{{"
- f"background: rgba{primary_color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"border-color: rgba{border_color.toTuple()};"
- f"{bottom_color}"
- f"border-radius: 3px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"}}"
- f"QRadioButton::indicator{{"
- f"width: 12px;"
- f"height: 12px;"
- f"border-radius: 1px;"
- f"margin: 4px;"
- f"}}"
- f"QRadioButton::indicator:checked{{"
- f"background: rgba{text_color.toTuple()};"
- f"}}"
- f"QRadioButton::hover{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QRadioButton::focus{{"
- f"outline-style: solid;"
- f"outline-width: 2px;"
- f"outline-radius: 3px;"
- f"outline-color: rgba{highlight_color.toTuple()};"
- f"}}"
+ color_swatch_style(
+ primary_color,
+ text_color,
+ border_color,
+ highlight_color,
+ text_color if color.secondary else None,
+ )
)
radio_button.clicked.connect(lambda checked=False, x=color: self.select_color(x))
color_group_layout.addWidget(radio_button)
@@ -136,7 +116,7 @@ class TagColorSelection(PanelWidget):
def add_no_color_widget(self):
no_color_str: str = Translations["color.title.no_color"]
- self.scroll_layout.addWidget(QLabel(f"{no_color_str}
"))
+ self.scroll_layout.addWidget(QLabel(header(no_color_str, 4)))
color_box_widget = QWidget()
color_group_layout = FlowLayout()
color_group_layout.setSpacing(4)
@@ -145,45 +125,20 @@ class TagColorSelection(PanelWidget):
color_box_widget.setLayout(color_group_layout)
color = None
primary_color = self._get_primary_color(color)
- border_color = get_border_color(primary_color)
- highlight_color = get_highlight_color(primary_color)
+ border_color = get_tag_border_color(primary_color)
+ highlight_color = get_tag_highlight_color(primary_color)
text_color: QColor
if color and color.secondary and color.color_border:
text_color = QColor(color.secondary)
else:
- text_color = get_text_color(primary_color, highlight_color)
+ text_color = get_tag_text_color(primary_color, highlight_color)
radio_button = QRadioButton()
- radio_button.setObjectName("None") # NOTE: Internal use, no translation needed.
+ radio_button.setObjectName("None")
radio_button.setToolTip(no_color_str)
radio_button.setFixedSize(24, 24)
radio_button.setStyleSheet(
- f"QRadioButton{{"
- f"background: rgba{primary_color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"border-color: rgba{border_color.toTuple()};"
- f"border-radius: 3px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"}}"
- f"QRadioButton::indicator{{"
- f"width: 12px;"
- f"height: 12px;"
- f"border-radius: 1px;"
- f"margin: 4px;"
- f"}}"
- f"QRadioButton::indicator:checked{{"
- f"background: rgba{text_color.toTuple()};"
- f"}}"
- f"QRadioButton::hover{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QRadioButton::focus{{"
- f"outline-style: solid;"
- f"outline-width: 2px;"
- f"outline-radius: 3px;"
- f"outline-color: rgba{highlight_color.toTuple()};"
- f"}}"
+ color_swatch_style(primary_color, text_color, border_color, highlight_color)
)
radio_button.clicked.connect(lambda checked=False, x=color: self.select_color(x))
color_group_layout.addWidget(radio_button)
diff --git a/src/tagstudio/qt/mixed/tag_widget.py b/src/tagstudio/qt/mixed/tag_widget.py
index 03a0db0f..c17cf196 100644
--- a/src/tagstudio/qt/mixed/tag_widget.py
+++ b/src/tagstudio/qt/mixed/tag_widget.py
@@ -15,6 +15,14 @@ from tagstudio.core.library.alchemy.models import Tag
from tagstudio.qt.helpers.escape_text import escape_text
from tagstudio.qt.models.palette import ColorType, get_tag_color
from tagstudio.qt.translations import Translations
+from tagstudio.qt.views.stylesheets.stylesheets import (
+ get_tag_border_color,
+ get_tag_highlight_color,
+ get_tag_primary_color,
+ get_tag_text_color,
+ tag_remove_button_style,
+ tag_style,
+)
logger = structlog.get_logger(__name__)
@@ -188,13 +196,13 @@ class TagWidget(QWidget):
if not tag:
return
- primary_color = get_primary_color(tag)
+ primary_color = get_tag_primary_color(tag)
border_color = (
- get_border_color(primary_color)
+ get_tag_border_color(primary_color)
if not (tag.color and tag.color.secondary and tag.color.color_border)
else (QColor(tag.color.secondary))
)
- highlight_color = get_highlight_color(
+ highlight_color = get_tag_highlight_color(
primary_color
if not (tag.color and tag.color.secondary)
else QColor(tag.color.secondary)
@@ -203,65 +211,14 @@ class TagWidget(QWidget):
if tag.color and tag.color.secondary:
text_color = QColor(tag.color.secondary)
else:
- text_color = get_text_color(primary_color, highlight_color)
+ text_color = get_tag_text_color(primary_color, highlight_color)
self.bg_button.setStyleSheet(
- f"QPushButton{{"
- f"background: rgba{primary_color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"font-weight: 600;"
- f"border-color: rgba{border_color.toTuple()};"
- f"border-radius: 6px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"padding-right: 4px;"
- f"padding-left: 4px;"
- f"font-size: 13px"
- f"}}"
- f"QPushButton::hover{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QPushButton::pressed{{"
- f"background: rgba{highlight_color.toTuple()};"
- f"color: rgba{primary_color.toTuple()};"
- f"border-color: rgba{primary_color.toTuple()};"
- f"}}"
- f"QPushButton::focus{{"
- f"padding-right: 0px;"
- f"padding-left: 0px;"
- f"outline-style: solid;"
- f"outline-width: 1px;"
- f"outline-radius: 4px;"
- f"outline-color: rgba{text_color.toTuple()};"
- f"}}"
+ tag_style(primary_color, text_color, border_color, highlight_color)
)
self._delete_button.setStyleSheet(
- f"QPushButton{{"
- f"color: rgba{primary_color.toTuple()};"
- f"background: rgba{text_color.toTuple()};"
- f"font-weight: 800;"
- f"border-radius: 5px;"
- f"border-width: 4;"
- f"border-color: rgba(0,0,0,0);"
- f"padding-bottom: 4px;"
- f"font-size: 14px"
- f"}}"
- f"QPushButton::hover{{"
- f"background: rgba{primary_color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"border-width: 2;"
- f"border-radius: 6px;"
- f"}}"
- f"QPushButton::pressed{{"
- f"background: rgba{border_color.toTuple()};"
- f"color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QPushButton::focus{{"
- f"background: rgba{border_color.toTuple()};"
- f"outline:none;"
- f"}}"
+ tag_remove_button_style(primary_color, text_color, border_color, highlight_color)
)
if self.lib:
@@ -285,42 +242,3 @@ class TagWidget(QWidget):
self._delete_button.setHidden(True)
self.update()
return super().leaveEvent(event)
-
-
-def get_primary_color(tag: Tag) -> QColor:
- primary_color = QColor(
- get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)
- if not tag.color
- else tag.color.primary
- )
-
- return primary_color
-
-
-def get_border_color(primary_color: QColor) -> QColor:
- border_color: QColor = QColor(primary_color)
- border_color.setRed(min(border_color.red() + 20, 255))
- border_color.setGreen(min(border_color.green() + 20, 255))
- border_color.setBlue(min(border_color.blue() + 20, 255))
-
- return border_color
-
-
-def get_highlight_color(primary_color: QColor) -> QColor:
- highlight_color: QColor = QColor(primary_color)
- highlight_color = highlight_color.toHsl()
- highlight_color.setHsl(highlight_color.hue(), min(highlight_color.saturation(), 200), 225, 255)
- highlight_color = highlight_color.toRgb()
-
- return highlight_color
-
-
-def get_text_color(primary_color: QColor, highlight_color: QColor) -> QColor:
- # logger.info("[TagWidget] Evaluating tag text color", lightness=primary_color.lightness())
- if primary_color.lightness() > 120:
- text_color = QColor(primary_color)
- text_color = text_color.toHsl()
- text_color.setHsl(text_color.hue(), text_color.saturation(), 50, 255)
- return text_color.toRgb()
- else:
- return highlight_color
diff --git a/src/tagstudio/qt/ts_qt.py b/src/tagstudio/qt/ts_qt.py
index cce250da..ad9aaadc 100644
--- a/src/tagstudio/qt/ts_qt.py
+++ b/src/tagstudio/qt/ts_qt.py
@@ -99,6 +99,7 @@ from tagstudio.qt.views.field_template_search_panel_view import FieldTemplateSea
from tagstudio.qt.views.main_window import MainWindow
from tagstudio.qt.views.panel_modal import PanelModal
from tagstudio.qt.views.splash import SplashScreen
+from tagstudio.qt.views.stylesheets.stylesheets import header
from tagstudio.qt.views.tag_search_panel_view import TagSearchPanelView
BADGE_TAGS = {
@@ -1014,9 +1015,8 @@ class QtDriver(DriverMixin, QObject):
perm_warning_msg = Translations.format(
"trash.dialog.permanent_delete_warning", trash_term=trash_term()
)
- perm_warning: str = (
- f""
- f"{perm_warning_msg}
"
+ perm_warning: str = header(
+ perm_warning_msg, 4, get_ui_color(ColorType.PRIMARY, UiColor.RED)
)
msg = QMessageBox()
@@ -1033,8 +1033,8 @@ class QtDriver(DriverMixin, QObject):
"trash.dialog.move.confirmation.singular", trash_term=trash_term()
)
msg.setText(
- f"{msg_text}
"
- f"{Translations['trash.dialog.disambiguation_warning.singular']}
"
+ f"{header(msg_text, 3)}"
+ f"{header(Translations['trash.dialog.disambiguation_warning.singular'], 4)}"
f"{filename if filename else ''}"
f"{perm_warning}
"
)
@@ -1045,8 +1045,8 @@ class QtDriver(DriverMixin, QObject):
trash_term=trash_term(),
)
msg.setText(
- f"{msg_text}
"
- f"{Translations['trash.dialog.disambiguation_warning.plural']}
"
+ f"{header(msg_text, 3)}"
+ f"{header(Translations['trash.dialog.disambiguation_warning.plural'], 4)}"
f"{perm_warning}
"
)
@@ -1069,9 +1069,7 @@ class QtDriver(DriverMixin, QObject):
pw.update_label(Translations["library.refresh.scanning_preparing"])
pw.show()
- iterator = FunctionIterator(
- lambda lib=unwrap(self.lib.library_dir): tracker.refresh_dir(lib) # noqa: B008
- )
+ iterator = FunctionIterator(lambda lib=self.lib.library_dir: tracker.refresh_dir(lib))
iterator.value.connect(
lambda x: (
pw.update_progress(x + 1),
diff --git a/src/tagstudio/qt/views/edit_text_view.py b/src/tagstudio/qt/views/edit_text_view.py
index 42727240..1ec7ed33 100644
--- a/src/tagstudio/qt/views/edit_text_view.py
+++ b/src/tagstudio/qt/views/edit_text_view.py
@@ -16,7 +16,7 @@ from PySide6.QtWidgets import (
from tagstudio.qt.controllers.clickable_label import ClickableLabel
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.panel_modal import PanelWidget
-from tagstudio.qt.views.stylesheets.stylesheets import checkbox_style
+from tagstudio.qt.views.stylesheets.stylesheets import checkbox_style, title_line_edit_style
class EditTextView(PanelWidget):
@@ -27,7 +27,7 @@ class EditTextView(PanelWidget):
self.root_layout.setContentsMargins(6, 0, 6, 0)
self.name_field = QLineEdit()
- self.name_field.setStyleSheet("font-weight:bold;font-size:14px;padding-top:6px")
+ self.name_field.setStyleSheet(title_line_edit_style())
self.text_box = QPlainTextEdit()
self.text_line = QLineEdit()
diff --git a/src/tagstudio/qt/views/field_template_widget_view.py b/src/tagstudio/qt/views/field_template_widget_view.py
index 46c2285b..78efdb93 100644
--- a/src/tagstudio/qt/views/field_template_widget_view.py
+++ b/src/tagstudio/qt/views/field_template_widget_view.py
@@ -5,41 +5,19 @@ from PySide6.QtCore import Signal
from PySide6.QtGui import QColor
from PySide6.QtWidgets import QHBoxLayout, QPushButton, QVBoxLayout, QWidget
-from tagstudio.core.enums import Theme
from tagstudio.core.library.alchemy.enums import TagColorEnum
-from tagstudio.qt.mixed.tag_widget import get_border_color, get_highlight_color, get_text_color
-from tagstudio.qt.models.palette import ColorType, UiColor, get_tag_color, get_ui_color
+from tagstudio.qt.models.palette import ColorType, get_tag_color
+from tagstudio.qt.views.stylesheets.stylesheets import (
+ get_tag_border_color,
+ get_tag_highlight_color,
+ get_tag_text_color,
+ list_button_style,
+)
primary_color: QColor = QColor(get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT))
-border_color: QColor = get_border_color(primary_color)
-highlight_color: QColor = get_highlight_color(primary_color)
-text_color: QColor = get_text_color(primary_color, highlight_color)
-
-FIELD_TEMPLATE_BUTTON_STYLESHEET = f"""
- QPushButton{{
- background-color: {Theme.COLOR_BG.value};
- font-weight: 600;
- border-radius: 6px;
- padding-right: 4px;
- padding-left: 4px;
- font-size: 13px;
- text-align: center;
- }}
-
- QPushButton::hover{{
- background-color: {Theme.COLOR_HOVER.value};
- border-color: {get_ui_color(ColorType.BORDER, UiColor.THEME_DARK)};
- border-style: solid;
- border-width: 2px;
- }}
-
- QPushButton::pressed{{
- background-color: {Theme.COLOR_PRESSED.value};
- border-color: {get_ui_color(ColorType.LIGHT_ACCENT, UiColor.THEME_DARK)};
- border-style: solid;
- border-width: 2px;
- }}
-"""
+border_color: QColor = get_tag_border_color(primary_color)
+highlight_color: QColor = get_tag_highlight_color(primary_color)
+text_color: QColor = get_tag_text_color(primary_color, highlight_color)
class FieldTemplateWidgetView(QWidget):
@@ -63,7 +41,7 @@ class FieldTemplateWidgetView(QWidget):
self._bg_button.setMinimumSize(44, 22)
self._bg_button.setMinimumHeight(22)
self._bg_button.setMaximumHeight(22)
- self._bg_button.setStyleSheet(FIELD_TEMPLATE_BUTTON_STYLESHEET)
+ self._bg_button.setStyleSheet(list_button_style())
self.__inner_layout = QHBoxLayout()
self.__inner_layout.setObjectName("inner_layout")
diff --git a/src/tagstudio/qt/views/library_info_window_view.py b/src/tagstudio/qt/views/library_info_window_view.py
index bfd06c0a..88f47027 100644
--- a/src/tagstudio/qt/views/library_info_window_view.py
+++ b/src/tagstudio/qt/views/library_info_window_view.py
@@ -24,6 +24,7 @@ from PySide6.QtWidgets import (
from tagstudio.qt.helpers.color_overlay import theme_fg_overlay
from tagstudio.qt.platform_strings import open_file_str
from tagstudio.qt.translations import Translations
+from tagstudio.qt.views.stylesheets.stylesheets import header
# Only import for type checking/autocompletion, will not be imported at runtime.
if TYPE_CHECKING:
@@ -65,7 +66,7 @@ class LibraryInfoWindowView(QWidget):
self.stats_layout.setContentsMargins(0, 0, 0, 0)
self.stats_layout.setSpacing(12)
- self.stats_label = QLabel(f"{Translations['library_info.stats']}
")
+ self.stats_label = QLabel(header(Translations["library_info.stats"], 3))
self.stats_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.stats_grid: QWidget = QWidget()
@@ -223,7 +224,7 @@ class LibraryInfoWindowView(QWidget):
self.cleanup_layout.setContentsMargins(0, 0, 0, 0)
self.cleanup_layout.setSpacing(12)
- self.cleanup_label = QLabel(f"{Translations['library_info.cleanup']}
")
+ self.cleanup_label = QLabel(header(Translations["library_info.cleanup"], 3))
self.cleanup_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.cleanup_grid: QWidget = QWidget()
diff --git a/src/tagstudio/qt/views/preview_panel_view.py b/src/tagstudio/qt/views/preview_panel_view.py
index 19144a00..266e06fe 100644
--- a/src/tagstudio/qt/views/preview_panel_view.py
+++ b/src/tagstudio/qt/views/preview_panel_view.py
@@ -16,45 +16,20 @@ from PySide6.QtWidgets import (
QWidget,
)
-from tagstudio.core.enums import Theme
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.alchemy.models import Entry
from tagstudio.core.utils.types import unwrap
from tagstudio.qt.controllers.preview_thumb_controller import PreviewThumb
from tagstudio.qt.mixed.field_containers import FieldContainers
from tagstudio.qt.mixed.file_attributes import FileAttributeData, FileAttributes
-from tagstudio.qt.models.palette import ColorType, UiColor, get_ui_color
from tagstudio.qt.translations import Translations
+from tagstudio.qt.views.stylesheets.stylesheets import button_style
if typing.TYPE_CHECKING:
from tagstudio.qt.ts_qt import QtDriver
logger = structlog.get_logger(__name__)
-BUTTON_STYLE: str = f"""
- QPushButton{{
- background-color: {Theme.COLOR_BG.value};
- border-radius: 6px;
- font-weight: 500;
- text-align: center;
- }}
- QPushButton::hover{{
- background-color: {Theme.COLOR_HOVER.value};
- border-color: {get_ui_color(ColorType.BORDER, UiColor.THEME_DARK)};
- border-style: solid;
- border-width: 2px;
- }}
- QPushButton::pressed{{
- background-color: {Theme.COLOR_PRESSED.value};
- border-color: {get_ui_color(ColorType.LIGHT_ACCENT, UiColor.THEME_DARK)};
- border-style: solid;
- border-width: 2px;
- }}
- QPushButton::disabled{{
- background-color: {Theme.COLOR_DISABLED_BG.value};
- }}
-"""
-
class PreviewPanelView(QWidget):
lib: Library
@@ -94,13 +69,13 @@ class PreviewPanelView(QWidget):
self.__add_tag_button.setEnabled(False)
self.__add_tag_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.__add_tag_button.setMinimumHeight(28)
- self.__add_tag_button.setStyleSheet(BUTTON_STYLE)
+ self.__add_tag_button.setStyleSheet(button_style())
self.__add_field_button = QPushButton(Translations["field.add"])
self.__add_field_button.setEnabled(False)
self.__add_field_button.setCursor(Qt.CursorShape.PointingHandCursor)
self.__add_field_button.setMinimumHeight(28)
- self.__add_field_button.setStyleSheet(BUTTON_STYLE)
+ self.__add_field_button.setStyleSheet(button_style())
add_buttons_layout.addWidget(self.__add_tag_button)
add_buttons_layout.addWidget(self.__add_field_button)
diff --git a/src/tagstudio/qt/views/search_panel_view.py b/src/tagstudio/qt/views/search_panel_view.py
index f6c54cbe..175828af 100644
--- a/src/tagstudio/qt/views/search_panel_view.py
+++ b/src/tagstudio/qt/views/search_panel_view.py
@@ -16,46 +16,14 @@ from PySide6.QtWidgets import (
QWidget,
)
-from tagstudio.core.library.alchemy.enums import TagColorEnum
from tagstudio.core.library.alchemy.library import Library
-from tagstudio.qt.models.palette import ColorType, get_tag_color
from tagstudio.qt.translations import Translations
from tagstudio.qt.views.panel_modal import PanelWidget
+from tagstudio.qt.views.stylesheets.stylesheets import list_button_style
if TYPE_CHECKING:
from tagstudio.qt.controllers.search_panel_controller import SearchPanel
-CREATE_BUTTON_STYLESHEET: str = f"""
- QPushButton{{
- background: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};
- color: {get_tag_color(ColorType.TEXT, TagColorEnum.DEFAULT)};
- font-weight: 600;
- border-color:{get_tag_color(ColorType.BORDER, TagColorEnum.DEFAULT)};
- border-radius: 6px;
- border-style: dashed;
- border-width: 2px;
- padding-right: 4px;
- padding-bottom: 1px;
- padding-left: 4px;
- font-size: 13px
- }}
-
- QPushButton::hover{{
- border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};
- }}
-
- QPushButton::pressed{{
- background: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};
- color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};
- border-color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};
- }}
-
- QPushButton::focus{{
- border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};
- outline: none;
- }}
-"""
-
class SearchPanelView(PanelWidget):
def __init__(self, is_chooser: bool) -> None:
@@ -120,7 +88,7 @@ class SearchPanelView(PanelWidget):
self.create_and_add_button = QPushButton()
self.create_and_add_button.setFlat(True)
self.create_and_add_button.setMinimumSize(22, 22)
- self.create_and_add_button.setStyleSheet(CREATE_BUTTON_STYLESHEET)
+ self.create_and_add_button.setStyleSheet(list_button_style(border_style="dashed"))
@property
def scroll_layout(self) -> QVBoxLayout:
diff --git a/src/tagstudio/qt/views/stylesheets/stylesheets.py b/src/tagstudio/qt/views/stylesheets/stylesheets.py
index af331fc7..542ebd3f 100644
--- a/src/tagstudio/qt/views/stylesheets/stylesheets.py
+++ b/src/tagstudio/qt/views/stylesheets/stylesheets.py
@@ -1,41 +1,431 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
-from PySide6.QtGui import QColor
+from PySide6.QtCore import Qt
+from PySide6.QtGui import QColor, QGuiApplication
+
+from tagstudio.core.enums import Theme
from tagstudio.core.library.alchemy.enums import TagColorEnum
-from tagstudio.qt.mixed.tag_widget import get_border_color, get_highlight_color, get_text_color
-from tagstudio.qt.models.palette import ColorType, get_tag_color
+from tagstudio.core.library.alchemy.models import Tag
+from tagstudio.qt.models.palette import ColorType, UiColor, get_tag_color, get_ui_color
+
+# TODO: There's plenty of good opportunities here to consolidate similar styles.
+# Work should be done to more closely use Qt's theming systems rather than override them.
+
+
+def add_button_style() -> str:
+ """Style used for tag-like "Add" buttons [+]."""
+ return f"""
+ QPushButton{{
+ background: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};
+ color: {get_tag_color(ColorType.TEXT, TagColorEnum.DEFAULT)};
+ font-weight: 600;
+ border-color: {get_tag_color(ColorType.BORDER, TagColorEnum.DEFAULT)};
+ border-radius: 6px;
+ border-style: solid;
+ border-width: 2px;
+ padding-right: 4px;
+ padding-bottom: 2px;
+ padding-left: 4px;
+ font-size: 15px
+ }}
+ QPushButton::hover{{
+ border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};
+ }}
+ QPushButton::pressed{{
+ background: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};
+ color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};
+ border-color: {get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)};
+ }}
+ QPushButton::focus{{
+ border-color: {get_tag_color(ColorType.LIGHT_ACCENT, TagColorEnum.DEFAULT)};
+ outline: none;
+ }}
+ """
+
+
+def button_style() -> str:
+ """Style used for common QPushButtons."""
+ return f"""
+ QPushButton{{
+ background-color: {Theme.COLOR_BG.value};
+ border-radius: 6px;
+ font-weight: 500;
+ text-align: center;
+ }}
+ QPushButton::hover{{
+ background-color: {Theme.COLOR_HOVER.value};
+ border-color: {get_ui_color(ColorType.BORDER, UiColor.THEME_DARK)};
+ border-style: solid;
+ border-width: 2px;
+ }}
+ QPushButton::pressed{{
+ background-color: {Theme.COLOR_PRESSED.value};
+ border-color: {get_ui_color(ColorType.LIGHT_ACCENT, UiColor.THEME_DARK)};
+ border-style: solid;
+ border-width: 2px;
+ }}
+ QPushButton::disabled{{
+ background-color: {Theme.COLOR_DISABLED_BG.value};
+ }}
+"""
def checkbox_style() -> str:
+ """Style used for QCheckBoxes."""
primary_color = QColor(get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT))
- border_color = get_border_color(primary_color)
- highlight_color = get_highlight_color(primary_color)
- text_color: QColor = get_text_color(primary_color, highlight_color)
- return (
- f"QCheckBox{{"
- f"background: rgba{primary_color.toTuple()};"
- f"color: rgba{text_color.toTuple()};"
- f"border-color: rgba{border_color.toTuple()};"
- f"border-radius: 6px;"
- f"border-style:solid;"
- f"border-width: 2px;"
- f"}}"
- f"QCheckBox::indicator{{"
- f"width: 10px;"
- f"height: 10px;"
- f"border-radius: 2px;"
- f"margin: 4px;"
- f"}}"
- f"QCheckBox::indicator:checked{{"
- f"background: rgba{text_color.toTuple()};"
- f"}}"
- f"QCheckBox::hover{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"}}"
- f"QCheckBox::focus{{"
- f"border-color: rgba{highlight_color.toTuple()};"
- f"outline:none;"
- f"}}"
+ border_color = get_tag_border_color(primary_color)
+ highlight_color = get_tag_highlight_color(primary_color)
+ text_color: QColor = get_tag_text_color(primary_color, highlight_color)
+ return f"""
+ QCheckBox{{
+ background: rgba{primary_color.toTuple()};
+ color: rgba{text_color.toTuple()};
+ border-color: rgba{border_color.toTuple()};
+ border-radius: 6px;
+ border-style: solid;
+ border-width: 2px;
+ }}
+ QCheckBox::indicator{{
+ width: 10px;
+ height: 10px;
+ border-radius: 2px;
+ margin: 4px;
+ }}
+ QCheckBox::indicator:checked{{
+ background: rgba{text_color.toTuple()};
+ }}
+ QCheckBox::hover{{
+ border-color: rgba{highlight_color.toTuple()};
+ }}
+ QCheckBox::focus{{
+ border-color: rgba{highlight_color.toTuple()};
+ outline: none;
+ }}
+ """
+
+
+def colored_radio_button_style(
+ primary_color: QColor,
+ text_color: QColor,
+ border_color: QColor,
+ highlight_color: QColor,
+) -> str:
+ return f"""
+ QRadioButton{{
+ background: rgba{primary_color.toTuple()};
+ color: rgba{text_color.toTuple()};
+ border-color: rgba{border_color.toTuple()};
+ border-radius: 6px;
+ border-style: solid;
+ border-width: 2px;
+ }}
+ QRadioButton::indicator{{
+ width: 10px;
+ height: 10px;
+ border-radius: 2px;
+ margin: 4px;
+ }}
+ QRadioButton::indicator:checked{{
+ background: rgba{text_color.toTuple()};
+ }}
+ QRadioButton::hover{{
+ border-color: rgba{highlight_color.toTuple()};
+ }}
+ QRadioButton::pressed{{
+ background: rgba{border_color.toTuple()};
+ color: rgba{primary_color.toTuple()};
+ border-color: rgba{primary_color.toTuple()};
+ }}
+ QRadioButton::focus{{
+ border-color: rgba{highlight_color.toTuple()};
+ outline: none;
+ }}
+ """
+
+
+def color_swatch_style(
+ primary_color: QColor,
+ text_color: QColor,
+ border_color: QColor,
+ highlight_color: QColor,
+ bottom_color: QColor | None = None,
+) -> str:
+ """A style used for color swatches (aka special QRadioButtons)."""
+ bottom_color_str: str = (
+ f"border-bottom-color: rgba{bottom_color.toTuple()};" if bottom_color else ""
)
+
+ return f"""
+ QRadioButton{{
+ background: rgba{primary_color.toTuple()};
+ color: rgba{text_color.toTuple()};
+ border-color: rgba{border_color.toTuple()};
+ {bottom_color_str}
+ border-radius: 3px;
+ border-style: solid;
+ border-width: 2px;
+ }}
+ QRadioButton::indicator{{
+ width: 12px;
+ height: 12px;
+ border-radius: 1px;
+ margin: 4px;
+ }}
+ QRadioButton::indicator:checked{{
+ background: rgba{text_color.toTuple()};
+ }}
+ QRadioButton::hover{{
+ border-color: rgba{highlight_color.toTuple()};
+ }}
+ QRadioButton::focus{{
+ outline-style: solid;
+ outline-width: 2px;
+ outline-radius: 3px;
+ outline-color: rgba{highlight_color.toTuple()};
+ }}
+ """
+
+
+def container_style() -> str:
+ """Style used for field containers."""
+ return f"""
+ QWidget#fieldContainer{{
+ border-radius: 4px;
+ }}
+ QWidget#fieldContainer::hover{{
+ background-color: {Theme.COLOR_HOVER.value};
+ }}
+ QWidget#fieldContainer::pressed{{
+ background-color: {Theme.COLOR_PRESSED.value};
+ }}
+ """
+
+
+def form_content_style() -> str:
+ return f"""
+ background-color: {
+ Theme.COLOR_BG.value
+ if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
+ else Theme.COLOR_BG_LIGHT.value
+ };
+ border-radius: 3px;
+ font-weight: 500;
+ padding: 1px;
+ """
+
+
+def line_edit_style() -> str:
+ """Style used for QLineEdits."""
+ return f"""
+ border: 1px solid {get_ui_color(ColorType.PRIMARY, UiColor.RED)};
+ border-radius: 2px
+ """
+
+
+def list_button_style(
+ color: QColor | None = None,
+ border_style: str = "solid",
+) -> str:
+ """Style used for special QPushButtons found in lists."""
+ if color is None:
+ color = QColor(get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT))
+
+ highlight_color = get_tag_highlight_color(color)
+ text_color = get_tag_text_color(color, highlight_color)
+ border_color = get_tag_border_color(color)
+
+ return f"""
+ QPushButton{{
+ background: rgba{color.toTuple()};
+ color: rgba{text_color.toTuple()};
+ font-weight: 600;
+ border-color: rgba{border_color.toTuple()};
+ border-radius: 6px;
+ border-style: {border_style};
+ border-width: 2px;
+ padding-right: 4px;
+ padding-bottom: 1px;
+ padding-left: 4px;
+ font-size: 13px
+ }}
+ QPushButton::hover{{
+ border-color: rgba{highlight_color.toTuple()};
+ }}
+ QPushButton::pressed{{
+ background: rgba{highlight_color.toTuple()};
+ color: rgba{color.toTuple()};
+ border-color: rgba{color.toTuple()};
+ }}
+ QPushButton::focus{{
+ padding-right: 0px;
+ padding-left: 0px;
+ outline-style: solid;
+ outline-width: 1px;
+ outline-radius: 4px;
+ outline-color: rgba{text_color.toTuple()};
+ }}
+ """
+
+
+def properties_style() -> str:
+ """Style used for small labels such as file properties."""
+ label_bg_color = (
+ Theme.COLOR_BG_DARK.value
+ if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
+ else Theme.COLOR_DARK_LABEL.value
+ )
+
+ return f"""
+ background-color: {label_bg_color};
+ color: #FFFFFF;
+ font-family: Oxanium;
+ font-weight: bold;
+ font-size: 12px;
+ border-radius: 3px;
+ padding-top: 4px;
+ padding-right: 1px;
+ padding-bottom: 1px;
+ padding-left: 1px;
+ """
+
+
+def tag_style(
+ primary_color: QColor,
+ text_color: QColor,
+ border_color: QColor,
+ highlight_color: QColor,
+ border_style: str = "solid",
+) -> str:
+ """Style used for TagWidgets."""
+ return f"""
+ QPushButton{{
+ background: rgba{primary_color.toTuple()};
+ color: rgba{text_color.toTuple()};
+ font-weight: 600;
+ border-color: rgba{border_color.toTuple()};
+ border-radius: 6px;
+ border-style: {border_style};
+ border-width: 2px;
+ padding-right: 4px;
+ padding-left: 4px;
+ font-size: 13px
+ }}
+ QPushButton::hover{{
+ border-color: rgba{highlight_color.toTuple()};
+ }}
+ QPushButton::pressed{{
+ background: rgba{highlight_color.toTuple()};
+ color: rgba{primary_color.toTuple()};
+ border-color: rgba{primary_color.toTuple()};
+ }}
+ QPushButton::focus{{
+ padding-right: 0px;
+ padding-left: 0px;
+ outline-style: solid;
+ outline-width: 1px;
+ outline-radius: 4px;
+ outline-color: rgba{text_color.toTuple()};
+ }}
+ """
+
+
+def tag_remove_button_style(
+ primary_color: QColor, text_color: QColor, border_color: QColor, highlight_color: QColor
+) -> str:
+ """Style used for "Remove" buttons on TagWidgets [-]."""
+ return f"""
+ QPushButton{{
+ color: rgba{primary_color.toTuple()};
+ background: rgba{text_color.toTuple()};
+ font-weight: 800;
+ border-radius: 5px;
+ border-width: 4;
+ border-color: rgba(0,0,0,0);
+ padding-bottom: 4px;
+ font-size: 14px
+ }}
+ QPushButton::hover{{
+ background: rgba{primary_color.toTuple()};
+ color: rgba{text_color.toTuple()};
+ border-color: rgba{highlight_color.toTuple()};
+ border-width: 2;
+ border-radius: 6px;
+ }}
+ QPushButton::pressed{{
+ background: rgba{border_color.toTuple()};
+ color: rgba{highlight_color.toTuple()};
+ }}
+ QPushButton::focus{{
+ background: rgba{border_color.toTuple()};
+ outline: none;
+ }}
+ """
+
+
+def title_line_edit_style() -> str:
+ """Used to mimic an H3-like header style inside a QLineEdit."""
+ return """
+ font-weight: bold;
+ font-size: 16px;
+ """
+
+
+def header(string: str, level: int, color: str | None = None) -> str:
+ """Wrap a string in HTML header tags.
+
+ Args:
+ string (str): The string to format.
+ level (int): A value between 1 and 6 denoting the header level.
+ For example, "1" will create tags, "6" will create tags, etc.
+ color: Optional color string to pass as an inline HTML style.
+ """
+ if level < 1:
+ level = 1
+ elif level > 6:
+ level = 6
+
+ style_tag: str = ""
+ if color is not None:
+ style_tag = f" style='color: {color}'"
+
+ return f"{string}"
+
+
+def get_tag_primary_color(tag: Tag) -> QColor:
+ primary_color = QColor(
+ get_tag_color(ColorType.PRIMARY, TagColorEnum.DEFAULT)
+ if not tag.color
+ else tag.color.primary
+ )
+
+ return primary_color
+
+
+def get_tag_border_color(primary_color: QColor) -> QColor:
+ border_color: QColor = QColor(primary_color)
+ border_color.setRed(min(border_color.red() + 20, 255))
+ border_color.setGreen(min(border_color.green() + 20, 255))
+ border_color.setBlue(min(border_color.blue() + 20, 255))
+
+ return border_color
+
+
+def get_tag_highlight_color(primary_color: QColor) -> QColor:
+ highlight_color: QColor = QColor(primary_color)
+ highlight_color = highlight_color.toHsl()
+ highlight_color.setHsl(highlight_color.hue(), min(highlight_color.saturation(), 200), 225, 255)
+ highlight_color = highlight_color.toRgb()
+
+ return highlight_color
+
+
+def get_tag_text_color(primary_color: QColor, highlight_color: QColor) -> QColor:
+ if primary_color.lightness() > 120:
+ text_color = QColor(primary_color)
+ text_color = text_color.toHsl()
+ text_color.setHsl(text_color.hue(), text_color.saturation(), 50, 255)
+ return text_color.toRgb()
+ else:
+ return highlight_color
diff --git a/src/tagstudio/resources/translations/en.json b/src/tagstudio/resources/translations/en.json
index e0cd5dbb..c04a4f62 100644
--- a/src/tagstudio/resources/translations/en.json
+++ b/src/tagstudio/resources/translations/en.json
@@ -5,6 +5,8 @@
"about.license": "License",
"about.module.found": "Found",
"about.title": "About TagStudio",
+ "about.version": "Version",
+ "about.version.latest": "{built_version} (Latest Release: {latest_version})",
"about.website": "Website",
"app.git": "Git Commit",
"app.pre_release": "Pre-Release",
@@ -352,6 +354,7 @@
"tag.parent_tags": "Parent Tags",
"tag.parent_tags.add": "Add Parent Tag(s)",
"tag.parent_tags.description": "This tag can be treated as a substitute for any of these Parent Tags in searches.",
+ "tag.properties": "Properties",
"tag.remove": "Remove Tag",
"tag.search_for_tag": "Search for Tag",
"tag.shorthand": "Shorthand",