Compare commits

..

5 Commits

Author SHA1 Message Date
Travis Abendshien
cbb2d8d342 chore: bump version to v9.5.7 2026-05-05 16:12:49 -07:00
Travis Abendshien
735bf072b0 ci: bump macOS build targets to 14/15 2026-05-05 16:03:06 -07:00
TheBobBobs
6c8d800598 perf: bulk insert/delete tag_entries (#1296)
* perf: Bulk insert/delete tag_entries

* add type annotations
2026-04-29 01:30:57 -07:00
Juozas Skarbalius
695b3923c9 fix(ui): system theme fix (#1328)
* fix: introduce system theme fix in Mac os

* fix: formatting
2026-04-28 16:45:02 -07:00
Weblate (bot)
eacb93728b translations: update from Hosted Weblate (#1301)
* Translated using Weblate (German)

Currently translated at 100.0% (362 of 362 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Patrick Schüle <patrickschuele90@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/de/
Translation: TagStudio/Strings

* Translated using Weblate (Italian)

Currently translated at 100.0% (362 of 362 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Octavian <223219150+EdelFlosWeiss@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/it/
Translation: TagStudio/Strings

* Translated using Weblate (Dutch)

Currently translated at 37.5% (136 of 362 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Kristof Hermans <hermans1986@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/nl/
Translation: TagStudio/Strings

* Translated using Weblate (Finnish)

Currently translated at 87.0% (315 of 362 strings)

Translated using Weblate (Finnish)

Currently translated at 87.0% (315 of 362 strings)

Translated using Weblate (Finnish)

Currently translated at 87.0% (315 of 362 strings)

Added translation using Weblate (Finnish)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jonne Saloranta <saloranta.jonne@gmail.com>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/fi/
Translation: TagStudio/Strings

* Translated using Weblate (Toki Pona)

Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 96.1% (348 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 95.0% (344 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 95.0% (344 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 95.0% (344 of 362 strings)

Translated using Weblate (Toki Pona)

Currently translated at 95.0% (344 of 362 strings)

Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
Co-authored-by: Cyborus <cyborus@disroot.org>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/tagstudio/strings/tok/
Translation: TagStudio/Strings

---------

Co-authored-by: Patrick Schüle <patrickschuele90@gmail.com>
Co-authored-by: Octavian <223219150+EdelFlosWeiss@users.noreply.github.com>
Co-authored-by: Kristof Hermans <hermans1986@gmail.com>
Co-authored-by: Jonne Saloranta <saloranta.jonne@gmail.com>
Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
Co-authored-by: Bee Crankson <ProfB.crankson@gmail.com>
Co-authored-by: Cyborus <cyborus@disroot.org>
2026-04-28 13:04:58 -07:00
8 changed files with 101 additions and 47 deletions

View File

@@ -48,17 +48,17 @@ jobs:
macos:
strategy:
matrix:
os-version: ['13', '14']
os-version: ['14', '15']
include:
- os-version: '13'
arch: x86_64
- os-version: '14'
arch: x86_64
- os-version: '15'
arch: aarch64
runs-on: macos-${{ matrix.os-version }}
env:
# INFO: Even though we run on 13, target towards compatibility
# INFO: Even though we run on 14, target towards compatibility
MACOSX_DEPLOYMENT_TARGET: '11.0'
steps:

View File

@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
[project]
name = "TagStudio"
description = "A User-Focused Photo & File Management System."
version = "9.5.6"
version = "9.5.7"
license = "GPL-3.0-only"
readme = "README.md"
requires-python = ">=3.12,<3.13"

View File

@@ -2,7 +2,7 @@
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
VERSION: str = "9.5.6" # Major.Minor.Patch
VERSION: str = "9.5.7" # Major.Minor.Patch
VERSION_BRANCH: str = "" # Usually "" or "Pre-Release"
# The folder & file names where TagStudio keeps its data relative to a library.

View File

@@ -42,6 +42,7 @@ from sqlalchemy import (
text,
update,
)
from sqlalchemy.dialects import sqlite
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import (
InstanceState,
@@ -1478,7 +1479,7 @@ class Library:
return None
def add_tags_to_entries(
self, entry_ids: int | list[int] | set[int], tag_ids: int | list[int] | set[int]
self, entry_ids: int | Iterable[int], tag_ids: int | Iterable[int]
) -> int:
"""Add one or more tags to one or more entries.
@@ -1494,45 +1495,57 @@ class Library:
entry_ids_ = [entry_ids] if isinstance(entry_ids, int) else entry_ids
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else tag_ids
values: list[tuple[int, int]] = []
for tag_id in tag_ids_:
values.extend((tag_id, entry_id) for entry_id in entry_ids_)
with Session(self.engine, expire_on_commit=False) as session:
for tag_id in tag_ids_:
for entry_id in entry_ids_:
try:
session.add(TagEntry(tag_id=tag_id, entry_id=entry_id))
total_added += 1
session.commit()
except IntegrityError:
session.rollback()
for sub_list in [
values[i : i + MAX_SQL_VARIABLES // 2]
for i in range(0, len(values), MAX_SQL_VARIABLES // 2)
]:
stmt = (
sqlite.insert(TagEntry)
.values(sub_list)
.on_conflict_do_nothing()
.returning(TagEntry)
)
added = session.scalars(stmt).all()
total_added += len(added)
session.commit()
return total_added
def remove_tags_from_entries(
self, entry_ids: int | list[int] | set[int], tag_ids: int | list[int] | set[int]
) -> bool:
self, entry_ids: int | Iterable[int], tag_ids: int | Iterable[int]
):
"""Remove one or more tags from one or more entries."""
entry_ids_ = [entry_ids] if isinstance(entry_ids, int) else entry_ids
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else tag_ids
logger.info(
"[Library][remove_tags_from_entries]",
entry_ids=entry_ids,
tag_ids=tag_ids,
)
entry_ids_ = [entry_ids] if isinstance(entry_ids, int) else list(entry_ids)
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else list(tag_ids)
with Session(self.engine, expire_on_commit=False) as session:
try:
for tag_id in tag_ids_:
for entry_id in entry_ids_:
tag_entry = session.scalars(
select(TagEntry).where(
and_(
TagEntry.tag_id == tag_id,
TagEntry.entry_id == entry_id,
)
)
).first()
if tag_entry:
session.delete(tag_entry)
session.flush()
session.commit()
return True
except IntegrityError as e:
logger.error(e)
session.rollback()
return False
for tags_sub_list in [
tag_ids_[i : i + MAX_SQL_VARIABLES // 2]
for i in range(0, len(tag_ids_), MAX_SQL_VARIABLES // 2)
]:
for entries_sub_list in [
entry_ids_[i : i + MAX_SQL_VARIABLES // 2]
for i in range(0, len(entry_ids_), MAX_SQL_VARIABLES // 2)
]:
stmt = delete(TagEntry).where(
and_(
TagEntry.tag_id.in_(tags_sub_list),
TagEntry.entry_id.in_(entries_sub_list),
)
)
session.execute(stmt)
session.commit()
def add_color(self, color_group: TagColorGroup) -> TagColorGroup | None:
with Session(self.engine, expire_on_commit=False) as session:

View File

@@ -306,13 +306,13 @@ class QtDriver(DriverMixin, QObject):
sys.argv += ["-platform", "windows:darkmode=2"]
self.app = QApplication(sys.argv)
self.app.setStyle("Fusion")
if self.settings.theme == Theme.SYSTEM:
# TODO: detect theme instead of always setting dark
# Apply theme color if explicitly set to DARK or LIGHT by the user.
# For SYSTEM, we let Qt decide based on OS theme.
if self.settings.theme == Theme.DARK:
self.app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
else:
self.app.styleHints().setColorScheme(
Qt.ColorScheme.Dark if self.settings.theme == Theme.DARK else Qt.ColorScheme.Light
)
elif self.settings.theme == Theme.LIGHT:
self.app.styleHints().setColorScheme(Qt.ColorScheme.Light)
if (
platform.system() == "Darwin" or platform.system() == "Windows"

View File

@@ -29,7 +29,7 @@
"drop_import.duplicates_choice.singular": "Seuraava tiedosto vastaa tiedostopolkua, joka on jo olemassa kirjastossa.",
"drop_import.progress.label.initial": "Tuo uusi tiedosto...",
"drop_import.progress.label.plural": "Tuodaan uusia tiedostoja...\n{count} Tiedostoa tuotu.{suffix}",
"drop_import.progress.label.singular": "Uusien tiedostojen tuonti...\n1 tiedosto tuotu",
"drop_import.progress.label.singular": "Uusien tiedostojen tuonti...\n1 tiedosto tuotu {suffix}",
"drop_import.progress.window_title": "Tuo tiedostoja",
"drop_import.title": "Ristiriitaisia tiedosto(ja)",
"edit.color_manager": "Hallitse tunnisteiden värejä",
@@ -54,7 +54,7 @@
"entries.mirror.label": "Peilataan {idx}/{total} merkintää...",
"entries.mirror.title": "Peilataan merkintöjä",
"entries.mirror.window_title": "Peilaa merkinnät",
"entries.remove.plural.confirm": "Haluatko varmasti poistaa nämä <b>{count</b> merkintää kirjastostasi? Levyllä olevia tiedostoja ei poisteta.",
"entries.remove.plural.confirm": "Haluatko varmasti poistaa nämä <b>{count}</b> merkintää kirjastostasi? Levyllä olevia tiedostoja ei poisteta.",
"entries.remove.singular.confirm": "Haluatko varmasti poistaa tämän merkinnän kirjastostasi? Levyllä olevia tiedostoja ei poisteta.",
"entries.running.dialog.new_entries": "Lisätään {total} uutta tiedosto merkintää...",
"entries.running.dialog.title": "Lisätään uudet tiedosto merkinnät",

View File

@@ -1,5 +1,6 @@
{
"about.config_path": "Configuratie Pad",
"about.description": "TagStudio is een applicatie om foto's en bestanden te organiseren, met een onderliggend systeem gebaseerd op tags, dat zich focust op vrijheid en flexibiliteit bieden aan de gebruiker. Geen gepatenteerde programma's of bestandsformaten, geen zeëen aan sidecarbestanden, en je bestaande bestandsstructuur wordt niet volledig overhoop gegooid.",
"about.documentation": "Documentatie",
"about.license": "Licentie",
"about.module.found": "Gevonden",
@@ -7,6 +8,7 @@
"about.website": "Website",
"app.git": "Git Commit",
"app.pre_release": "Pre-Release",
"app.title": "{base_title} - Bibliotheek '{library_dir}'",
"color.color_border": "Gebruik Secundaire Kleur voor Rand",
"color.confirm_delete": "Weet u zeker dat u de kleur \"{color_name}\" wilt verwijderen?",
"color.delete": "Verwijder Label",
@@ -20,6 +22,9 @@
"color.title.no_color": "Geen Kleur",
"color_manager.title": "Beheer Label Kleuren",
"dependency.missing.title": "{dependency} Niet Gevonden",
"drop_import.description": "Volgende bestanden hebben een pad dat overeenkomt met een reeds bestaand pad in de bibliotheek",
"drop_import.duplicates_choice.plural": "De volgende {count} bestanden hebben een pad dat overeenkomt met een reeds bestaand pad in de bibliotheek.",
"drop_import.duplicates_choice.singular": "Volgend bestand heeft een pad dat overeenkomt met een reeds bestaand pad in de bibliotheek.",
"drop_import.progress.label.initial": "Nieuwe bestanden importeren…",
"drop_import.progress.label.plural": "Nieuwe bestanden importeren…\n{count} bestanden geïmporteerd.{suffix}",
"drop_import.progress.label.singular": "Nieuwe bestanden importeren…\n1 bestand geïmporteerd.{suffix}",
@@ -31,6 +36,7 @@
"edit.tag_manager": "Beheer Labels",
"entries.duplicate.merge": "Dubbele Vermeldingen Samenvoegen",
"entries.duplicate.merge.label": "Dubbele vermeldingen samenvoegen...",
"entries.duplicate.refresh": "Dubbele Invoer Vernieuwen",
"entries.tags": "Labels",
"field.copy": "Veld Kopiëren",
"field.edit": "Veld Aanpassen",

View File

@@ -0,0 +1,35 @@
# Copyright (C) 2025
# Licensed under the GPL-3.0 License.
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
"""Test theme handling in QtDriver, particularly the SYSTEM theme fix (issue #999)."""
from unittest.mock import Mock
import pytest
from PySide6.QtCore import Qt
from tagstudio.qt.global_settings import Theme
@pytest.mark.parametrize(
"theme,expected_call",
[
(Theme.DARK, Qt.ColorScheme.Dark),
(Theme.LIGHT, Qt.ColorScheme.Light),
(Theme.SYSTEM, None), # SYSTEM theme should NOT call setColorScheme
],
)
def test_theme_colorscheme_handling(theme: Theme, expected_call):
mock_style_hints = Mock()
if theme == Theme.DARK:
mock_style_hints.setColorScheme(Qt.ColorScheme.Dark)
elif theme == Theme.LIGHT:
mock_style_hints.setColorScheme(Qt.ColorScheme.Light)
if expected_call is None:
# SYSTEM theme should NOT call setColorScheme
mock_style_hints.setColorScheme.assert_not_called()
else:
mock_style_hints.setColorScheme.assert_called_once_with(expected_call)