mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-17 06:58:24 +00:00
Merge branch 'main' into Alpha-v9.4
This commit is contained in:
@@ -163,3 +163,6 @@ TAG_COLORS = [
|
||||
"cool gray",
|
||||
"olive",
|
||||
]
|
||||
|
||||
TAG_FAVORITE = 1
|
||||
TAG_ARCHIVED = 0
|
||||
|
||||
@@ -24,3 +24,16 @@ class SearchMode(int, enum.Enum):
|
||||
|
||||
AND = 0
|
||||
OR = 1
|
||||
|
||||
|
||||
class FieldID(int, enum.Enum):
|
||||
TITLE = 0
|
||||
AUTHOR = 1
|
||||
ARTIST = 2
|
||||
DESCRIPTION = 4
|
||||
NOTES = 5
|
||||
TAGS = 6
|
||||
CONTENT_TAGS = 7
|
||||
META_TAGS = 8
|
||||
DATE_PUBLISHED = 14
|
||||
SOURCE = 21
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
"""The Library object and related methods for TagStudio."""
|
||||
|
||||
import datetime
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
@@ -18,6 +17,7 @@ from pathlib import Path
|
||||
from typing import cast, Generator
|
||||
from typing_extensions import Self
|
||||
|
||||
from src.core.enums import FieldID
|
||||
from src.core.json_typing import JsonCollation, JsonEntry, JsonLibary, JsonTag
|
||||
from src.core.utils.str import strip_punctuation
|
||||
from src.core.utils.web import strip_web_protocol
|
||||
@@ -80,7 +80,7 @@ class Entry:
|
||||
# self.word_count: int = None
|
||||
|
||||
def __str__(self) -> str:
|
||||
return f"\n{self.compressed_dict()}\n"
|
||||
return str(self.compressed_dict())
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return self.__str__()
|
||||
@@ -889,12 +889,12 @@ class Library:
|
||||
and "tagstudio_thumbs" not in f.parts
|
||||
and not f.is_dir()
|
||||
):
|
||||
if f.suffix not in self.ext_list and self.is_exclude_list:
|
||||
if f.suffix.lower() not in self.ext_list and self.is_exclude_list:
|
||||
self.dir_file_count += 1
|
||||
file = f.relative_to(self.library_dir)
|
||||
if file not in self.filename_to_entry_id_map:
|
||||
self.files_not_in_library.append(file)
|
||||
elif f.suffix in self.ext_list and not self.is_exclude_list:
|
||||
elif f.suffix.lower() in self.ext_list and not self.is_exclude_list:
|
||||
self.dir_file_count += 1
|
||||
file = f.relative_to(self.library_dir)
|
||||
try:
|
||||
@@ -1382,7 +1382,7 @@ class Library:
|
||||
# non_entry_count = 0
|
||||
# Iterate over all Entries =============================================================
|
||||
for entry in self.entries:
|
||||
allowed_ext: bool = entry.filename.suffix not in self.ext_list
|
||||
allowed_ext: bool = entry.filename.suffix.lower() not in self.ext_list
|
||||
# try:
|
||||
# entry: Entry = self.entries[self.file_to_library_index_map[self._source_filenames[i]]]
|
||||
# print(f'{entry}')
|
||||
@@ -1539,7 +1539,7 @@ class Library:
|
||||
else:
|
||||
for entry in self.entries:
|
||||
added = False
|
||||
allowed_ext = entry.filename.suffix not in self.ext_list
|
||||
allowed_ext = entry.filename.suffix.lower() not in self.ext_list
|
||||
if allowed_ext == self.is_exclude_list:
|
||||
for f in entry.fields:
|
||||
if self.get_field_attr(f, "type") == "collation":
|
||||
@@ -1948,48 +1948,44 @@ class Library:
|
||||
if data:
|
||||
# Add a Title Field if the data doesn't already exist.
|
||||
if data.get("title"):
|
||||
field_id = 0 # Title Field ID
|
||||
if not self.does_field_content_exist(entry_id, field_id, data["title"]):
|
||||
self.add_field_to_entry(entry_id, field_id)
|
||||
if not self.does_field_content_exist(
|
||||
entry_id, FieldID.TITLE, data["title"]
|
||||
):
|
||||
self.add_field_to_entry(entry_id, FieldID.TITLE)
|
||||
self.update_entry_field(entry_id, -1, data["title"], "replace")
|
||||
|
||||
# Add an Author Field if the data doesn't already exist.
|
||||
if data.get("author"):
|
||||
field_id = 1 # Author Field ID
|
||||
if not self.does_field_content_exist(
|
||||
entry_id, field_id, data["author"]
|
||||
entry_id, FieldID.AUTHOR, data["author"]
|
||||
):
|
||||
self.add_field_to_entry(entry_id, field_id)
|
||||
self.add_field_to_entry(entry_id, FieldID.AUTHOR)
|
||||
self.update_entry_field(entry_id, -1, data["author"], "replace")
|
||||
|
||||
# Add an Artist Field if the data doesn't already exist.
|
||||
if data.get("artist"):
|
||||
field_id = 2 # Artist Field ID
|
||||
if not self.does_field_content_exist(
|
||||
entry_id, field_id, data["artist"]
|
||||
entry_id, FieldID.ARTIST, data["artist"]
|
||||
):
|
||||
self.add_field_to_entry(entry_id, field_id)
|
||||
self.add_field_to_entry(entry_id, FieldID.ARTIST)
|
||||
self.update_entry_field(entry_id, -1, data["artist"], "replace")
|
||||
|
||||
# Add a Date Published Field if the data doesn't already exist.
|
||||
if data.get("date_published"):
|
||||
field_id = 14 # Date Published Field ID
|
||||
date = str(
|
||||
datetime.datetime.strptime(
|
||||
data["date_published"], "%Y-%m-%d %H:%M:%S"
|
||||
)
|
||||
)
|
||||
if not self.does_field_content_exist(entry_id, field_id, date):
|
||||
self.add_field_to_entry(entry_id, field_id)
|
||||
if not self.does_field_content_exist(
|
||||
entry_id, FieldID.DATE_PUBLISHED, date
|
||||
):
|
||||
self.add_field_to_entry(entry_id, FieldID.DATE_PUBLISHED)
|
||||
# entry = self.entries[entry_id]
|
||||
self.update_entry_field(entry_id, -1, date, "replace")
|
||||
|
||||
# Process String Tags if the data doesn't already exist.
|
||||
if data.get("tags"):
|
||||
tags_field_id = 6 # Tags Field ID
|
||||
content_tags_field_id = 7 # Content Tags Field ID
|
||||
meta_tags_field_id = 8 # Meta Tags Field ID
|
||||
notes_field_id = 5 # Notes Field ID
|
||||
tags: list[str] = data["tags"]
|
||||
# extra: list[str] = []
|
||||
# for tag in tags:
|
||||
@@ -2038,7 +2034,7 @@ class Library:
|
||||
# tag_field_indices = self.get_field_index_in_entry(
|
||||
# entry_index, tags_field_id)
|
||||
content_tags_field_indices = self.get_field_index_in_entry(
|
||||
self.get_entry(entry_id), content_tags_field_id
|
||||
self.get_entry(entry_id), FieldID.CONTENT_TAGS
|
||||
)
|
||||
# meta_tags_field_indices = self.get_field_index_in_entry(
|
||||
# entry_index, meta_tags_field_id)
|
||||
@@ -2055,45 +2051,40 @@ class Library:
|
||||
entry_id, priority_field_index, [matching[0]], "append"
|
||||
)
|
||||
else:
|
||||
self.add_field_to_entry(entry_id, content_tags_field_id)
|
||||
self.add_field_to_entry(entry_id, FieldID.CONTENT_TAGS)
|
||||
self.update_entry_field(
|
||||
entry_id, -1, [matching[0]], "append"
|
||||
)
|
||||
|
||||
# Add all original string tags as a note.
|
||||
str_tags = f"Original Tags: {tags}"
|
||||
if not self.does_field_content_exist(
|
||||
entry_id, notes_field_id, str_tags
|
||||
):
|
||||
self.add_field_to_entry(entry_id, notes_field_id)
|
||||
if not self.does_field_content_exist(entry_id, FieldID.NOTES, str_tags):
|
||||
self.add_field_to_entry(entry_id, FieldID.NOTES)
|
||||
self.update_entry_field(entry_id, -1, str_tags, "replace")
|
||||
|
||||
# Add a Description Field if the data doesn't already exist.
|
||||
if "description" in data.keys() and data["description"]:
|
||||
field_id = 4 # Description Field ID
|
||||
if data.get("description"):
|
||||
if not self.does_field_content_exist(
|
||||
entry_id, field_id, data["description"]
|
||||
entry_id, FieldID.DESCRIPTION, data["description"]
|
||||
):
|
||||
self.add_field_to_entry(entry_id, field_id)
|
||||
self.add_field_to_entry(entry_id, FieldID.DESCRIPTION)
|
||||
self.update_entry_field(
|
||||
entry_id, -1, data["description"], "replace"
|
||||
)
|
||||
if "content" in data.keys() and data["content"]:
|
||||
field_id = 4 # Description Field ID
|
||||
if data.get("content"):
|
||||
if not self.does_field_content_exist(
|
||||
entry_id, field_id, data["content"]
|
||||
entry_id, FieldID.DESCRIPTION, data["content"]
|
||||
):
|
||||
self.add_field_to_entry(entry_id, field_id)
|
||||
self.add_field_to_entry(entry_id, FieldID.DESCRIPTION)
|
||||
self.update_entry_field(entry_id, -1, data["content"], "replace")
|
||||
if "source" in data.keys() and data["source"]:
|
||||
field_id = 21 # Source Field ID
|
||||
if data.get("source"):
|
||||
for source in data["source"].split(" "):
|
||||
if source and source != " ":
|
||||
source = strip_web_protocol(string=source)
|
||||
if not self.does_field_content_exist(
|
||||
entry_id, field_id, source
|
||||
entry_id, FieldID.SOURCE, source
|
||||
):
|
||||
self.add_field_to_entry(entry_id, field_id)
|
||||
self.add_field_to_entry(entry_id, FieldID.SOURCE)
|
||||
self.update_entry_field(entry_id, -1, source, "replace")
|
||||
|
||||
def add_field_to_entry(self, entry_id: int, field_id: int) -> None:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
from enum import Enum
|
||||
|
||||
from src.core.library import Entry, Library
|
||||
from src.core.constants import TS_FOLDER_NAME, TEXT_FIELDS
|
||||
|
||||
16
tagstudio/src/qt/helpers/qbutton_wrapper.py
Normal file
16
tagstudio/src/qt/helpers/qbutton_wrapper.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
from PySide6.QtWidgets import QPushButton
|
||||
|
||||
|
||||
class QPushButtonWrapper(QPushButton):
|
||||
"""
|
||||
This is a customized implementation of the PySide6 QPushButton that allows to suppress the warning that is triggered
|
||||
by disconnecting a signal that is not currently connected.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.is_connected = False
|
||||
@@ -24,6 +24,7 @@ class AddFieldModal(QWidget):
|
||||
# - OR -
|
||||
# [Cancel] [Save]
|
||||
super().__init__()
|
||||
self.is_connected = False
|
||||
self.lib = library
|
||||
self.setWindowTitle(f"Add Field")
|
||||
self.setWindowModality(Qt.WindowModality.ApplicationModal)
|
||||
|
||||
@@ -110,4 +110,4 @@ class FileExtensionModal(PanelWidget):
|
||||
for i in range(self.table.rowCount()):
|
||||
ext = self.table.item(i, 0)
|
||||
if ext and ext.text():
|
||||
self.lib.ext_list.append(ext.text())
|
||||
self.lib.ext_list.append(ext.text().lower())
|
||||
|
||||
@@ -18,6 +18,7 @@ from PySide6.QtWidgets import (
|
||||
QFrame,
|
||||
)
|
||||
|
||||
from src.core.enums import FieldID
|
||||
from src.core.library import Library, Tag
|
||||
from src.core.palette import ColorType, get_tag_color
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
@@ -73,13 +74,13 @@ def folders_to_tags(library: Library):
|
||||
tag = add_folders_to_tree(folders)
|
||||
if tag:
|
||||
if not entry.has_tag(library, tag.id):
|
||||
entry.add_tag(library, tag.id, 6)
|
||||
entry.add_tag(library, tag.id, FieldID.TAGS)
|
||||
|
||||
logging.info("Done")
|
||||
|
||||
|
||||
def reverse_tag(library: Library, tag: Tag, list: list[Tag]) -> list[Tag]:
|
||||
if list != None:
|
||||
if list is not None:
|
||||
list.append(tag)
|
||||
else:
|
||||
list = [tag]
|
||||
@@ -144,7 +145,7 @@ def generate_preview_data(library: Library):
|
||||
if cut:
|
||||
branch["dirs"].pop(folder)
|
||||
|
||||
if not "tag" in branch:
|
||||
if "tag" not in branch:
|
||||
return
|
||||
if branch["tag"].id == -1 or len(branch["files"]) > 0: # Needs to be first
|
||||
return False
|
||||
@@ -289,7 +290,7 @@ class TreeItem(QWidget):
|
||||
self.children_layout.addWidget(item)
|
||||
for file in data["files"]:
|
||||
label = QLabel()
|
||||
label.setText(" -> " + file)
|
||||
label.setText(" -> " + str(file))
|
||||
self.children_layout.addWidget(label)
|
||||
|
||||
if len(data["files"]) == 0 and len(data["dirs"].values()) == 0:
|
||||
@@ -321,7 +322,7 @@ class ModifiedTagWidget(
|
||||
|
||||
self.bg_button = QPushButton(self)
|
||||
self.bg_button.setFlat(True)
|
||||
if parentTag != None:
|
||||
if parentTag is not None:
|
||||
text = f"{tag.name} ({parentTag.name})".replace("&", "&&")
|
||||
else:
|
||||
text = tag.name.replace("&", "&&")
|
||||
|
||||
@@ -15,7 +15,7 @@ from PySide6.QtWidgets import (
|
||||
QLineEdit,
|
||||
QSizePolicy,
|
||||
)
|
||||
|
||||
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
|
||||
# class NumberEdit(QLineEdit):
|
||||
# def __init__(self, parent=None) -> None:
|
||||
@@ -50,13 +50,13 @@ class Pagination(QWidget, QObject):
|
||||
# self.setMinimumHeight(32)
|
||||
|
||||
# [<] ----------------------------------
|
||||
self.prev_button = QPushButton()
|
||||
self.prev_button = QPushButtonWrapper()
|
||||
self.prev_button.setText("<")
|
||||
self.prev_button.setMinimumSize(self.button_size)
|
||||
self.prev_button.setMaximumSize(self.button_size)
|
||||
|
||||
# --- [1] ------------------------------
|
||||
self.start_button = QPushButton()
|
||||
self.start_button = QPushButtonWrapper()
|
||||
self.start_button.setMinimumSize(self.button_size)
|
||||
self.start_button.setMaximumSize(self.button_size)
|
||||
# self.start_button.setStyleSheet('background:cyan;')
|
||||
@@ -104,14 +104,14 @@ class Pagination(QWidget, QObject):
|
||||
self.end_ellipses.setText(". . .")
|
||||
|
||||
# ----------------------------- [42] ---
|
||||
self.end_button = QPushButton()
|
||||
self.end_button = QPushButtonWrapper()
|
||||
self.end_button.setMinimumSize(self.button_size)
|
||||
self.end_button.setMaximumSize(self.button_size)
|
||||
# self.end_button.setMaximumHeight(self.button_size.height())
|
||||
# self.end_button.setStyleSheet('background:red;')
|
||||
|
||||
# ---------------------------------- [>]
|
||||
self.next_button = QPushButton()
|
||||
self.next_button = QPushButtonWrapper()
|
||||
self.next_button.setText(">")
|
||||
self.next_button.setMinimumSize(self.button_size)
|
||||
self.next_button.setMaximumSize(self.button_size)
|
||||
@@ -428,16 +428,15 @@ class Pagination(QWidget, QObject):
|
||||
# print(f'GOTO PAGE: {index}')
|
||||
self.update_buttons(self.page_count, index)
|
||||
|
||||
def _assign_click(self, button: QPushButton, index):
|
||||
try:
|
||||
def _assign_click(self, button: QPushButtonWrapper, index):
|
||||
if button.is_connected:
|
||||
button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
button.clicked.connect(lambda checked=False, i=index: self._goto_page(i))
|
||||
button.is_connected = True
|
||||
|
||||
def _populate_buffer_buttons(self):
|
||||
for i in range(max(self.buffer_page_count * 2, 5)):
|
||||
button = QPushButton()
|
||||
button = QPushButtonWrapper()
|
||||
button.setMinimumSize(self.button_size)
|
||||
button.setMaximumSize(self.button_size)
|
||||
button.setHidden(True)
|
||||
@@ -445,7 +444,7 @@ class Pagination(QWidget, QObject):
|
||||
self.start_buffer_layout.addWidget(button)
|
||||
|
||||
for i in range(max(self.buffer_page_count * 2, 5)):
|
||||
button = QPushButton()
|
||||
button = QPushButtonWrapper()
|
||||
button.setMinimumSize(self.button_size)
|
||||
button.setMaximumSize(self.button_size)
|
||||
button.setHidden(True)
|
||||
|
||||
@@ -64,6 +64,8 @@ from src.core.constants import (
|
||||
TS_FOLDER_NAME,
|
||||
VERSION_BRANCH,
|
||||
VERSION,
|
||||
TAG_FAVORITE,
|
||||
TAG_ARCHIVED,
|
||||
)
|
||||
from src.core.utils.web import strip_web_protocol
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
@@ -1260,8 +1262,8 @@ class QtDriver(QObject):
|
||||
filepath = self.lib.library_dir / entry.path / entry.filename
|
||||
|
||||
item_thumb.set_item_id(entry.id)
|
||||
item_thumb.assign_archived(entry.has_tag(self.lib, 0))
|
||||
item_thumb.assign_favorite(entry.has_tag(self.lib, 1))
|
||||
item_thumb.assign_archived(entry.has_tag(self.lib, TAG_ARCHIVED))
|
||||
item_thumb.assign_favorite(entry.has_tag(self.lib, TAG_FAVORITE))
|
||||
# ctrl_down = True if QGuiApplication.keyboardModifiers() else False
|
||||
# TODO: Change how this works. The click function
|
||||
# for collations a few lines down should NOT be allowed during modifier keys.
|
||||
|
||||
@@ -13,6 +13,7 @@ from PIL import Image, ImageQt
|
||||
from PySide6.QtCore import Qt, QEvent
|
||||
from PySide6.QtGui import QPixmap, QEnterEvent
|
||||
from PySide6.QtWidgets import QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton
|
||||
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
|
||||
|
||||
class FieldContainer(QWidget):
|
||||
@@ -81,7 +82,7 @@ class FieldContainer(QWidget):
|
||||
|
||||
self.title_layout.addStretch(2)
|
||||
|
||||
self.copy_button = QPushButton()
|
||||
self.copy_button = QPushButtonWrapper()
|
||||
self.copy_button.setMinimumSize(button_size, button_size)
|
||||
self.copy_button.setMaximumSize(button_size, button_size)
|
||||
self.copy_button.setFlat(True)
|
||||
@@ -92,7 +93,7 @@ class FieldContainer(QWidget):
|
||||
self.title_layout.addWidget(self.copy_button)
|
||||
self.copy_button.setHidden(True)
|
||||
|
||||
self.edit_button = QPushButton()
|
||||
self.edit_button = QPushButtonWrapper()
|
||||
self.edit_button.setMinimumSize(button_size, button_size)
|
||||
self.edit_button.setMaximumSize(button_size, button_size)
|
||||
self.edit_button.setFlat(True)
|
||||
@@ -101,7 +102,7 @@ class FieldContainer(QWidget):
|
||||
self.title_layout.addWidget(self.edit_button)
|
||||
self.edit_button.setHidden(True)
|
||||
|
||||
self.remove_button = QPushButton()
|
||||
self.remove_button = QPushButtonWrapper()
|
||||
self.remove_button.setMinimumSize(button_size, button_size)
|
||||
self.remove_button.setMaximumSize(button_size, button_size)
|
||||
self.remove_button.setFlat(True)
|
||||
@@ -124,31 +125,30 @@ class FieldContainer(QWidget):
|
||||
# self.set_inner_widget(mode)
|
||||
|
||||
def set_copy_callback(self, callback: Optional[MethodType]):
|
||||
try:
|
||||
if self.copy_button.is_connected:
|
||||
self.copy_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
self.copy_callback = callback
|
||||
self.copy_button.clicked.connect(callback)
|
||||
if callback is not None:
|
||||
self.copy_button.is_connected = True
|
||||
|
||||
def set_edit_callback(self, callback: Optional[MethodType]):
|
||||
try:
|
||||
if self.edit_button.is_connected:
|
||||
self.edit_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
self.edit_callback = callback
|
||||
self.edit_button.clicked.connect(callback)
|
||||
if callback is not None:
|
||||
self.edit_button.is_connected = True
|
||||
|
||||
def set_remove_callback(self, callback: Optional[Callable]):
|
||||
try:
|
||||
if self.remove_button.is_connected:
|
||||
self.remove_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
self.remove_callback = callback
|
||||
self.remove_button.clicked.connect(callback)
|
||||
self.remove_button.is_connected = True
|
||||
|
||||
def set_inner_widget(self, widget: "FieldWidget"):
|
||||
# widget.setStyleSheet('background-color:green;')
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
# Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
import typing
|
||||
from types import FunctionType
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -23,9 +21,15 @@ from PySide6.QtWidgets import (
|
||||
QCheckBox,
|
||||
)
|
||||
|
||||
|
||||
from src.core.enums import FieldID
|
||||
from src.core.library import ItemType, Library, Entry
|
||||
from src.core.constants import AUDIO_TYPES, VIDEO_TYPES, IMAGE_TYPES
|
||||
from src.core.constants import (
|
||||
AUDIO_TYPES,
|
||||
VIDEO_TYPES,
|
||||
IMAGE_TYPES,
|
||||
TAG_FAVORITE,
|
||||
TAG_ARCHIVED,
|
||||
)
|
||||
from src.qt.flowlayout import FlowWidget
|
||||
from src.qt.helpers.file_opener import FileOpenerHelper
|
||||
from src.qt.widgets.thumb_renderer import ThumbRenderer
|
||||
@@ -38,9 +42,6 @@ ERROR = f"[ERROR]"
|
||||
WARNING = f"[WARNING]"
|
||||
INFO = f"[INFO]"
|
||||
|
||||
DEFAULT_META_TAG_FIELD = 8
|
||||
TAG_FAVORITE = 1
|
||||
TAG_ARCHIVED = 0
|
||||
|
||||
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
||||
|
||||
@@ -395,19 +396,22 @@ class ItemThumb(FlowWidget):
|
||||
def update_clickable(self, clickable: typing.Callable):
|
||||
"""Updates attributes of a thumbnail element."""
|
||||
# logging.info(f'[GUI] Updating Click Event for element {id(element)}: {id(clickable) if clickable else None}')
|
||||
try:
|
||||
if self.thumb_button.is_connected:
|
||||
self.thumb_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
if clickable:
|
||||
self.thumb_button.clicked.connect(clickable)
|
||||
self.thumb_button.is_connected = True
|
||||
|
||||
def update_badges(self):
|
||||
if self.mode == ItemType.ENTRY:
|
||||
# logging.info(f'[UPDATE BADGES] ENTRY: {self.lib.get_entry(self.item_id)}')
|
||||
# logging.info(f'[UPDATE BADGES] ARCH: {self.lib.get_entry(self.item_id).has_tag(self.lib, 0)}, FAV: {self.lib.get_entry(self.item_id).has_tag(self.lib, 1)}')
|
||||
self.assign_archived(self.lib.get_entry(self.item_id).has_tag(self.lib, 0))
|
||||
self.assign_favorite(self.lib.get_entry(self.item_id).has_tag(self.lib, 1))
|
||||
self.assign_archived(
|
||||
self.lib.get_entry(self.item_id).has_tag(self.lib, TAG_ARCHIVED)
|
||||
)
|
||||
self.assign_favorite(
|
||||
self.lib.get_entry(self.item_id).has_tag(self.lib, TAG_FAVORITE)
|
||||
)
|
||||
|
||||
def set_item_id(self, id: int):
|
||||
"""
|
||||
@@ -476,7 +480,7 @@ class ItemThumb(FlowWidget):
|
||||
entry.add_tag(
|
||||
self.panel.driver.lib,
|
||||
tag_id,
|
||||
field_id=DEFAULT_META_TAG_FIELD,
|
||||
field_id=FieldID.META_TAGS,
|
||||
field_index=-1,
|
||||
)
|
||||
else:
|
||||
|
||||
@@ -40,6 +40,7 @@ from src.qt.widgets.text import TextWidget
|
||||
from src.qt.widgets.panel import PanelModal
|
||||
from src.qt.widgets.text_box_edit import EditTextBox
|
||||
from src.qt.widgets.text_line_edit import EditTextLine
|
||||
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
from src.qt.widgets.video_player import VideoPlayer
|
||||
|
||||
|
||||
@@ -61,6 +62,7 @@ class PreviewPanel(QWidget):
|
||||
|
||||
def __init__(self, library: Library, driver: "QtDriver"):
|
||||
super().__init__()
|
||||
self.is_connected = False
|
||||
self.lib = library
|
||||
self.driver: QtDriver = driver
|
||||
self.initialized = False
|
||||
@@ -83,7 +85,7 @@ class PreviewPanel(QWidget):
|
||||
self.open_file_action = QAction("Open file", self)
|
||||
self.open_explorer_action = QAction("Open file in explorer", self)
|
||||
|
||||
self.preview_img = QPushButton()
|
||||
self.preview_img = QPushButtonWrapper()
|
||||
self.preview_img.setMinimumSize(*self.img_button_size)
|
||||
self.preview_img.setFlat(True)
|
||||
self.preview_img.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
@@ -218,7 +220,7 @@ class PreviewPanel(QWidget):
|
||||
self.afb_layout = QVBoxLayout(self.afb_container)
|
||||
self.afb_layout.setContentsMargins(0, 12, 0, 0)
|
||||
|
||||
self.add_field_button = QPushButton()
|
||||
self.add_field_button = QPushButtonWrapper()
|
||||
self.add_field_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.add_field_button.setMinimumSize(96, 28)
|
||||
self.add_field_button.setMaximumSize(96, 28)
|
||||
@@ -279,7 +281,9 @@ class PreviewPanel(QWidget):
|
||||
row_layout.addWidget(label)
|
||||
layout.addLayout(row_layout)
|
||||
|
||||
def set_button_style(btn: QPushButton, extras: list[str] | None = None):
|
||||
def set_button_style(
|
||||
btn: QPushButtonWrapper | QPushButton, extras: list[str] | None = None
|
||||
):
|
||||
base_style = [
|
||||
f"background-color:{Theme.COLOR_BG.value};",
|
||||
"border-radius:6px;",
|
||||
@@ -317,7 +321,6 @@ class PreviewPanel(QWidget):
|
||||
|
||||
button.clicked.connect(open_library_button_clicked(full_val))
|
||||
set_button_style(button)
|
||||
|
||||
button_remove = QPushButton("➖")
|
||||
button_remove.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
button_remove.setFixedWidth(30)
|
||||
@@ -411,16 +414,16 @@ class PreviewPanel(QWidget):
|
||||
self.afb_container, Qt.AlignmentFlag.AlignHCenter
|
||||
)
|
||||
|
||||
try:
|
||||
if self.afm.is_connected:
|
||||
self.afm.done.disconnect()
|
||||
if self.add_field_button.is_connected:
|
||||
self.add_field_button.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
# self.afm.done.connect(lambda f: (self.lib.add_field_to_entry(self.selected[0][1], f), self.update_widgets()))
|
||||
self.afm.done.connect(
|
||||
lambda f: (self.add_field_to_selected(f), self.update_widgets())
|
||||
)
|
||||
self.afm.is_connected = True
|
||||
self.add_field_button.clicked.connect(self.afm.show)
|
||||
|
||||
def add_field_to_selected(self, field_id: int):
|
||||
@@ -466,10 +469,8 @@ class PreviewPanel(QWidget):
|
||||
True,
|
||||
update_on_ratio_change=True,
|
||||
)
|
||||
try:
|
||||
if self.preview_img.is_connected:
|
||||
self.preview_img.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
for i, c in enumerate(self.containers):
|
||||
c.setHidden(True)
|
||||
self.preview_img.show()
|
||||
@@ -588,14 +589,12 @@ class PreviewPanel(QWidget):
|
||||
f"[PreviewPanel][ERROR] Couldn't Render thumbnail for {filepath} (because of {e})"
|
||||
)
|
||||
|
||||
try:
|
||||
if self.preview_img.is_connected:
|
||||
self.preview_img.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.preview_img.clicked.connect(
|
||||
lambda checked=False, filepath=filepath: open_file(filepath)
|
||||
)
|
||||
|
||||
self.preview_img.is_connected = True
|
||||
self.selected = list(self.driver.selected)
|
||||
for i, f in enumerate(item.fields):
|
||||
self.write_container(i, f)
|
||||
@@ -641,10 +640,8 @@ class PreviewPanel(QWidget):
|
||||
True,
|
||||
update_on_ratio_change=True,
|
||||
)
|
||||
try:
|
||||
if self.preview_img.is_connected:
|
||||
self.preview_img.clicked.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
self.common_fields = []
|
||||
self.mixed_fields = []
|
||||
@@ -773,12 +770,12 @@ class PreviewPanel(QWidget):
|
||||
"""
|
||||
Replacement for tag_callback.
|
||||
"""
|
||||
try:
|
||||
if self.is_connected:
|
||||
self.tags_updated.disconnect()
|
||||
except RuntimeError:
|
||||
pass
|
||||
|
||||
logging.info("[UPDATE CONTAINER] Setting tags updated slot")
|
||||
self.tags_updated.connect(slot)
|
||||
self.is_connected = True
|
||||
|
||||
# def write_container(self, item:Union[Entry, Collation, Tag], index, field):
|
||||
def write_container(self, index, field, mixed=False):
|
||||
@@ -1067,7 +1064,8 @@ class PreviewPanel(QWidget):
|
||||
)
|
||||
# remove_mb.setStandardButtons(QMessageBox.StandardButton.Cancel)
|
||||
remove_mb.setDefaultButton(cancel_button)
|
||||
remove_mb.setEscapeButton(cancel_button)
|
||||
result = remove_mb.exec_()
|
||||
# logging.info(result)
|
||||
if result == 1:
|
||||
if result == 3:
|
||||
callback()
|
||||
|
||||
@@ -10,6 +10,7 @@ import typing
|
||||
from PySide6.QtCore import Signal, Qt
|
||||
from PySide6.QtWidgets import QPushButton
|
||||
|
||||
from src.core.constants import TAG_FAVORITE, TAG_ARCHIVED
|
||||
from src.core.library import Library, Tag
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
from src.qt.widgets.fields import FieldWidget
|
||||
@@ -141,7 +142,7 @@ class TagBoxWidget(FieldWidget):
|
||||
# panel.tag_updated.connect(lambda tag: self.lib.update_tag(tag))
|
||||
self.edit_modal.show()
|
||||
|
||||
def add_tag_callback(self, tag_id):
|
||||
def add_tag_callback(self, tag_id: int):
|
||||
# self.base_layout.addWidget(TagWidget(self.lib, self.lib.get_tag(tag), True))
|
||||
# self.tags.append(tag)
|
||||
logging.info(
|
||||
@@ -154,7 +155,7 @@ class TagBoxWidget(FieldWidget):
|
||||
self.driver.lib, tag_id, field_id=id, field_index=-1
|
||||
)
|
||||
self.updated.emit()
|
||||
if tag_id == 0 or tag_id == 1:
|
||||
if tag_id in (TAG_FAVORITE, TAG_ARCHIVED):
|
||||
self.driver.update_badges()
|
||||
|
||||
# if type((x[0]) == ThumbButton):
|
||||
@@ -180,7 +181,7 @@ class TagBoxWidget(FieldWidget):
|
||||
self.driver.lib, tag_id, field_index=index[0]
|
||||
)
|
||||
self.updated.emit()
|
||||
if tag_id == 0 or tag_id == 1:
|
||||
if tag_id in (TAG_FAVORITE, TAG_ARCHIVED):
|
||||
self.driver.update_badges()
|
||||
|
||||
# def show_add_button(self, value:bool):
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
from PySide6 import QtCore
|
||||
from PySide6.QtCore import QEvent
|
||||
from PySide6.QtGui import QEnterEvent, QPainter, QColor, QPen, QPainterPath, QPaintEvent
|
||||
from PySide6.QtWidgets import QWidget, QPushButton
|
||||
from PySide6.QtWidgets import QWidget
|
||||
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
|
||||
|
||||
class ThumbButton(QPushButton):
|
||||
class ThumbButton(QPushButtonWrapper):
|
||||
def __init__(self, parent: QWidget, thumb_size: tuple[int, int]) -> None:
|
||||
super().__init__(parent)
|
||||
self.thumb_size: tuple[int, int] = thumb_size
|
||||
|
||||
42
tagstudio/tests/conftest.py
Normal file
42
tagstudio/tests/conftest.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import sys
|
||||
import pathlib
|
||||
|
||||
import pytest
|
||||
from syrupy.extensions.json import JSONSnapshotExtension
|
||||
|
||||
CWD = pathlib.Path(__file__).parent
|
||||
|
||||
sys.path.insert(0, str(CWD.parent))
|
||||
|
||||
from src.core.library import Tag, Library
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_tag():
|
||||
yield Tag(
|
||||
id=1,
|
||||
name="Tag Name",
|
||||
shorthand="TN",
|
||||
aliases=["First A", "Second A"],
|
||||
subtags_ids=[2, 3, 4],
|
||||
color="",
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_library():
|
||||
lib_dir = CWD / "fixtures" / "library"
|
||||
|
||||
lib = Library()
|
||||
ret_code = lib.open_library(lib_dir)
|
||||
assert ret_code == 1
|
||||
# create files for the entries
|
||||
for entry in lib.entries:
|
||||
(lib_dir / entry.filename).touch()
|
||||
|
||||
yield lib
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def snapshot_json(snapshot):
|
||||
return snapshot.with_defaults(extension_class=JSONSnapshotExtension)
|
||||
@@ -0,0 +1 @@
|
||||
[]
|
||||
@@ -0,0 +1,6 @@
|
||||
[
|
||||
[
|
||||
"<ItemType.ENTRY: 0>",
|
||||
2
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
[
|
||||
[
|
||||
"<ItemType.ENTRY: 0>",
|
||||
1
|
||||
]
|
||||
]
|
||||
@@ -0,0 +1,4 @@
|
||||
[
|
||||
"{'id': 1, 'filename': 'foo.txt', 'path': '.', 'fields': [{6: [1001]}]}",
|
||||
"{'id': 2, 'filename': 'bar.txt', 'path': '.', 'fields': [{6: [1000]}]}"
|
||||
]
|
||||
18
tagstudio/tests/core/test_lib.py
Normal file
18
tagstudio/tests/core/test_lib.py
Normal file
@@ -0,0 +1,18 @@
|
||||
import pytest
|
||||
|
||||
|
||||
def test_open_library(test_library, snapshot_json):
|
||||
assert test_library.entries == snapshot_json
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
["query"],
|
||||
[
|
||||
("First",),
|
||||
("Second",),
|
||||
("--nomatch--",),
|
||||
],
|
||||
)
|
||||
def test_library_search(test_library, query, snapshot_json):
|
||||
res = test_library.search_library(query)
|
||||
assert res == snapshot_json
|
||||
@@ -1,18 +1,8 @@
|
||||
from src.core.library import Tag
|
||||
def test_subtag(test_tag):
|
||||
test_tag.remove_subtag(2)
|
||||
test_tag.remove_subtag(2)
|
||||
|
||||
|
||||
def test_construction():
|
||||
tag = Tag(
|
||||
id=1,
|
||||
name="Tag Name",
|
||||
shorthand="TN",
|
||||
aliases=["First A", "Second A"],
|
||||
subtags_ids=[2, 3, 4],
|
||||
color="",
|
||||
)
|
||||
assert tag
|
||||
|
||||
|
||||
def test_empty_construction():
|
||||
tag = Tag(id=1, name="", shorthand="", aliases=[], subtags_ids=[], color="")
|
||||
assert tag
|
||||
test_tag.add_subtag(5)
|
||||
# repeated add should not add the subtag
|
||||
test_tag.add_subtag(5)
|
||||
assert test_tag.subtag_ids == [3, 4, 5]
|
||||
|
||||
69
tagstudio/tests/fixtures/library/.TagStudio/ts_library.json
vendored
Normal file
69
tagstudio/tests/fixtures/library/.TagStudio/ts_library.json
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"ts-version": "9.3.1",
|
||||
"ext_list": [
|
||||
".json",
|
||||
".xmp",
|
||||
".aae"
|
||||
],
|
||||
"is_exclude_list": true,
|
||||
"tags": [
|
||||
{
|
||||
"id": 0,
|
||||
"name": "Archived",
|
||||
"aliases": [
|
||||
"Archive"
|
||||
],
|
||||
"color": "Red"
|
||||
},
|
||||
{
|
||||
"id": 1,
|
||||
"name": "Favorite",
|
||||
"aliases": [
|
||||
"Favorited",
|
||||
"Favorites"
|
||||
],
|
||||
"color": "Yellow"
|
||||
},
|
||||
{
|
||||
"id": 1000,
|
||||
"name": "first",
|
||||
"shorthand": "first",
|
||||
"color": "magenta"
|
||||
},
|
||||
{
|
||||
"id": 1001,
|
||||
"name": "second",
|
||||
"shorthand": "second",
|
||||
"color": "blue"
|
||||
}
|
||||
],
|
||||
"collations": [],
|
||||
"fields": [],
|
||||
"macros": [],
|
||||
"entries": [
|
||||
{
|
||||
"id": 1,
|
||||
"filename": "foo.txt",
|
||||
"path": ".",
|
||||
"fields": [
|
||||
{
|
||||
"6": [
|
||||
1001
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"filename": "bar.txt",
|
||||
"path": ".",
|
||||
"fields": [
|
||||
{
|
||||
"6": [
|
||||
1000
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user