begin making renderers classes, improve text thumbnails

This commit is contained in:
Travis Abendshien
2026-08-21 14:10:23 -07:00
parent f66b8a88bd
commit 22d1b70c33
19 changed files with 1056 additions and 387 deletions
+15 -4
View File
@@ -23,12 +23,12 @@ path = [
".gitignore",
"flake.lock",
]
SPDX-FileCopyrightText = "(c) TagStudio Contributors"
SPDX-FileCopyrightText = "TagStudio Contributors"
SPDX-License-Identifier = "GPL-3.0-only"
[[annotations]]
path = ["src/tagstudio/resources/translations/*.json"]
SPDX-FileCopyrightText = "(c) TagStudio Contributors"
SPDX-FileCopyrightText = "TagStudio Contributors"
SPDX-License-Identifier = "GPL-3.0-or-later"
[[annotations]]
@@ -41,7 +41,7 @@ path = [
"src/tagstudio/resources/qt/images/bxs-volume-full-solid.png",
"src/tagstudio/resources/qt/images/file_icons/database.png",
]
SPDX-FileCopyrightText = "(c) 2026 Boxicons"
SPDX-FileCopyrightText = "2026 Boxicons"
SPDX-License-Identifier = "MIT"
[[annotations]]
@@ -53,5 +53,16 @@ path = [
"src/tagstudio/resources/qt/images/hint_tag_added.png",
"src/tagstudio/resources/qt/images/hint_tag_create.png",
]
SPDX-FileCopyrightText = "(c) github:google/material-design-icons Contributors"
SPDX-FileCopyrightText = "github:google/material-design-icons Contributors"
SPDX-License-Identifier = "Apache-2.0"
[[annotations]]
path = ["src/tagstudio/resources/qt/fonts/Oxanium-Bold.ttf"]
SPDX-FileCopyrightText = "2019 The Oxanium Project Authors (https://github.com/sevmeyer/oxanium)"
SPDX-License-Identifier = "OFL-1.1"
[[annotations]]
path = ["src/tagstudio/resources/fonts/JetBrainsMono/**"]
SPDX-FileCopyrightText = "2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)"
SPDX-License-Identifier = "OFL-1.1"
+8 -1
View File
@@ -31,7 +31,7 @@ class TagClickActionOption(enum.IntEnum):
DEFAULT = OPEN_EDIT
class Theme(enum.StrEnum):
class ThemePalette(enum.StrEnum):
COLOR_BG_DARK = "#65000000"
COLOR_BG_LIGHT = "#22000000"
COLOR_DARK_LABEL = "#DD000000"
@@ -44,6 +44,13 @@ class Theme(enum.StrEnum):
COLOR_FORBIDDEN_BG = "#65440D12"
class Theme(enum.IntEnum):
DARK = 0
LIGHT = 1
SYSTEM = 2
DEFAULT = SYSTEM
class OpenStatus(enum.IntEnum):
NOT_FOUND = 0
SUCCESS = 1
+8 -1
View File
@@ -105,7 +105,14 @@ class RefreshTracker:
shell=True,
encoding="UTF-8",
)
compiled_ignore_path.unlink()
try:
compiled_ignore_path.unlink()
except Exception as e:
logger.error(
"[Refresh] Could not remove compiled ignore path",
path=compiled_ignore_path,
error=e,
)
if result.stderr:
logger.error(result.stderr)
+591 -248
View File
@@ -37,8 +37,8 @@ class _Type:
class MediaTypeGroup:
def __init__(self, name_key: str, types: list[_Type]) -> None:
self.searchable: frozenset[str]
self.renderable: frozenset[str]
self.searchable: frozenset[str] # TODO: Make these dynamic
self.renderable: frozenset[str] # TODO: Make these dynamic
self.name_key = name_key
self.types = types
# NOTE: Does self.types need to exist?
@@ -78,254 +78,597 @@ class MediaTypeGroup:
class MediaTypes:
SEARCH, RENDER = Context.SEARCH, Context.RENDER
# Adobe ----------------------------------------------------------------------------------------
adobe_photoshop = MediaTypeGroup(
"adobe.photoshop",
[
_Type(".pdd", SEARCH),
_Type(".psb", SEARCH),
_Type(".psd", SEARCH),
],
)
adobe_illustrator = MediaTypeGroup(
"adobe.illustrator",
[
_Type(".ai", SEARCH),
],
)
pdf = MediaTypeGroup(
"pdf",
[
_Type(".pdf", [SEARCH, RENDER]),
],
)
adobe = MediaTypeGroup("adobe", adobe_photoshop.types + adobe_illustrator.types + pdf.types)
# Affinity -------------------------------------------------------------------------------------
affinity_photo = MediaTypeGroup(
"affinity.photo",
[_Type(".afphoto", [SEARCH, RENDER])],
)
affinity_designer = MediaTypeGroup(
"affinity.designer",
[_Type(".afdesign", [SEARCH, RENDER])],
)
affinity_publisher = MediaTypeGroup(
"affinity.publisher",
[_Type([".afpublisher", ".afpub"], [SEARCH, RENDER])],
)
affinity = MediaTypeGroup(
"affinity",
affinity_photo.types
+ affinity_designer.types
+ affinity_publisher.types
+ [_Type(".af", [SEARCH, RENDER])],
)
# MIDI -----------------------------------------------------------------------------------------
midi = MediaTypeGroup("midi", [_Type([".mid", ".midi"], SEARCH)])
# Audio ----------------------------------------------------------------------------------------
audio = MediaTypeGroup(
"audio",
[
_Type(".aac", [RENDER, SEARCH]),
_Type(
[".aif", ".aiff", ".aifc"],
[RENDER, SEARCH],
),
_Type(".caf", [RENDER, SEARCH]),
_Type(".flac", [RENDER, SEARCH]),
_Type(".m4a", [RENDER, SEARCH]),
_Type(".m4p", [RENDER, SEARCH]),
_Type(".mp3", [RENDER, SEARCH]),
_Type(".ogg", [RENDER, SEARCH]),
_Type(".wav", [RENDER, SEARCH]),
_Type(".wma", [RENDER, SEARCH]),
]
+ midi.types,
)
# RAW Images -----------------------------------------------------------------------------------
raw_image = MediaTypeGroup(
"image.raw",
[
_Type(".arw", [SEARCH, RENDER]),
_Type(".cr2", [SEARCH, RENDER]),
_Type(".cr3", [SEARCH, RENDER]),
_Type(".crw", [SEARCH, RENDER]),
_Type(".dng", [SEARCH, RENDER]),
_Type(".nef", [SEARCH, RENDER]),
_Type(".nrw", [SEARCH, RENDER]),
_Type(".orf", [SEARCH, RENDER]),
_Type(".r3d", [SEARCH, RENDER]),
_Type(".raf", [SEARCH, RENDER]),
_Type(".raw", [SEARCH, RENDER]),
_Type(".rw2", [SEARCH, RENDER]),
_Type(".srf", [SEARCH, RENDER]),
_Type(".srf2", [SEARCH, RENDER]),
],
)
# Raster Images --------------------------------------------------------------------------------
raster_image = MediaTypeGroup(
"image.raster",
[
_Type(".apng", [SEARCH, RENDER]),
_Type(".avif", [SEARCH, RENDER]),
_Type(".bmp", [SEARCH, RENDER]),
_Type(".exr", [SEARCH, RENDER]),
_Type(".gif", [SEARCH, RENDER]),
_Type(
[
".jfif",
".jpeg_large",
".jpeg",
".jpg_large",
".jpg",
],
[SEARCH, RENDER],
),
_Type(".jxl", [SEARCH, RENDER]),
_Type(".png", [SEARCH, RENDER]),
_Type(".psb", RENDER),
_Type(".psd", RENDER),
_Type(".webp", [SEARCH, RENDER]),
_Type([".heic", ".heif"], [SEARCH, RENDER]),
_Type([".j2k", ".jp2", ".jpg2"], [SEARCH, RENDER]),
_Type([".tif", ".tiff"], [SEARCH, RENDER]),
],
)
# Vector Images --------------------------------------------------------------------------------
vector = MediaTypeGroup(
"image.vector",
[
_Type(".ai", RENDER),
_Type(".eps", SEARCH),
_Type(".epsf", SEARCH),
_Type(".epsi", SEARCH),
_Type(".svg", [SEARCH, RENDER]),
_Type(".svgz", SEARCH),
],
)
binary = MediaTypeGroup(
"binary",
[
_Type(".dll", [RENDER, SEARCH]),
_Type(".dylib", [RENDER, SEARCH]),
_Type(".exe", [RENDER, SEARCH]),
_Type(".o", [RENDER, SEARCH]),
_Type(".pyc", [RENDER, SEARCH]),
_Type(".pyd", [RENDER, SEARCH]),
_Type(".pyo", [RENDER, SEARCH]),
],
)
python = MediaTypeGroup(
"python",
[
_Type(".ipynb", [RENDER, SEARCH]),
_Type(".py", [RENDER, SEARCH]),
_Type(".pyc", SEARCH),
_Type(".pyd", SEARCH),
_Type(".pyi", [RENDER, SEARCH]),
_Type(".pyo", SEARCH),
],
)
javascript = MediaTypeGroup(
"javascript",
[
_Type(".cjs", [SEARCH, RENDER]),
_Type(".js", [SEARCH, RENDER]),
_Type(".jsx", [SEARCH, RENDER]),
_Type(".mjs", [SEARCH, RENDER]),
],
)
typescript = MediaTypeGroup(
"typescript",
[
_Type(".cts", [SEARCH, RENDER]),
_Type(".mts", [SEARCH, RENDER]),
# _Type(".ts", [SEARCH, RENDER]),
_Type(".tsx", [SEARCH, RENDER]),
],
)
# Shell Script ---------------------------------------------------------------------------------
shell = MediaTypeGroup(
"shell",
[
_Type(".bat", [SEARCH, RENDER]),
_Type(".csh", [SEARCH, RENDER]),
_Type(".fish", [SEARCH, RENDER]),
_Type(".ps1", [SEARCH, RENDER]),
_Type(".sh", [SEARCH, RENDER]),
],
)
# Markdown -------------------------------------------------------------------------------------
markdown = MediaTypeGroup(
"markdown",
[
_Type(
[
".markdown",
".md",
".mkd",
".rmd",
],
[SEARCH, RENDER],
),
],
)
# Plaintext ------------------------------------------------------------------------------------
plaintext = MediaTypeGroup(
"plaintext",
[_Type([".txt", ".text"], [SEARCH, RENDER])] + markdown.types,
)
# Code -----------------------------------------------------------------------------------------
code_types = MediaTypeGroup(
"type.code",
python.types + javascript.types + typescript.types + shell.types,
)
# Video ----------------------------------------------------------------------------------------
video = MediaTypeGroup(
"video",
[
_Type(".3gp", [SEARCH, RENDER]),
_Type(".avi", [SEARCH, RENDER]),
_Type(".flv", [SEARCH, RENDER]),
_Type(".gifv", [SEARCH, RENDER]),
_Type(".hevc", [SEARCH, RENDER]),
_Type(".m4p", [SEARCH, RENDER]),
_Type(".m4v", [SEARCH, RENDER]),
_Type(".mkv", [SEARCH, RENDER]),
_Type(".mov", [SEARCH, RENDER]),
_Type(".mp4", [SEARCH, RENDER]),
_Type(".webm", [SEARCH, RENDER]),
_Type(".wmv", [SEARCH, RENDER]),
],
)
# ------------------
# TODO: Implement filename equivalencies
# TODO: Implement in_all_groups or something, if needed
# TODO: Implement collision detection (e.g. ".ts")
@staticmethod
def all_media_types():
static_methods = [
name for name, attr in MediaTypes.__dict__.items() if isinstance(attr, MediaTypeGroup)
]
return static_methods
def register(group: MediaTypeGroup) -> None:
setattr(MediaTypes, group.name_key.replace(".", "_"), group)
SEARCH, RENDER = Context.SEARCH, Context.RENDER
# Adobe ----------------------------------------------------------------------------------------
# TODO: Don't have these floating in the global space
_adobe_photoshop = MediaTypeGroup(
"adobe.photoshop",
[
_Type(".pdd", SEARCH),
_Type(".psb", SEARCH),
_Type(".psd", SEARCH),
],
)
MediaTypes.register(_adobe_photoshop)
_adobe_illustrator = MediaTypeGroup(
"adobe.illustrator",
[
_Type(".ai", SEARCH),
],
)
MediaTypes.register(_adobe_illustrator)
_pdf = MediaTypeGroup(
"pdf",
[
_Type(".pdf", [SEARCH, RENDER]),
_Type(".ai", RENDER),
],
)
MediaTypes.register(_pdf)
adobe = MediaTypeGroup("adobe", _adobe_photoshop.types + _adobe_illustrator.types + _pdf.types)
MediaTypes.register(adobe)
# Affinity -------------------------------------------------------------------------------------
affinity_photo = MediaTypeGroup(
"affinity.photo",
[_Type(".afphoto", [SEARCH, RENDER])],
)
affinity_designer = MediaTypeGroup(
"affinity.designer",
[_Type(".afdesign", [SEARCH, RENDER])],
)
affinity_publisher = MediaTypeGroup(
"affinity.publisher",
[_Type([".afpublisher", ".afpub"], [SEARCH, RENDER])],
)
affinity = MediaTypeGroup(
"affinity",
affinity_photo.types
+ affinity_designer.types
+ affinity_publisher.types
+ [_Type(".af", [SEARCH, RENDER])],
)
# Blender --------------------------------------------------------------------------------------
_blender = MediaTypeGroup(
"blender",
[
_Type(".blen_tc", [SEARCH, RENDER]),
_Type(".blend", [SEARCH, RENDER]),
# Numbered Blender auto-backup files (.blend1 - .blend32)
_Type([f".blend{i}" for i in range(1, 33)], [SEARCH, RENDER]),
],
)
MediaTypes.register(_blender)
# Clip Studio Paint ----------------------------------------------------------------------------
clip_studio_paint = MediaTypeGroup(
"clip_studio_paint",
[
_Type(".lip", [SEARCH, RENDER]),
_Type(".clip", [SEARCH, RENDER]),
_Type(".cmc", [SEARCH, RENDER]),
],
)
# Krita ----------------------------------------------------------------------------------------
krita = MediaTypeGroup(
"krita",
[
_Type(".kra", [SEARCH, RENDER]),
_Type(".krz", [SEARCH, RENDER]),
],
)
# MediBang Paint / FireAlpaca ------------------------------------------------------------------
medibang_paint = MediaTypeGroup("medibang_paint", [_Type(".mdp", [SEARCH, RENDER])])
# Paint.NET ------------------------------------------------------------------------------------
paint_dot_net = MediaTypeGroup("paint_dot_net", [_Type(".pdn", [SEARCH, RENDER])])
# Archives -------------------------------------------------------------------------------------
archive = MediaTypeGroup(
"archive",
[
_Type(".7z", [SEARCH, RENDER]),
_Type(".gz", SEARCH),
_Type(".rar", [SEARCH, RENDER]),
_Type(".s7z", [SEARCH, RENDER]),
_Type(".tar", [SEARCH, RENDER]),
_Type(".tgz", [SEARCH, RENDER]),
_Type(".zip", [SEARCH, RENDER]),
],
)
# eBooks -------------------------------------------------------------------------------------
ebook = MediaTypeGroup(
"ebook",
[
_Type(".azw", [SEARCH, RENDER]),
_Type(".azw3", [SEARCH, RENDER]),
_Type(".cb7", [SEARCH, RENDER]),
_Type(".cba", [SEARCH, RENDER]),
_Type(".cbr", [SEARCH, RENDER]),
_Type(".cbt", [SEARCH, RENDER]),
_Type(".cbz", [SEARCH, RENDER]),
_Type(".djvu", [SEARCH, RENDER]),
_Type(".epub", [SEARCH, RENDER]),
_Type(".fb2", [SEARCH, RENDER]),
_Type(".ibook", [SEARCH, RENDER]),
_Type(".inf", [SEARCH, RENDER]),
_Type(".kfx", [SEARCH, RENDER]),
_Type(".lit", [SEARCH, RENDER]),
_Type(".mobi", [SEARCH, RENDER]),
_Type(".pdb", [SEARCH, RENDER]),
_Type(".prc", [SEARCH, RENDER]),
_Type(".mscz", RENDER),
],
)
# Fonts --------------------------------------------------------------------------------------
font = MediaTypeGroup(
"font",
[
_Type(".fon", SEARCH),
_Type(".otf", [SEARCH, RENDER]),
_Type(".ttc", [SEARCH, RENDER]),
_Type(".ttf", [SEARCH, RENDER]),
_Type(".woff", [SEARCH, RENDER]),
_Type(".woff2", [SEARCH, RENDER]),
],
)
# Office & Documents ---------------------------------------------------------------------------
document = MediaTypeGroup(
"document",
[
_Type(".doc", SEARCH),
_Type(".docm", SEARCH),
_Type(".docx", SEARCH),
_Type(".dot", SEARCH),
_Type(".dotm", SEARCH),
_Type(".dotx", SEARCH),
_Type(".odt", SEARCH),
_Type(".pages", SEARCH),
_Type(".pdf", SEARCH),
_Type(".pxd", SEARCH),
_Type(".rtf", SEARCH),
_Type(".tex", SEARCH),
_Type(".wpd", SEARCH),
_Type(".wps", SEARCH),
],
)
open_document = MediaTypeGroup(
"open_document",
[
_Type(".fodg", [SEARCH, RENDER]),
_Type(".fodp", [SEARCH, RENDER]),
_Type(".fods", [SEARCH, RENDER]),
_Type(".fodt", [SEARCH, RENDER]),
_Type(".mscz", SEARCH),
_Type(".odf", [SEARCH, RENDER]),
_Type(".odg", [SEARCH, RENDER]),
_Type(".odp", [SEARCH, RENDER]),
_Type(".ods", [SEARCH, RENDER]),
_Type(".odt", [SEARCH, RENDER]),
_Type(".ora", [SEARCH, RENDER]),
],
)
iwork = MediaTypeGroup(
"iwork",
[
_Type(".key", [SEARCH, RENDER]),
_Type(".numbers", [SEARCH, RENDER]),
_Type(".pages", [SEARCH, RENDER]),
_Type(".pxd", RENDER),
],
)
presentation = MediaTypeGroup(
"presentation",
[
_Type(".key", SEARCH),
_Type(".odp", SEARCH),
_Type(".ppt", SEARCH),
_Type(".pptx", [SEARCH, RENDER]),
],
)
spreadsheet = MediaTypeGroup(
"spreadsheet",
[
_Type(".csv", SEARCH),
_Type(".numbers", SEARCH),
_Type(".ods", SEARCH),
_Type(".xls", SEARCH),
_Type(".xlsx", SEARCH),
],
)
# 3D -------------------------------------------------------------------------------------------
model = MediaTypeGroup(
"model",
[
_Type(".3ds", SEARCH),
_Type(".fbx", SEARCH),
_Type(".obj", SEARCH),
_Type(".stl", SEARCH),
],
)
material = MediaTypeGroup(
"material",
[
_Type(".mtl", SEARCH),
],
)
shader = MediaTypeGroup(
"shader",
[
_Type(".effect", SEARCH),
_Type(".frag", SEARCH),
_Type(".fsh", SEARCH),
_Type(".glsl", SEARCH),
_Type(".shader", SEARCH),
_Type(".vert", SEARCH),
_Type(".vsh", SEARCH),
],
)
# System & Misc ------------------------------------------------------------------------------
database = MediaTypeGroup(
"database",
[
_Type(".accdb", SEARCH),
_Type(".mdb", SEARCH),
_Type(".pdb", SEARCH),
_Type(".db", SEARCH),
_Type(".sqlite", SEARCH),
_Type(".sqlite3", SEARCH),
],
)
disk_image = MediaTypeGroup(
"disk_image",
[
_Type(".bios", SEARCH),
_Type(".dmg", SEARCH),
_Type(".fhdx", SEARCH),
_Type(".iso", SEARCH),
],
)
installer = MediaTypeGroup(
"installer",
[
_Type(".appx", SEARCH),
_Type(".msi", SEARCH),
_Type(".msix", SEARCH),
],
)
package = MediaTypeGroup(
"package",
[
_Type(".aab", SEARCH),
_Type(".akp", SEARCH),
_Type(".apk", SEARCH),
_Type(".apkm", SEARCH),
_Type(".apks", SEARCH),
_Type(".pkg", SEARCH),
_Type(".xapk", SEARCH),
],
)
program = MediaTypeGroup(
"program",
[
_Type(".app", SEARCH),
_Type(".bin", SEARCH),
_Type(".exe", SEARCH),
],
)
shortcut = MediaTypeGroup(
"shortcut",
[
_Type(".desktop", SEARCH),
_Type(".lnk", SEARCH),
_Type(".url", SEARCH),
],
)
# Valve Source Engine --------------------------------------------------------------------------
source_engine = MediaTypeGroup(
"source_engine",
[_Type(".vtf", [SEARCH, RENDER])],
)
# MIDI -----------------------------------------------------------------------------------------
midi = MediaTypeGroup("midi", [_Type([".mid", ".midi"], SEARCH)])
# Audio ----------------------------------------------------------------------------------------
audio = MediaTypeGroup(
"audio",
[
_Type(".aac", [RENDER, SEARCH]),
_Type(
[".aif", ".aiff", ".aifc"],
[RENDER, SEARCH],
),
_Type(".caf", [RENDER, SEARCH]),
_Type(".flac", [RENDER, SEARCH]),
_Type(".m4a", [RENDER, SEARCH]),
_Type(".m4p", [RENDER, SEARCH]),
_Type(".mp3", [RENDER, SEARCH]),
_Type(".ogg", [RENDER, SEARCH]),
_Type(".wav", [RENDER, SEARCH]),
_Type(".wma", [RENDER, SEARCH]),
]
+ midi.types,
)
# RAW Images -----------------------------------------------------------------------------------
raw_image = MediaTypeGroup(
"image.raw",
[
_Type(".arw", [SEARCH, RENDER]),
_Type(".cr2", [SEARCH, RENDER]),
_Type(".cr3", [SEARCH, RENDER]),
_Type(".crw", [SEARCH, RENDER]),
_Type(".dng", [SEARCH, RENDER]),
_Type(".nef", [SEARCH, RENDER]),
_Type(".nrw", [SEARCH, RENDER]),
_Type(".orf", [SEARCH, RENDER]),
_Type(".r3d", [SEARCH, RENDER]),
_Type(".raf", [SEARCH, RENDER]),
_Type(".raw", [SEARCH, RENDER]),
_Type(".rw2", [SEARCH, RENDER]),
_Type(".srf", [SEARCH, RENDER]),
_Type(".srf2", [SEARCH, RENDER]),
],
)
# Raster Images --------------------------------------------------------------------------------
raster_image = MediaTypeGroup(
"image.raster",
[
_Type(".apng", [SEARCH, RENDER]),
_Type(".avif", [SEARCH, RENDER]),
_Type(".bmp", [SEARCH, RENDER]),
_Type(".exr", [SEARCH, RENDER]),
_Type(".gif", [SEARCH, RENDER]),
_Type(
[
".jfif",
".jpeg_large",
".jpeg",
".jpg_large",
".jpg",
],
[SEARCH, RENDER],
),
_Type(".jxl", [SEARCH, RENDER]),
_Type(".png", [SEARCH, RENDER]),
_Type(".psb", RENDER),
_Type(".psd", RENDER),
_Type(".webp", [SEARCH, RENDER]),
_Type([".heic", ".heif"], [SEARCH, RENDER]),
_Type([".j2k", ".jp2", ".jpg2"], [SEARCH, RENDER]),
_Type([".tif", ".tiff"], [SEARCH, RENDER]),
],
)
# Vector Images --------------------------------------------------------------------------------
vector_image = MediaTypeGroup(
"image.vector",
[
_Type(".eps", SEARCH),
_Type(".epsf", SEARCH),
_Type(".epsi", SEARCH),
_Type(".svg", [SEARCH, RENDER]),
_Type(".svgz", SEARCH),
],
)
# Animated Images ------------------------------------------------------------------------------
animated_image = MediaTypeGroup(
"image.animated",
[
_Type(".gif", [SEARCH, RENDER]),
_Type(".apng", [SEARCH, RENDER]),
_Type(".webp", [SEARCH, RENDER]),
_Type(".jxl", SEARCH),
],
)
# Binary ---------------------------------------------------------------------------------------
binary = MediaTypeGroup(
"binary",
[
_Type(".dll", [RENDER, SEARCH]),
_Type(".dylib", [RENDER, SEARCH]),
_Type(".exe", [RENDER, SEARCH]),
_Type(".o", [RENDER, SEARCH]),
_Type(".pyc", [RENDER, SEARCH]),
_Type(".pyd", [RENDER, SEARCH]),
_Type(".pyo", [RENDER, SEARCH]),
],
)
# Python ---------------------------------------------------------------------------------------
python = MediaTypeGroup(
"python",
[
_Type(".ipynb", [RENDER, SEARCH]),
_Type(".py", [RENDER, SEARCH]),
_Type(".pyc", SEARCH),
_Type(".pyd", SEARCH),
_Type(".pyi", [RENDER, SEARCH]),
_Type(".pyo", SEARCH),
],
)
# JavaScript -----------------------------------------------------------------------------------
javascript = MediaTypeGroup(
"javascript",
[
_Type(".cjs", [SEARCH, RENDER]),
_Type(".js", [SEARCH, RENDER]),
_Type(".jsx", [SEARCH, RENDER]),
_Type(".mjs", [SEARCH, RENDER]),
],
)
# TypeScript -----------------------------------------------------------------------------------
typescript = MediaTypeGroup(
"typescript",
[
_Type(".cts", [SEARCH, RENDER]),
_Type(".mts", [SEARCH, RENDER]),
# _Type(".ts", [SEARCH, RENDER]),
_Type(".tsx", [SEARCH, RENDER]),
],
)
# Shell Script ---------------------------------------------------------------------------------
shell = MediaTypeGroup(
"shell",
[
_Type(".bat", [SEARCH, RENDER]),
_Type(".csh", [SEARCH, RENDER]),
_Type(".fish", [SEARCH, RENDER]),
_Type(".nu", [SEARCH, RENDER]),
_Type(".ps1", [SEARCH, RENDER]),
_Type(".sh", [SEARCH, RENDER]),
_Type("activate", [SEARCH, RENDER]),
],
)
# Markdown -------------------------------------------------------------------------------------
markdown = MediaTypeGroup(
"markdown",
[
_Type(
[
".markdown",
".md",
".mkd",
".rmd",
],
[SEARCH, RENDER],
),
],
)
# Markup ---------------------------------------------------------------------------------------
markup = MediaTypeGroup(
"markup",
[
_Type(
[
".yml",
".yaml",
],
[SEARCH, RENDER],
),
_Type(
[
".json",
".jsonc",
".json5",
".jsonl",
],
[SEARCH, RENDER],
),
_Type(
[
".xml",
".xul",
],
[SEARCH, RENDER],
),
_Type(
[
".html",
".htm",
".xhtml",
".shtml",
".dhtml",
],
[SEARCH, RENDER],
),
_Type(".toml", [SEARCH, RENDER]),
],
)
# Code -----------------------------------------------------------------------------------------
code = MediaTypeGroup(
"code",
[
_Type(".lock", [SEARCH, RENDER]),
_Type(".log", [SEARCH, RENDER]),
]
+ python.types
+ javascript.types
+ typescript.types
+ shell.types
+ markdown.types
+ markup.types,
)
MediaTypes.register(code)
# Plaintext ------------------------------------------------------------------------------------
plaintext = MediaTypeGroup(
"plaintext",
[
_Type(".i3u", [SEARCH, RENDER]),
_Type(".lang", [SEARCH, RENDER]),
_Type("contributing", [SEARCH, RENDER]),
_Type("license", [SEARCH, RENDER]),
_Type("readme", [SEARCH, RENDER]),
_Type([".txt", ".text"], [SEARCH, RENDER]),
],
)
MediaTypes.register(plaintext)
# Video ----------------------------------------------------------------------------------------
video = MediaTypeGroup(
"video",
[
_Type(".3gp", [SEARCH, RENDER]),
_Type(".avi", [SEARCH, RENDER]),
_Type(".flv", [SEARCH, RENDER]),
_Type(".gifv", [SEARCH, RENDER]),
_Type(".hevc", [SEARCH, RENDER]),
_Type(".m4p", [SEARCH, RENDER]),
_Type(".m4v", [SEARCH, RENDER]),
_Type(".mkv", [SEARCH, RENDER]),
_Type(".mov", [SEARCH, RENDER]),
_Type(".mp4", [SEARCH, RENDER]),
_Type(".webm", [SEARCH, RENDER]),
_Type(".wmv", [SEARCH, RENDER]),
],
)
# ------------------
# @staticmethod
# def all_media_types():
# static_methods = [
# name for name, attr in MediaTypes.__dict__.items() if isinstance(attr, MediaTypeGroup)
# ]
# return static_methods
FILETYPE_EQUIVALENTS = [
+31
View File
@@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: MIT
# pyright: standard
from pathlib import Path
from PIL.Image import Image
from tagstudio.core.enums import Theme
class BasePreview:
"""A base preview renderer class."""
# The attribute name used for identifying the MediaType used with the preview renderer.
media_type_name: str
def __init__(self) -> None:
pass
@classmethod
def render(
cls,
filepath: Path,
theme: Theme,
size: tuple[int, int],
dpi_scale: float,
) -> Image | None:
raise NotImplementedError
+93 -18
View File
@@ -14,6 +14,7 @@ import structlog
from PIL import Image, ImageChops, ImageDraw, ImageEnhance, ImageFile, UnidentifiedImageError
from PIL.Image import DecompressionBombError
from tagstudio.core.enums import Theme
from tagstudio.core.exceptions import NoRendererError
from tagstudio.core.library.alchemy.library import Library
from tagstudio.core.library.ignore import Ignore
@@ -25,6 +26,7 @@ from tagstudio.core.media_types import (
MediaTypes,
)
from tagstudio.core.utils.types import unwrap
from tagstudio.previews.base_preview import BasePreview
from tagstudio.previews.gradients import four_corner_gradient
from tagstudio.previews.renderers.archive import (
apple_embedded_thumb,
@@ -34,8 +36,9 @@ from tagstudio.previews.renderers.archive import (
powerpoint_thumb,
)
from tagstudio.previews.renderers.audio import audio_album_thumb, audio_thumb, audio_waveform_thumb
from tagstudio.previews.renderers.blender import blender_thumb
from tagstudio.previews.renderers.blender import BlenderPreview, _blender_thumb
from tagstudio.previews.renderers.clip_studio import clip_studio_thumb
from tagstudio.previews.renderers.code import CodePreview
from tagstudio.previews.renderers.ebook import epub_thumb
from tagstudio.previews.renderers.font import font_full_preview, font_small_thumb
from tagstudio.previews.renderers.medibang_paint import medibang_paint_thumb
@@ -47,7 +50,10 @@ from tagstudio.previews.renderers.raster_image import (
raw_image_thumb,
)
from tagstudio.previews.renderers.source_engine import vtf_thumb
from tagstudio.previews.renderers.text import code_thumb, text_thumb
from tagstudio.previews.renderers.text import (
TextPreview,
text_thumb,
)
from tagstudio.previews.renderers.vector_image import vector_image_thumb
from tagstudio.previews.renderers.video import video_thumb
from tagstudio.qt.app_settings import (
@@ -55,7 +61,6 @@ from tagstudio.qt.app_settings import (
MAX_CACHED_THUMB_RES,
MIN_CACHED_THUMB_RES,
AppSettings,
Theme,
)
from tagstudio.qt.cache_manager import CacheManager
from tagstudio.qt.resource_manager import ResourceManager
@@ -99,13 +104,13 @@ class FileRenderer:
ext = url.suffix.lower()
types: set[MediaTypeOld] = MediaCategories.get_types(ext, mime_fallback=True)
# Manual icon overrides.
if ext in {".gif", ".vtf"}:
return MediaTypeOld.IMAGE
elif ext in {".dll", ".pyc", ".o", ".dylib"}:
return MediaTypeOld.PROGRAM
elif ext in {".mscz"}: # noqa: SIM114
return MediaTypeOld.TEXT
# # Manual icon overrides.
# if ext in {".gif", ".vtf"}:
# return MediaTypeOld.IMAGE
# elif ext in {".dll", ".pyc", ".o", ".dylib"}:
# return MediaTypeOld.PROGRAM
# elif ext in {".mscz"}: # noqa: SIM114
# return MediaTypeOld.TEXT
# Loop though the specific (non-IANA) categories and return the string
# name of the first matching category found.
@@ -766,17 +771,82 @@ class FileRenderer:
# Ordered groups of file renderers.
# A file extension is rendered with the first group it's found in.
# TODO: Dynamically import these from the renderers/ directory at runtime,
# And allow user-created ones from an external directory.
previews: list[type[BasePreview]] = [
# ArchivePreview,
# AudioPreview,
BlenderPreview,
# ClipStudioPaintPreview,
CodePreview,
# EbookPreview,
# FontPreview,
# IWorkPreview,
# KritaPreview,
# MediBangPaintPreview,
# PaintDotNetPreview,
# RasterImagePreview,
# RawImagePreview,
TextPreview,
# VectorImagePreview,
]
if filepath and filepath.is_file():
try:
ext = filepath.suffix.lower() if filepath.suffix else filepath.stem.lower()
for preview in previews:
media_type: MediaTypeGroup = getattr(MediaTypes, preview.media_type_name)
if media_type.contains(ext, Context.RENDER):
logger.info(f"{ext} in: {media_type.renderable}")
image = preview.render(
filepath=filepath,
theme=theme,
size=(scaled_size, scaled_size),
dpi_scale=dpi_scale,
)
break
if image:
image = self._resize_image(image, (scaled_size, scaled_size))
if cache_filename and is_savable_type and image and cache:
cache.save_image(image, cache_filename, mode="RGBA")
except Exception as e:
logger.error("[FileRenderer] Couldn't render thumbnail", filepath=filepath, error=e)
image = None
return image
# -------------------------------- new old
render_groups: list[tuple[MediaTypeGroup, Callable[..., Image.Image | None]]] = [
(MediaTypes.raw_image, partial(raw_image_thumb, filepath)),
(MediaTypes.binary, partial(raster_image_thumb, filepath)),
(MediaTypes.python, partial(text_thumb, filepath)),
(MediaTypes.pdf, partial(pdf_thumb, filepath, scaled_size)),
(MediaTypes.raster_image, partial(raster_image_thumb, filepath)),
(MediaTypes.vector, partial(vector_image_thumb, filepath, scaled_size)),
(MediaTypes.code_types, partial(code_thumb, filepath)),
(MediaTypes.plaintext, partial(text_thumb, filepath)),
(MediaTypes.vector_image, partial(vector_image_thumb, filepath, scaled_size)),
(
getattr(MediaTypes, CodePreview.media_type_name),
partial(CodePreview.render, filepath),
),
(
getattr(MediaTypes, TextPreview.media_type_name),
partial(TextPreview.render, filepath),
),
(MediaTypes.audio, partial(audio_thumb, filepath, scaled_size, dpi_scale)),
(MediaTypes.video, partial(video_thumb, filepath)),
(
MediaTypes.font,
partial(font_small_thumb if is_thumb else font_full_preview, filepath, scaled_size),
),
(MediaTypes.archive, partial(archive_thumb, filepath)),
(MediaTypes.pdf, partial(pdf_thumb, filepath, scaled_size)),
(MediaTypes.ebook, partial(epub_thumb, filepath)),
(MediaTypes.iwork, partial(apple_embedded_thumb, filepath)),
(MediaTypes.blender, partial(_blender_thumb, filepath)),
(MediaTypes.krita, partial(krita_thumb, filepath)),
(MediaTypes.clip_studio_paint, partial(clip_studio_thumb, filepath)),
(MediaTypes.paint_dot_net, partial(paint_dot_net_thumb, filepath)),
(MediaTypes.medibang_paint, partial(medibang_paint_thumb, filepath)),
(MediaTypes.binary, partial(raster_image_thumb, filepath)),
]
if filepath and filepath.is_file():
try:
@@ -791,6 +861,9 @@ class FileRenderer:
# TODO: Differentiate between album art and waveform
image = self._apply_overlay_color(image, UiColor.GREEN, theme)
is_savable_type = False
elif image and ext in MediaTypes.font.renderable:
# TODO: Differentiate between ful preview and small preview
image = self._apply_overlay_color(image, UiColor.BLUE, theme)
break
@@ -821,6 +894,8 @@ class FileRenderer:
return image
# ---------- old old
if filepath and filepath.is_file():
try:
ext: str = filepath.suffix.lower() if filepath.suffix else filepath.stem.lower()
@@ -854,7 +929,7 @@ class FileRenderer:
):
image = raw_image_thumb(filepath)
# Vector Images ----------------------------------------------------------------
elif ext in MediaTypes.vector.renderable:
elif ext in MediaTypes.vector_image.renderable:
image = vector_image_thumb(filepath, scaled_size)
# EXR Images -------------------------------------------------------------------
elif ext in [".exr"]:
@@ -912,7 +987,7 @@ class FileRenderer:
elif MediaCategories.is_ext_in_category(
ext, MediaCategories.BLENDER_TYPES, mime_fallback=True
):
image = blender_thumb(filepath)
image = _blender_thumb(filepath)
# PDF ==========================================================
elif MediaCategories.is_ext_in_category(
ext, MediaCategories.PDF_TYPES, mime_fallback=True
+3 -3
View File
@@ -60,7 +60,7 @@ class TarFile:
self.tar.__exit__(*args)
def open_archive(filepath: Path, ext: str = "") -> Archive:
def open_archive(filepath: Path) -> Archive:
"""Open an archive with its corresponding archiver.
Args:
@@ -70,6 +70,7 @@ def open_archive(filepath: Path, ext: str = "") -> Archive:
Returns:
Archive: The opened archive.
"""
ext = filepath.suffix.lower()
archiver: type[Archive] = zipfile.ZipFile
if ext in {".7z", ".cb7", ".s7z"}:
archiver = SevenZipFile
@@ -101,7 +102,6 @@ def first_image_in_archive(archive: Archive) -> Image.Image | None:
def archive_thumb(
filepath: Path,
image_names: list[Path] | list[str] | None = None,
ext: str = "",
) -> Image.Image | None:
"""Extract an embedded preview image from an archive.
@@ -114,7 +114,7 @@ def archive_thumb(
Image: The first image found in the archive.
"""
try:
with open_archive(filepath, ext) as archive:
with open_archive(filepath) as archive:
# If no list of image names to search for was provided, default to the first image.
if not image_names:
return first_image_in_archive(archive)
+24 -11
View File
@@ -3,32 +3,45 @@
from pathlib import Path
from typing import override
import structlog
from PIL import Image
from PySide6.QtCore import Qt
from PySide6.QtGui import QGuiApplication
from PIL.Image import Image, new
from tagstudio.core.enums import Theme
from tagstudio.previews.base_preview import BasePreview
from tagstudio.previews.vendored.blender_thumbnailer import blend_thumb
logger = structlog.get_logger(__name__)
def blender_thumb(filepath: Path) -> Image.Image | None:
class BlenderPreview(BasePreview):
media_type_name = "blender"
@override
@classmethod
def render(
cls,
filepath: Path,
theme: Theme,
size: tuple[int, int],
dpi_scale: float,
) -> Image | None:
return _blender_thumb(filepath, theme)
def _blender_thumb(filepath: Path, theme: Theme) -> Image | None:
"""Get an emended thumbnail from a Blender file, if a thumbnail is present.
Args:
filepath (Path): The path of the file.
theme (Theme): The system color theme.
"""
bg_color: str = (
"#1e1e1e"
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else "#FFFFFF"
)
im: Image.Image | None = None
bg_color: str = "#1e1e1e" if theme == Theme.DARK else "#FFFFFF"
im: Image | None = None
try:
if (blend_image := blend_thumb(str(filepath))) is not None:
bg = Image.new("RGB", blend_image.size, color=bg_color)
bg = new("RGB", blend_image.size, color=bg_color)
bg.paste(blend_image, mask=blend_image.getchannel(3))
im = bg
else:
+105
View File
@@ -0,0 +1,105 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: MIT
from pathlib import Path
from typing import override
import structlog
from PIL.Image import (
Image,
)
from pygments.style import Style
from pygments.token import (
Comment,
Error,
Generic,
Keyword,
Literal,
Name,
Number,
Operator,
Other,
Punctuation,
String,
Text,
Whitespace,
)
from tagstudio.core.enums import Theme
from tagstudio.previews.base_preview import BasePreview
from tagstudio.previews.renderers.text import text_thumb
logger = structlog.get_logger(__name__)
class CodeStyle(Style):
background = "#111111"
foreground = "#f8f8f2"
selection = "#44475a"
comment = "#6272a4"
cyan = "#8be9fd"
green = "#50fa7b"
orange = "#ffb86c"
pink = "#ff79c6"
purple = "#bd93f9"
red = "#ff5555"
yellow = "#f1fa8c"
deletion = "#8b080b"
background_color = background
highlight_color = selection
line_number_color = yellow
line_number_background_color = selection
line_number_special_color = green
line_number_special_background_color = comment
styles = {
Whitespace: foreground,
Comment: comment,
Comment.Preproc: pink,
Generic: foreground,
Generic.Deleted: deletion,
Generic.Emph: "underline",
Generic.Heading: "bold",
Generic.Inserted: "bold",
Generic.Output: selection,
Generic.EmphStrong: "underline",
Generic.Subheading: "bold",
Error: foreground,
Keyword: pink,
Keyword.Constant: pink,
Keyword.Declaration: cyan + " italic",
Keyword.Type: cyan,
Literal: foreground,
Name: foreground,
Name.Attribute: green,
Name.Builtin: cyan + " italic",
Name.Builtin.Pseudo: foreground,
Name.Class: green,
Name.Function: green,
Name.Label: cyan + " italic",
Name.Tag: pink,
Name.Variable: cyan + " italic",
Number: orange,
Operator: pink,
Other: foreground,
Punctuation: foreground,
String: purple,
Text: foreground,
}
class CodePreview(BasePreview):
media_type_name = "code"
@override
@classmethod
def render(
cls,
filepath: Path,
theme: Theme,
size: tuple[int, int],
dpi_scale: float,
) -> Image | None:
return text_thumb(filepath, size, CodeStyle)
+2 -2
View File
@@ -18,7 +18,7 @@ from tagstudio.previews.renderers.raster_image import image_from_bytes
logger = structlog.get_logger(__name__)
def epub_thumb(filepath: Path, ext: str) -> Image.Image | None:
def epub_thumb(filepath: Path) -> Image.Image | None:
"""Extracts the cover specified by ComicInfo.xml or first image found in the ePub file.
Args:
@@ -31,7 +31,7 @@ def epub_thumb(filepath: Path, ext: str) -> Image.Image | None:
"""
im: Image.Image | None = None
try:
with open_archive(filepath, ext) as archive:
with open_archive(filepath) as archive:
if "ComicInfo.xml" in archive.namelist():
comic_info = ET.fromstring(archive.read("ComicInfo.xml"))
im = _cover_from_comic_info(archive, comic_info, "FrontCover")
+144 -58
View File
@@ -1,86 +1,172 @@
# SPDX-FileCopyrightText: (c) TagStudio Contributors
# SPDX-License-Identifier: GPL-3.0-only
# SPDX-License-Identifier: MIT
import io
import textwrap
from math import ceil
from pathlib import Path
from typing import override
import cv2
import structlog
from PIL import Image, ImageDraw, UnidentifiedImageError
from PIL.Image import DecompressionBombError
from PySide6.QtCore import Qt
from PySide6.QtGui import QGuiApplication
from PIL import ImageFont, UnidentifiedImageError
from PIL.Image import (
DecompressionBombError,
Image,
Resampling,
)
from PIL.Image import (
new as new_image,
)
from PIL.Image import (
open as open_image,
)
from pygments import highlight
from pygments.formatters import ImageFormatter
from pygments.lexers import PythonLexer
from pygments.style import Style
from pygments.token import (
Comment,
Error,
Generic,
Keyword,
Literal,
Name,
Number,
Operator,
Other,
Punctuation,
String,
Text,
)
from tagstudio.core.enums import Theme
from tagstudio.core.utils.encoding import detect_char_encoding
from tagstudio.previews.base_preview import BasePreview
logger = structlog.get_logger(__name__)
def text_thumb(filepath: Path) -> Image.Image | None:
class TextLightStyle(Style):
background = "#FFFFFF"
foreground = "#111111"
background_color = background
styles = {
Generic: foreground,
Text: foreground,
Literal: foreground,
String: foreground,
}
class TextDarkStyle(Style):
background = "#1e1e1e"
foreground = "#FFFFFF"
background_color = background
styles = {
Generic: foreground,
Text: foreground,
Literal: foreground,
String: foreground,
Comment: foreground,
Error: foreground,
Keyword: foreground,
Name: foreground,
Number: foreground,
Operator: foreground,
Other: foreground,
Punctuation: foreground,
}
# text_light_style = TextLightStyle()
# text_dark_style = TextDarkStyle()
class TextPreview(BasePreview):
media_type_name = "plaintext"
font = ImageFont.load_default(20)
@override
@classmethod
def render(
cls,
filepath: Path,
theme: Theme,
size: tuple[int, int],
dpi_scale: float,
) -> Image | None:
return text_thumb(
filepath=filepath,
size=size,
style=TextDarkStyle if theme == Theme.DARK else TextLightStyle,
)
@staticmethod
def text_thumb(
filepath: Path,
size: tuple[int, int],
style: type[Style],
) -> Image | None:
"""Render a thumbnail for a plaintext file.
Args:
filepath (Path): The path of the file.
size (str): The final size for the image.
style (str): The pygments style class to use.
"""
im: Image.Image | None = None
bg_color: str = (
"#1e1e1e"
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else "#FFFFFF"
)
fg_color: str = (
"#FFFFFF"
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else "#111111"
)
im: Image | None = None
try:
encoding = detect_char_encoding(filepath)
encoding: str = detect_char_encoding(filepath) or "UTF-8"
with open(filepath, encoding=encoding) as text_file:
text = text_file.read(256)
bg = Image.new("RGB", (256, 256), color=bg_color)
draw = ImageDraw.Draw(bg)
draw.text((16, 16), text, fill=fg_color)
im = bg
text = text_file.read(1024)
wrapped_text = "\n".join(
"\n".join(textwrap.wrap(line, width=40)) for line in text.splitlines()
)
# TODO: Get this path from the ResourceManager, when that can handle fonts.
font_path = str(
Path(__file__).parents[2] / "resources/fonts/JetBrainsMono/JetBrainsMono.ttf"
)
# logger.info(font_path)
image_bytes = highlight(
wrapped_text,
PythonLexer(),
ImageFormatter(
encoding=encoding,
font_name=font_path,
font_size=32,
line_numbers=False,
style=style,
image_pad=48,
),
)
im_text = open_image(io.BytesIO(image_bytes))
ratio_w = size[0] / im_text.width
im_text = Image.resize(
im_text,
(ceil(im_text.width * ratio_w), ceil(im_text.height * ratio_w)),
Resampling.BILINEAR,
)
bg = new_image("RGB", size, color=style.background_color)
Image.paste(bg, im_text, (0, 0))
return bg
except (
UnidentifiedImageError,
cv2.error,
DecompressionBombError,
UnicodeDecodeError,
OSError,
FileNotFoundError,
) as e:
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
return im
def code_thumb(filepath: Path) -> Image.Image | None:
"""Render a thumbnail for a plaintext file.
Args:
filepath (Path): The path of the file.
"""
im: Image.Image | None = None
bg_color: str = "#000000"
fg_color: str = "#00FF00"
try:
encoding = detect_char_encoding(filepath)
with open(filepath, encoding=encoding) as text_file:
text = text_file.read(256)
bg = Image.new("RGB", (256, 256), color=bg_color)
draw = ImageDraw.Draw(bg)
draw.text((16, 16), text, fill=fg_color)
im = bg
except (
UnidentifiedImageError,
cv2.error,
DecompressionBombError,
UnicodeDecodeError,
OSError,
FileNotFoundError,
) as e:
logger.error("Couldn't render thumbnail", filepath=filepath, error=type(e).__name__)
logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
return im
+2 -9
View File
@@ -4,7 +4,7 @@
import platform
from datetime import datetime
from enum import Enum, IntEnum, StrEnum
from enum import Enum, StrEnum
from pathlib import Path
from typing import override
@@ -12,7 +12,7 @@ import structlog
import toml
from pydantic import BaseModel, Field
from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption
from tagstudio.core.enums import ShowFilepathOption, TagClickActionOption, Theme
logger = structlog.get_logger(__name__)
@@ -32,13 +32,6 @@ MAX_CACHED_THUMB_RES = 1024 # Pixels
DEFAULT_CACHED_THUMB_RES = 256 # Pixels
class Theme(IntEnum):
DARK = 0
LIGHT = 1
SYSTEM = 2
DEFAULT = SYSTEM
class Splash(StrEnum):
DEFAULT = "default"
RANDOM = "random"
+3 -3
View File
@@ -64,8 +64,8 @@ class ProgressWidget(QWidget):
self.show()
r = CustomRunnable(lambda: iterator.run())
r.done.connect(
runnable = CustomRunnable(lambda: iterator.run())
runnable.done.connect(
lambda: (self.hide(), self.deleteLater(), [callback() for callback in done_callbacks])
)
QThreadPool.globalInstance().start(r)
QThreadPool.globalInstance().start(runnable)
+3 -3
View File
@@ -22,7 +22,7 @@ from PySide6.QtWidgets import (
)
from tagstudio.core.constants import RESERVED_NAMESPACE_PREFIX
from tagstudio.core.enums import Theme
from tagstudio.core.enums import ThemePalette
from tagstudio.core.utils.types import unwrap
from tagstudio.i18n.translations import Translations
from tagstudio.qt.controllers.modal import Modal
@@ -56,9 +56,9 @@ class TagColorManager(QWidget):
self.root_layout.setContentsMargins(6, 6, 6, 6)
panel_bg_color = (
Theme.COLOR_BG_DARK.value
ThemePalette.COLOR_BG_DARK.value
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else Theme.COLOR_BG_LIGHT.value
else ThemePalette.COLOR_BG_LIGHT.value
)
self.title_label = QLabel()
+23 -23
View File
@@ -5,7 +5,7 @@
from PySide6.QtCore import Qt
from PySide6.QtGui import QColor, QGuiApplication
from tagstudio.core.enums import Theme
from tagstudio.core.enums import ThemePalette
from tagstudio.core.library.alchemy.enums import TagColorEnum
from tagstudio.core.library.alchemy.models import Tag
from tagstudio.qt.views.styles.palette import (
@@ -55,14 +55,14 @@ def button_style() -> str:
"""Style used for common QPushButtons."""
return f"""
QPushButton{{
background-color: {Theme.COLOR_BG.value};
background-color: {ThemePalette.COLOR_BG.value};
border-radius: 6px;
font-weight: 500;
text-align: center;
padding: 0px 12px;
}}
QPushButton::hover{{
background-color: {Theme.COLOR_HOVER.value};
background-color: {ThemePalette.COLOR_HOVER.value};
border-style: solid;
border-width: 2px;
border-color: {get_ui_color(ColorType.BORDER, UiColor.THEME_DARK)};
@@ -84,7 +84,7 @@ def button_style() -> str:
padding: 0px 8px;
}}
QPushButton::disabled{{
background-color: {Theme.COLOR_DISABLED_BG.value};
background-color: {ThemePalette.COLOR_DISABLED_BG.value};
}}
"""
@@ -92,9 +92,9 @@ def button_style() -> str:
def line_edit_style_main() -> str:
"""Style used for common QLineEdits."""
bg_color = (
Theme.COLOR_BG_DARK.value
ThemePalette.COLOR_BG_DARK.value
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else Theme.COLOR_BG_LIGHT.value
else ThemePalette.COLOR_BG_LIGHT.value
)
return f"""
@@ -118,7 +118,7 @@ def line_edit_style_main() -> str:
padding: 0px 2px;
}}
QLineEdit::disabled{{
background-color: {Theme.COLOR_DISABLED_BG.value};
background-color: {ThemePalette.COLOR_DISABLED_BG.value};
}}
"""
@@ -259,10 +259,10 @@ def container_style() -> str:
border-radius: 4px;
}}
QWidget#fieldContainer::hover{{
background-color: {Theme.COLOR_HOVER.value};
background-color: {ThemePalette.COLOR_HOVER.value};
}}
QWidget#fieldContainer::pressed{{
background-color: {Theme.COLOR_PRESSED.value};
background-color: {ThemePalette.COLOR_PRESSED.value};
}}
"""
@@ -271,9 +271,9 @@ def form_content_style() -> str:
return f"""
QLabel{{
background-color: {
Theme.COLOR_BG.value
ThemePalette.COLOR_BG.value
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else Theme.COLOR_BG_LIGHT.value
else ThemePalette.COLOR_BG_LIGHT.value
};
border-radius: 3px;
font-weight: 500;
@@ -340,9 +340,9 @@ def list_button_style(
def properties_style() -> str:
"""Style used for small labels such as file properties."""
label_bg_color = (
Theme.COLOR_BG_DARK.value
ThemePalette.COLOR_BG_DARK.value
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else Theme.COLOR_DARK_LABEL.value
else ThemePalette.COLOR_DARK_LABEL.value
)
return f"""
@@ -447,9 +447,9 @@ def title_line_edit_style() -> str:
def inset_container_style(object_name: str = "") -> str:
"""Used for darkened inset areas."""
bg_color = (
Theme.COLOR_BG_DARK.value
ThemePalette.COLOR_BG_DARK.value
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else Theme.COLOR_BG_LIGHT.value
else ThemePalette.COLOR_BG_LIGHT.value
)
return f"""
@@ -464,9 +464,9 @@ def inset_container_style(object_name: str = "") -> str:
def autofill_scroll_top_style(object_name: str = "") -> str:
"""Used autofill lists positioned on top of line edits."""
bg_color = (
Theme.COLOR_BG_DARK.value
ThemePalette.COLOR_BG_DARK.value
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else Theme.COLOR_BG_LIGHT.value
else ThemePalette.COLOR_BG_LIGHT.value
)
return f"""
@@ -482,9 +482,9 @@ def autofill_scroll_top_style(object_name: str = "") -> str:
def autofill_scroll_top_focus_style(object_name: str = "") -> str:
"""Used autofill lists positioned on top of line edits."""
bg_color = (
Theme.COLOR_BG_DARK.value
ThemePalette.COLOR_BG_DARK.value
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else Theme.COLOR_BG_LIGHT.value
else ThemePalette.COLOR_BG_LIGHT.value
)
return f"""
@@ -502,9 +502,9 @@ def autofill_scroll_top_focus_style(object_name: str = "") -> str:
def autofill_line_edit_style() -> str:
"""Used for QLineEdits."""
bg_color = (
Theme.COLOR_BG_DARK.value
ThemePalette.COLOR_BG_DARK.value
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else Theme.COLOR_BG_LIGHT.value
else ThemePalette.COLOR_BG_LIGHT.value
)
return f"""
@@ -525,9 +525,9 @@ def autofill_line_edit_style() -> str:
def autofill_line_edit_top_style() -> str:
"""Used for QLineEdits when there's a top autofill section present."""
bg_color = (
Theme.COLOR_BG_DARK.value
ThemePalette.COLOR_BG_DARK.value
if QGuiApplication.styleHints().colorScheme() is Qt.ColorScheme.Dark
else Theme.COLOR_BG_LIGHT.value
else ThemePalette.COLOR_BG_LIGHT.value
)
return f"""
@@ -1,2 +0,0 @@
SPDX-FileCopyrightText: 2019 The Oxanium Project Authors (https://github.com/sevmeyer/oxanium)
SPDX-License-Identifier: OFL-1.1
+1 -1
View File
@@ -9,7 +9,7 @@ from unittest.mock import Mock
import pytest
from PySide6.QtCore import Qt
from tagstudio.qt.app_settings import Theme
from tagstudio.core.enums import Theme
@pytest.mark.parametrize(