mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-05-25 02:02:38 +00:00
Compare commits
4 Commits
v9.5.7
...
ffc2680bf7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ffc2680bf7 | ||
|
|
ddc1077e96 | ||
|
|
09c2d7e2b5 | ||
|
|
0ca5dc03e5 |
8
.github/workflows/release.yml
vendored
8
.github/workflows/release.yml
vendored
@@ -48,17 +48,17 @@ jobs:
|
||||
macos:
|
||||
strategy:
|
||||
matrix:
|
||||
os-version: ['14', '15']
|
||||
os-version: ['13', '14']
|
||||
include:
|
||||
- os-version: '14'
|
||||
- os-version: '13'
|
||||
arch: x86_64
|
||||
- os-version: '15'
|
||||
- os-version: '14'
|
||||
arch: aarch64
|
||||
|
||||
runs-on: macos-${{ matrix.os-version }}
|
||||
|
||||
env:
|
||||
# INFO: Even though we run on 14, target towards compatibility
|
||||
# INFO: Even though we run on 13, target towards compatibility
|
||||
MACOSX_DEPLOYMENT_TARGET: '11.0'
|
||||
|
||||
steps:
|
||||
|
||||
@@ -5,7 +5,7 @@ build-backend = "hatchling.build"
|
||||
[project]
|
||||
name = "TagStudio"
|
||||
description = "A User-Focused Photo & File Management System."
|
||||
version = "9.5.7"
|
||||
version = "9.5.6"
|
||||
license = "GPL-3.0-only"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12,<3.13"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
VERSION: str = "9.5.7" # Major.Minor.Patch
|
||||
VERSION: str = "9.5.6" # Major.Minor.Patch
|
||||
VERSION_BRANCH: str = "" # Usually "" or "Pre-Release"
|
||||
|
||||
# The folder & file names where TagStudio keeps its data relative to a library.
|
||||
|
||||
@@ -42,7 +42,6 @@ from sqlalchemy import (
|
||||
text,
|
||||
update,
|
||||
)
|
||||
from sqlalchemy.dialects import sqlite
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
from sqlalchemy.orm import (
|
||||
InstanceState,
|
||||
@@ -1479,7 +1478,7 @@ class Library:
|
||||
return None
|
||||
|
||||
def add_tags_to_entries(
|
||||
self, entry_ids: int | Iterable[int], tag_ids: int | Iterable[int]
|
||||
self, entry_ids: int | list[int] | set[int], tag_ids: int | list[int] | set[int]
|
||||
) -> int:
|
||||
"""Add one or more tags to one or more entries.
|
||||
|
||||
@@ -1495,57 +1494,45 @@ 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 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()
|
||||
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()
|
||||
|
||||
return total_added
|
||||
|
||||
def remove_tags_from_entries(
|
||||
self, entry_ids: int | Iterable[int], tag_ids: int | Iterable[int]
|
||||
):
|
||||
self, entry_ids: int | list[int] | set[int], tag_ids: int | list[int] | set[int]
|
||||
) -> bool:
|
||||
"""Remove one or more tags from one or more entries."""
|
||||
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)
|
||||
|
||||
entry_ids_ = [entry_ids] if isinstance(entry_ids, int) else entry_ids
|
||||
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else tag_ids
|
||||
with Session(self.engine, expire_on_commit=False) as session:
|
||||
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()
|
||||
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
|
||||
|
||||
def add_color(self, color_group: TagColorGroup) -> TagColorGroup | None:
|
||||
with Session(self.engine, expire_on_commit=False) as session:
|
||||
|
||||
@@ -306,13 +306,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
sys.argv += ["-platform", "windows:darkmode=2"]
|
||||
self.app = QApplication(sys.argv)
|
||||
self.app.setStyle("Fusion")
|
||||
|
||||
# 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:
|
||||
if self.settings.theme == Theme.SYSTEM:
|
||||
# TODO: detect theme instead of always setting dark
|
||||
self.app.styleHints().setColorScheme(Qt.ColorScheme.Dark)
|
||||
elif self.settings.theme == Theme.LIGHT:
|
||||
self.app.styleHints().setColorScheme(Qt.ColorScheme.Light)
|
||||
else:
|
||||
self.app.styleHints().setColorScheme(
|
||||
Qt.ColorScheme.Dark if self.settings.theme == Theme.DARK else Qt.ColorScheme.Light
|
||||
)
|
||||
|
||||
if (
|
||||
platform.system() == "Darwin" or platform.system() == "Windows"
|
||||
|
||||
@@ -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 {suffix}",
|
||||
"drop_import.progress.label.singular": "Uusien tiedostojen tuonti...\n1 tiedosto tuotu",
|
||||
"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",
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"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",
|
||||
@@ -8,7 +7,6 @@
|
||||
"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",
|
||||
@@ -22,9 +20,6 @@
|
||||
"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}",
|
||||
@@ -36,7 +31,6 @@
|
||||
"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",
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user