mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-05-24 17:52:27 +00:00
Compare commits
5 Commits
v9.5.7
...
dd13273cdd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dd13273cdd | ||
|
|
690dfd01c3 | ||
|
|
2c19ea1878 | ||
|
|
484f458485 | ||
|
|
b8dfa7b040 |
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,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