Compare commits

...

4 Commits

Author SHA1 Message Date
Travis Abendshien 0bb1615b61 ci: bonk coverage check we don't use 2026-06-25 19:05:06 -07:00
Travis Abendshien beb77588bd docs: update field documentation 2026-06-25 18:55:45 -07:00
Travis Abendshien 095ec9a379 fix(ui): fix untranslated key 2026-06-25 18:54:18 -07:00
Travis Abendshien 8989bedeb5 refactor: move stylesheets to central file 2026-06-25 17:55:15 -07:00
41 changed files with 683 additions and 703 deletions
+4 -3
View File
@@ -75,6 +75,7 @@ jobs:
run: |
pytest
# TODO: Change the coverage check and generally figure out how we want to use one.
coverage:
name: Check coverage
runs-on: ubuntu-latest
@@ -89,9 +90,9 @@ jobs:
- name: Check coverage
uses: yedpodtrzitko/coverage@main
with:
thresholdAll: 0.1
thresholdNew: 0.1
thresholdModified: 0.1
thresholdAll: 0
thresholdNew: 0
thresholdModified: 0
coverageFile: coverage.xml
token: ${{ secrets.GITHUB_TOKEN }}
sourceDir: tagstudio/src
Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

+40 -11
View File
@@ -8,24 +8,53 @@ icon: material/text-box
# :material-text-box: Fields
Fields are additional types of metadata that you can attach to [file entries](./entries.md). Like [tags](tags.md), fields are not stored inside files themselves nor in sidecar files, but rather inside the respective TagStudio [library](./index.md) save file.
Fields are extra pieces of information you can add to [file entries](./entries.md), similar to how [tags](tags.md) are added to entries. Fields are useful for storing information that doesn't nessisarily need to be a tag, such as titles, comments, notes, specific dates or times, etc.
## Field Types
To add a field to an entry, click the "Add Field" button in the preview panel. From there you can search and/or select a [field template](#field-templates) to choose from, or create a new one from the search bar. Alternatively you can create new field templates from **Edit -> Manage Field Templates**.
### Text Line
<figure markdown="span">
![Fields Example](assets/fields_example.png)
<figcaption>Example of tags and various fields on a file entry.</figcaption>
</figure>
A string of text, displayed as a single line.
## :material-text-box-plus-outline: Field Templates
- e.g: Title, Author, Artist, URL, etc.
Field templates are handy templates to use when adding fields to entries that contain preconfigured options but no actual data. When you add a field to an entry from the "Add Field" button, you choose from a template to add and then fill in the information afterwards. TagStudio includes a handful of field templates to start you off with, but you're free to modify or delete them, or simply create your own.
### Text Box
Field templates can be viewed, created, and deleted from the **Edit -> Manage Field Templates** window. You can also edit field templates from the "Add Field" menu, and create new ones on the fly from the search bar. Note that you can not currently delete field templates from the "Add Field" menu, just like tags.
A long string of text displayed as a box of text.
<figure markdown="span">
![Field Template Manager](assets/field_template_manager.png)
<figcaption>Field Template Manager from <b>Edit -> Manage Field Templates</b>.</figcaption>
</figure>
- e.g: Description, Notes, etc.
<figure markdown="span">
![Field Template Editor](assets/field_template_editor.png)
<figcaption>The field template editor, shown creating a new "Citations" field.</figcaption>
</figure>
### Datetime
## :material-format-list-bulleted-type: Field Types
A date and time value.
Fields come in a variety of types that are better suited for different types of information, and may provide additional options unique to those types. Single lines are good for fields like titles, while multiline blocks are good for things like comments and notes.
- e.g: Date Published, Date Taken, etc.
### :material-text-box: Text
Text fields contain a piece of text with the option to display it either a single line or a multiline body of text.
| Option | Value | Description |
| --------- | ---------- | ------------------------------------------------------------------------ |
| Multiline | True/False | Indicates if the text should be displayed on multiple lines or just one. |
<figure markdown="span">
![Text Field Editor](assets/text_field_editor.png)
<figcaption>The text field editor, editing a "Comments" field on an entry.</figcaption>
</figure>
### :material-calendar-month: Datetime
Datetime fields contain a date and time value. Dates are formatted using the format specified in your application settings.
<figure markdown="span">
![Datetime Field Editor](assets/datetime_field_editor.png)
<figcaption>The datetime field editor, expanded to show the date picker.</figcaption>
</figure>
+4 -4
View File
@@ -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"
@@ -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)
@@ -79,9 +79,7 @@ class FieldTemplateSearchPanel(SearchPanel[BaseFieldTemplate]):
modal: PanelModal = PanelModal(
panel,
Translations["field_template.new"],
Translations["field_template.add"]
if add_to_entry
else Translations["field_template.new"],
Translations["field_template.new"],
is_savable=True,
)
@@ -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"<h3>{count_text}</h3>")
self.ignored_count_label.setText(header(count_text, 3))
def update_driver_widgets(self):
if (
@@ -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"<h2>{title}</h2>")
self.title_label.setText(header(title, 2))
def update_stats(self):
self.entry_count_label.setText(f"<b>{self.lib.entries_count}</b>")
@@ -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"<h1>{frame.title}</h1>")
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
+30 -33
View File
@@ -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"<h2>TagStudio Alpha {VERSION}{branch}</h2>")
# 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'<p><a href="{repo_link}">GitHub</a> | '
f'<a href="{docs_link}">{Translations["about.documentation"]}</a> | '
f'<a href="{discord_link}">Discord</a></p>'
f'<p><a href="{GITHUB_REPO_URL}">GitHub</a> | '
f'<a href="{DOCS_URL}">{Translations["about.documentation"]}</a> | '
f'<a href="{DISCORD_URL}">Discord</a></p>'
)
self.links_label.setWordWrap(True)
self.links_label.setOpenExternalLinks(True)
+2 -2
View File
@@ -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()
+16 -100
View File
@@ -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()
+5 -11
View File
@@ -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()
+17 -45
View File
@@ -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("<h3>Properties</h3>"))
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)
+2 -31
View File
@@ -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(
+2 -1
View File
@@ -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):
+1 -1
View File
@@ -434,7 +434,7 @@ class FieldContainers(QWidget):
)
else:
text = f"<i>{Translations['field.mixed_data']}</i>"
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()
+3 -14
View File
@@ -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"<h4>{title}</h4>"
self.title = header(title, 4)
self.title_widget.setText(self.title)
@override
+5 -23
View File
@@ -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()
+10 -4
View File
@@ -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
+2 -1
View File
@@ -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"<h3>{count_text}</h3>")
self.unlinked_count_label.setText(header(count_text, 3))
@override
def showEvent(self, event: QtGui.QShowEvent) -> None:
+3 -3
View File
@@ -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()
+5 -4
View File
@@ -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"<h4>{x}</h4>"),
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"<h4>{type(e).__name__}</h4>")
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
)
),
+16 -63
View File
@@ -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()
+6 -5
View File
@@ -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"<h3>{Translations['color_manager.title']}</h3>")
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:
+10 -27
View File
@@ -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(
+23 -68
View File
@@ -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"<h4>{display_name if display_name else group}</h4>")
)
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"<h4>{no_color_str}</h4>"))
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)
+14 -96
View File
@@ -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
+8 -10
View File
@@ -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"<h4 style='color: {get_ui_color(ColorType.PRIMARY, UiColor.RED)}'>"
f"{perm_warning_msg}</h4>"
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"<h3>{msg_text}</h3>"
f"<h4>{Translations['trash.dialog.disambiguation_warning.singular']}</h4>"
f"{header(msg_text, 3)}"
f"{header(Translations['trash.dialog.disambiguation_warning.singular'], 4)}"
f"{filename if filename else ''}"
f"{perm_warning}<br>"
)
@@ -1045,8 +1045,8 @@ class QtDriver(DriverMixin, QObject):
trash_term=trash_term(),
)
msg.setText(
f"<h3>{msg_text}</h3>"
f"<h4>{Translations['trash.dialog.disambiguation_warning.plural']}</h4>"
f"{header(msg_text, 3)}"
f"{header(Translations['trash.dialog.disambiguation_warning.plural'], 4)}"
f"{perm_warning}<br>"
)
@@ -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),
+2 -2
View File
@@ -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()
@@ -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")
@@ -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"<h3>{Translations['library_info.stats']}</h3>")
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"<h3>{Translations['library_info.cleanup']}</h3>")
self.cleanup_label = QLabel(header(Translations["library_info.cleanup"], 3))
self.cleanup_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
self.cleanup_grid: QWidget = QWidget()
+3 -28
View File
@@ -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)
+2 -34
View File
@@ -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:
+421 -31
View File
@@ -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 <h1> tags, "6" will create <h6> 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"<h{level}{style_tag}>{string}</h{level}>"
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
@@ -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",