mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-01 15:49:09 +00:00
feat: add tag categories to preview panel
This commit is contained in:
@@ -432,7 +432,9 @@ class Library:
|
||||
selectinload(Entry.text_fields),
|
||||
selectinload(Entry.datetime_fields),
|
||||
selectinload(Entry.tags).options(
|
||||
selectinload(Tag.aliases), selectinload(Tag.subtags)
|
||||
selectinload(Tag.aliases),
|
||||
selectinload(Tag.subtags),
|
||||
selectinload(Tag.parent_tags),
|
||||
),
|
||||
)
|
||||
entry = session.scalar(statement)
|
||||
|
||||
@@ -31,8 +31,10 @@ from src.qt.translations import Translations
|
||||
from src.core.library.alchemy.models import Entry
|
||||
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
from src.qt.modals.add_field import AddFieldModal
|
||||
from src.core.library.alchemy.models import Entry, Tag
|
||||
from src.qt.widgets.fields import FieldContainer
|
||||
from src.qt.widgets.panel import PanelModal
|
||||
from src.qt.widgets.tag_box import TagBoxWidget
|
||||
from src.qt.widgets.text import TextWidget
|
||||
from src.qt.widgets.text_box_edit import EditTextBox
|
||||
from src.qt.widgets.text_line_edit import EditTextLine
|
||||
@@ -100,19 +102,6 @@ class FieldContainers(QWidget):
|
||||
)
|
||||
self.scroll_area.setWidget(scroll_container)
|
||||
|
||||
self.afb_container = QWidget()
|
||||
self.afb_layout = QVBoxLayout(self.afb_container)
|
||||
self.afb_layout.setContentsMargins(0, 12, 0, 0)
|
||||
|
||||
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)
|
||||
Translations.translate_qobject(self.add_field_button, "library.field.add")
|
||||
self.afb_layout.addWidget(self.add_field_button)
|
||||
self.add_field_modal = AddFieldModal(self.lib)
|
||||
self.place_add_field_button()
|
||||
|
||||
root_layout = QHBoxLayout(self)
|
||||
root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
root_layout.addWidget(self.scroll_area)
|
||||
@@ -121,48 +110,81 @@ class FieldContainers(QWidget):
|
||||
"""Update tags and fields from a single Entry source."""
|
||||
self.selected = [self.lib.get_entry_full(entry.id)]
|
||||
logger.info(
|
||||
"[Field Containers] Updating Selection",
|
||||
"[FieldContainers] Updating Selection",
|
||||
entry=entry,
|
||||
fields=entry.fields,
|
||||
tags=entry.tags,
|
||||
)
|
||||
|
||||
entry_ = self.selected[0]
|
||||
for idx, field in enumerate(entry_.fields):
|
||||
self.write_container(idx, field, is_mixed=False)
|
||||
container_len: int = len(entry_.fields)
|
||||
# for index, tag in enumerate(entry_.tags):
|
||||
# self.write_tag_container(index, )
|
||||
# TODO: Break up into categories
|
||||
container_index = 0
|
||||
if entry_.tags:
|
||||
# TODO: Display the tag categories
|
||||
pass
|
||||
categories = self.get_tag_categories(entry_.tags)
|
||||
for cat, tags in sorted(categories.items(), key=lambda kv: (kv[0] is None, kv)):
|
||||
self.write_tag_container(
|
||||
container_index, tags=tags, category_tag=cat, is_mixed=False
|
||||
)
|
||||
container_index += 1
|
||||
container_len += 1
|
||||
for index, field in enumerate(entry_.fields, start=container_index):
|
||||
self.write_container(index, field, is_mixed=False)
|
||||
|
||||
# Hide leftover containers
|
||||
if len(self.containers) > len(entry_.fields):
|
||||
if len(self.containers) > container_len:
|
||||
for i, c in enumerate(self.containers):
|
||||
if i > (len(entry_.fields) - 1):
|
||||
if i > (container_len - 1):
|
||||
c.setHidden(True)
|
||||
|
||||
self.add_field_button.setHidden(False)
|
||||
def get_tag_categories(self, tags: set[Tag]) -> dict[Tag, set[Tag | None]]:
|
||||
cats: dict[Tag, set[Tag | None]] = {}
|
||||
cats[None] = set()
|
||||
|
||||
# Initialize all categories from parents
|
||||
for tag in tags:
|
||||
for p_tag in list(tag.subtags) + [tag]:
|
||||
logger.info(f"[{tag.name}] is {p_tag.name} a category? ({p_tag.is_category})")
|
||||
if p_tag.is_category:
|
||||
cats[p_tag] = set()
|
||||
logger.info("Blank Tag Categories", cats=cats)
|
||||
|
||||
for tag in tags:
|
||||
is_general = True
|
||||
for p_tag in list(cats.keys()):
|
||||
logger.info(f"[{tag.name}] Checking category tag key {p_tag}")
|
||||
if not p_tag:
|
||||
pass
|
||||
elif p_tag in tag.subtags:
|
||||
cats[p_tag].add(tag)
|
||||
is_general = False
|
||||
elif tag == p_tag:
|
||||
cats[p_tag].add(tag)
|
||||
is_general = False
|
||||
pass
|
||||
if is_general:
|
||||
cats[None].add(tag)
|
||||
|
||||
empty: list[Tag] = []
|
||||
for k, v in list(cats.items()):
|
||||
if not v:
|
||||
empty.append(k)
|
||||
for key in empty:
|
||||
cats.pop(key, None)
|
||||
|
||||
logger.info("Tag Categories", cats=cats)
|
||||
return cats
|
||||
|
||||
def remove_field_prompt(self, name: str) -> str:
|
||||
return Translations.translate_formatted("library.field.confirm_remove", name=name)
|
||||
|
||||
def place_add_field_button(self):
|
||||
self.scroll_layout.addWidget(self.afb_container)
|
||||
self.scroll_layout.setAlignment(self.afb_container, Qt.AlignmentFlag.AlignHCenter)
|
||||
|
||||
if self.add_field_modal.is_connected:
|
||||
self.add_field_modal.done.disconnect()
|
||||
if self.add_field_button.is_connected:
|
||||
self.add_field_button.clicked.disconnect()
|
||||
|
||||
self.add_field_modal.done.connect(
|
||||
lambda f: (self.add_field_to_selected(f), self.update_from_entry(self.selected[0]))
|
||||
)
|
||||
self.add_field_modal.is_connected = True
|
||||
self.add_field_button.clicked.connect(self.add_field_modal.show)
|
||||
|
||||
def add_field_to_selected(self, field_list: list):
|
||||
"""Add list of entry fields to one or more selected items."""
|
||||
logger.info("add_field_to_selected", selected=self.selected, fields=field_list)
|
||||
logger.info(
|
||||
"[FieldContainers][add_field_to_selected]", selected=self.selected, fields=field_list
|
||||
)
|
||||
for entry in self.selected:
|
||||
for field_item in field_list:
|
||||
self.lib.add_entry_field_type(
|
||||
@@ -170,6 +192,15 @@ class FieldContainers(QWidget):
|
||||
field_id=field_item.data(Qt.ItemDataRole.UserRole),
|
||||
)
|
||||
|
||||
def add_tags_to_selected(self, tags: list[int]):
|
||||
"""Add list of tags to one or more selected items."""
|
||||
logger.info("[FieldContainers][add_tags_to_selected]", selected=self.selected, tags=tags)
|
||||
for entry in self.selected:
|
||||
self.lib.add_tags_to_entry(
|
||||
entry.id,
|
||||
tag_ids=tags,
|
||||
)
|
||||
|
||||
def write_container(self, index: int, field: BaseField, is_mixed: bool = False):
|
||||
"""Update/Create data for a FieldContainer.
|
||||
|
||||
@@ -180,8 +211,7 @@ class FieldContainers(QWidget):
|
||||
|
||||
If True, field is not present in all selected items.
|
||||
"""
|
||||
# Remove 'Add Field' button from scroll_layout, to be re-added later.
|
||||
self.scroll_layout.takeAt(self.scroll_layout.count() - 1).widget()
|
||||
logger.info("[write_field_container]", index=index)
|
||||
if len(self.containers) < (index + 1):
|
||||
container = FieldContainer()
|
||||
self.containers.append(container)
|
||||
@@ -189,59 +219,6 @@ class FieldContainers(QWidget):
|
||||
else:
|
||||
container = self.containers[index]
|
||||
|
||||
# if isinstance(field, TagBoxField):
|
||||
# container.set_title(field.type.name)
|
||||
# container.set_inline(False)
|
||||
# title = f"{field.type.name} (Tag Box)"
|
||||
#
|
||||
# if not is_mixed:
|
||||
# inner_container = container.get_inner_widget()
|
||||
# if isinstance(inner_container, TagBoxWidget):
|
||||
# inner_container.set_field(field)
|
||||
# inner_container.set_tags(list(field.tags))
|
||||
#
|
||||
# try:
|
||||
# inner_container.updated.disconnect()
|
||||
# except RuntimeError:
|
||||
# logger.error("Failed to disconnect inner_container.updated")
|
||||
#
|
||||
# else:
|
||||
# inner_container = TagBoxWidget(
|
||||
# field,
|
||||
# title,
|
||||
# self.driver,
|
||||
# )
|
||||
#
|
||||
# container.set_inner_widget(inner_container)
|
||||
#
|
||||
# inner_container.updated.connect(
|
||||
# lambda: (
|
||||
# self.write_container(index, field),
|
||||
# self.update_widgets(),
|
||||
# )
|
||||
# )
|
||||
# # NOTE: Tag Boxes have no Edit Button
|
||||
# (But will when you can convert field types)
|
||||
# container.set_remove_callback(
|
||||
# lambda: self.remove_message_box(
|
||||
# prompt=self.remove_field_prompt(field.type.name),
|
||||
# callback=lambda: (
|
||||
# self.remove_field(field),
|
||||
# self.update_selected_entry(self.driver),
|
||||
# # reload entry and its fields
|
||||
# self.update_widgets(),
|
||||
# ),
|
||||
# )
|
||||
# )
|
||||
# else:
|
||||
# text = "<i>Mixed Data</i>"
|
||||
# title = f"{field.type.name} (Wacky Tag Box)"
|
||||
# inner_container = TextWidget(title, text)
|
||||
# container.set_inner_widget(inner_container)
|
||||
#
|
||||
# self.tags_updated.emit()
|
||||
# # self.dynamic_widgets.append(inner_container)
|
||||
# elif field.type.type == FieldTypeEnum.TEXT_LINE:
|
||||
if field.type.type == FieldTypeEnum.TEXT_LINE:
|
||||
container.set_title(field.type.name)
|
||||
container.set_inline(False)
|
||||
@@ -254,8 +231,8 @@ class FieldContainers(QWidget):
|
||||
text = "<i>Mixed Data</i>"
|
||||
|
||||
title = f"{field.type.name} ({field.type.type.value})"
|
||||
inner_container = TextWidget(title, text)
|
||||
container.set_inner_widget(inner_container)
|
||||
inner_widget = TextWidget(title, text)
|
||||
container.set_inner_widget(inner_widget)
|
||||
if not is_mixed:
|
||||
modal = PanelModal(
|
||||
EditTextLine(field.value),
|
||||
@@ -294,8 +271,8 @@ class FieldContainers(QWidget):
|
||||
else:
|
||||
text = "<i>Mixed Data</i>"
|
||||
title = f"{field.type.name} (Text Box)"
|
||||
inner_container = TextWidget(title, text)
|
||||
container.set_inner_widget(inner_container)
|
||||
inner_widget = TextWidget(title, text)
|
||||
container.set_inner_widget(inner_widget)
|
||||
if not is_mixed:
|
||||
modal = PanelModal(
|
||||
EditTextBox(field.value),
|
||||
@@ -328,15 +305,15 @@ class FieldContainers(QWidget):
|
||||
# TODO: Localize this and/or add preferences.
|
||||
date = dt.strptime(field.value, "%Y-%m-%d %H:%M:%S")
|
||||
title = f"{field.type.name} (Date)"
|
||||
inner_container = TextWidget(title, date.strftime("%D - %r"))
|
||||
container.set_inner_widget(inner_container)
|
||||
inner_widget = TextWidget(title, date.strftime("%D - %r"))
|
||||
container.set_inner_widget(inner_widget)
|
||||
except Exception:
|
||||
container.set_title(field.type.name)
|
||||
# container.set_editable(False)
|
||||
container.set_inline(False)
|
||||
title = f"{field.type.name} (Date) (Unknown Format)"
|
||||
inner_container = TextWidget(title, str(field.value))
|
||||
container.set_inner_widget(inner_container)
|
||||
inner_widget = TextWidget(title, str(field.value))
|
||||
container.set_inner_widget(inner_widget)
|
||||
|
||||
container.set_remove_callback(
|
||||
lambda: self.remove_message_box(
|
||||
@@ -350,15 +327,15 @@ class FieldContainers(QWidget):
|
||||
else:
|
||||
text = "<i>Mixed Data</i>"
|
||||
title = f"{field.type.name} (Wacky Date)"
|
||||
inner_container = TextWidget(title, text)
|
||||
container.set_inner_widget(inner_container)
|
||||
inner_widget = TextWidget(title, text)
|
||||
container.set_inner_widget(inner_widget)
|
||||
else:
|
||||
logger.warning("write_container - unknown field", field=field)
|
||||
logger.warning("[FieldContainers][write_container] Unknown Field", field=field)
|
||||
container.set_title(field.type.name)
|
||||
container.set_inline(False)
|
||||
title = f"{field.type.name} (Unknown Field Type)"
|
||||
inner_container = TextWidget(title, field.type.name)
|
||||
container.set_inner_widget(inner_container)
|
||||
inner_widget = TextWidget(title, field.type.name)
|
||||
container.set_inner_widget(inner_widget)
|
||||
container.set_remove_callback(
|
||||
lambda: self.remove_message_box(
|
||||
prompt=self.remove_field_prompt(field.type.name),
|
||||
@@ -371,11 +348,67 @@ class FieldContainers(QWidget):
|
||||
|
||||
container.edit_button.setHidden(True)
|
||||
container.setHidden(False)
|
||||
self.place_add_field_button()
|
||||
|
||||
def write_tag_container(
|
||||
self, index: int, tags: set[Tag], category_tag: Tag | None = None, is_mixed: bool = False
|
||||
):
|
||||
"""Update/Create tag data for a FieldContainer.
|
||||
|
||||
Args:
|
||||
index(int): The container index.
|
||||
tags(set[Tag]): The list of tags for this container.
|
||||
category_tag(Tag|None): The category tag this container represents.
|
||||
is_mixed(bool): Relevant when multiple items are selected.
|
||||
|
||||
If True, field is not present in all selected items.
|
||||
"""
|
||||
logger.info("[write_tag_container]", index=index)
|
||||
if len(self.containers) < (index + 1):
|
||||
container = FieldContainer()
|
||||
self.containers.append(container)
|
||||
self.scroll_layout.addWidget(container)
|
||||
else:
|
||||
container = self.containers[index]
|
||||
|
||||
container.set_title("Tags" if not category_tag else category_tag.name)
|
||||
container.set_inline(False)
|
||||
|
||||
if not is_mixed:
|
||||
inner_widget = container.get_inner_widget()
|
||||
|
||||
if isinstance(inner_widget, TagBoxWidget):
|
||||
inner_widget.set_tags(tags)
|
||||
try:
|
||||
inner_widget.updated.disconnect()
|
||||
except RuntimeError:
|
||||
logger.error("[FieldContainers] Failed to disconnect inner_container.updated")
|
||||
|
||||
else:
|
||||
inner_widget = TagBoxWidget(
|
||||
tags,
|
||||
"Tags",
|
||||
self.driver,
|
||||
)
|
||||
container.set_inner_widget(inner_widget)
|
||||
|
||||
inner_widget.updated.connect(
|
||||
lambda: (
|
||||
self.write_tag_container(index, tags, category_tag),
|
||||
self.update_from_entry(self.selected[0]),
|
||||
)
|
||||
)
|
||||
else:
|
||||
text = "<i>Mixed Data</i>"
|
||||
inner_widget = TextWidget("Mixed Tags", text)
|
||||
container.set_inner_widget(inner_widget)
|
||||
|
||||
self.tags_updated.emit()
|
||||
container.edit_button.setHidden(True)
|
||||
container.setHidden(False)
|
||||
|
||||
def remove_field(self, field: BaseField):
|
||||
"""Remove a field from all selected Entries."""
|
||||
logger.info("removing field", field=field, selected=self.selected)
|
||||
logger.info("[FieldContainers] Removing Field", field=field, selected=self.selected)
|
||||
entry_ids = [e.id for e in self.selected]
|
||||
self.lib.remove_entry_field(field, entry_ids)
|
||||
|
||||
@@ -421,344 +454,6 @@ class FieldContainers(QWidget):
|
||||
if self.is_connected:
|
||||
self.tags_updated.disconnect()
|
||||
|
||||
logger.info("[UPDATE CONTAINER] Setting tags updated slot")
|
||||
logger.info("[FieldContainers][set_tags_updated_slot] Setting tags updated slot")
|
||||
self.tags_updated.connect(slot)
|
||||
self.is_connected = True
|
||||
|
||||
# def update_widgets(self):
|
||||
# """Render the panel widgets with the newest data from the Library."""
|
||||
# logger.info("update_widgets", selected=self.driver.selected)
|
||||
# # self.is_open = True
|
||||
# # self.tag_callback = tag_callback if tag_callback else None
|
||||
# # window_title = ""
|
||||
|
||||
# # # update list of libraries
|
||||
# # self.fill_libs_widget(self.libs_layout)
|
||||
|
||||
# # if not self.driver.selected:
|
||||
# # if self.selected or not self.initialized:
|
||||
# # # self.file_label.setText("<i>No Items Selected</i>")
|
||||
# # # self.file_label.set_file_path("")
|
||||
# # # self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
# # # self.dimensions_label.setText("")
|
||||
# # # self.update_date_label()
|
||||
# # # self.preview_img.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
|
||||
# # # self.preview_img.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
# # # ratio = self.devicePixelRatio()
|
||||
# # # self.thumb_renderer.render(
|
||||
# # # time.time(),
|
||||
# # # "",
|
||||
# # # (512, 512),
|
||||
# # # ratio,
|
||||
# # # is_loading=True,
|
||||
# # # update_on_ratio_change=True,
|
||||
# # # )
|
||||
# # # if self.preview_img.is_connected:
|
||||
# # # self.preview_img.clicked.disconnect()
|
||||
# # # for c in self.containers:
|
||||
# # # c.setHidden(True)
|
||||
# # # self.preview_img.show()
|
||||
# # # self.preview_vid.stop()
|
||||
# # # self.preview_vid.hide()
|
||||
# # # self.media_player.hide()
|
||||
# # # self.media_player.stop()
|
||||
# # # self.preview_gif.hide()
|
||||
# # # self.selected = list(self.driver.selected)
|
||||
# # self.add_field_button.setHidden(True)
|
||||
|
||||
# # # common code
|
||||
# # self.initialized = True
|
||||
# # self.setWindowTitle(window_title)
|
||||
# # self.show()
|
||||
# # return True
|
||||
|
||||
# # reload entry and fill it into the grid again
|
||||
# # TODO - do this more granular
|
||||
# # TODO - Entry reload is maybe not necessary
|
||||
# for grid_idx in self.driver.selected:
|
||||
# entry = self.driver.frame_content[grid_idx]
|
||||
# results = self.lib.search_library(FilterState(id=entry.id))
|
||||
# logger.info(
|
||||
# "found item",
|
||||
# entries=len(results.items),
|
||||
# grid_idx=grid_idx,
|
||||
# lookup_id=entry.id,
|
||||
# )
|
||||
# self.driver.frame_content[grid_idx] = results[0]
|
||||
|
||||
# if len(self.driver.selected) == 1:
|
||||
# # 1 Selected Entry
|
||||
# selected_idx = self.driver.selected[0]
|
||||
# item = self.driver.frame_content[selected_idx]
|
||||
|
||||
# self.preview_img.show()
|
||||
# self.preview_vid.stop()
|
||||
# self.preview_vid.hide()
|
||||
# self.media_player.stop()
|
||||
# self.media_player.hide()
|
||||
# self.preview_gif.hide()
|
||||
|
||||
# # If a new selection is made, update the thumbnail and filepath.
|
||||
# if not self.selected or self.selected != self.driver.selected:
|
||||
# filepath = self.lib.library_dir / item.path
|
||||
# self.file_label.set_file_path(filepath)
|
||||
# ratio = self.devicePixelRatio()
|
||||
# self.thumb_renderer.render(
|
||||
# time.time(),
|
||||
# filepath,
|
||||
# (512, 512),
|
||||
# ratio,
|
||||
# update_on_ratio_change=True,
|
||||
# )
|
||||
# file_str: str = ""
|
||||
# separator: str = f"<a style='color: #777777'><b>{os.path.sep}</a>" # Gray
|
||||
# for i, part in enumerate(filepath.parts):
|
||||
# part_ = part.strip(os.path.sep)
|
||||
# if i != len(filepath.parts) - 1:
|
||||
# file_str += f"{"\u200b".join(part_)}{separator}</b>"
|
||||
# else:
|
||||
# file_str += f"<br><b>{"\u200b".join(part_)}</b>"
|
||||
# self.file_label.setText(file_str)
|
||||
# self.file_label.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
|
||||
# self.preview_img.setContextMenuPolicy(Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||
# self.preview_img.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
|
||||
# self.opener = FileOpenerHelper(filepath)
|
||||
# self.open_file_action.triggered.connect(self.opener.open_file)
|
||||
# self.open_explorer_action.triggered.connect(self.opener.open_explorer)
|
||||
|
||||
# # TODO: Do this all somewhere else, this is just here temporarily.
|
||||
# ext: str = filepath.suffix.lower()
|
||||
# try:
|
||||
# if MediaCategories.is_ext_in_category(
|
||||
# ext, MediaCategories.IMAGE_ANIMATED_TYPES, mime_fallback=True
|
||||
# ):
|
||||
# if self.preview_gif.movie():
|
||||
# self.preview_gif.movie().stop()
|
||||
# self.gif_buffer.close()
|
||||
|
||||
# image: Image.Image = Image.open(filepath)
|
||||
# anim_image: Image.Image = image
|
||||
# image_bytes_io: io.BytesIO = io.BytesIO()
|
||||
# anim_image.save(
|
||||
# image_bytes_io,
|
||||
# "GIF",
|
||||
# lossless=True,
|
||||
# save_all=True,
|
||||
# loop=0,
|
||||
# disposal=2,
|
||||
# )
|
||||
# image_bytes_io.seek(0)
|
||||
# ba: bytes = image_bytes_io.read()
|
||||
|
||||
# self.gif_buffer.setData(ba)
|
||||
# movie = QMovie(self.gif_buffer, QByteArray())
|
||||
# self.preview_gif.setMovie(movie)
|
||||
# movie.start()
|
||||
|
||||
# self.resizeEvent(
|
||||
# QResizeEvent(
|
||||
# QSize(image.width, image.height),
|
||||
# QSize(image.width, image.height),
|
||||
# )
|
||||
# )
|
||||
# self.preview_img.hide()
|
||||
# self.preview_vid.hide()
|
||||
# self.preview_gif.show()
|
||||
|
||||
# image = None
|
||||
# if MediaCategories.is_ext_in_category(ext, MediaCategories.IMAGE_RASTER_TYPES): #noqa: E501
|
||||
# image = Image.open(str(filepath))
|
||||
# elif MediaCategories.is_ext_in_category(ext, MediaCategories.IMAGE_RAW_TYPES):
|
||||
# try:
|
||||
# with rawpy.imread(str(filepath)) as raw:
|
||||
# rgb = raw.postprocess()
|
||||
# image = Image.new("L", (rgb.shape[1], rgb.shape[0]), color="black") #noqa: E501
|
||||
# except (
|
||||
# rawpy._rawpy.LibRawIOError,
|
||||
# rawpy._rawpy.LibRawFileUnsupportedError,
|
||||
# ):
|
||||
# pass
|
||||
# elif MediaCategories.is_ext_in_category(ext, MediaCategories.AUDIO_TYPES):
|
||||
# self.media_player.show()
|
||||
# self.media_player.play(filepath)
|
||||
# elif MediaCategories.is_ext_in_category(
|
||||
# ext, MediaCategories.VIDEO_TYPES
|
||||
# ) and is_readable_video(filepath):
|
||||
# video = cv2.VideoCapture(str(filepath), cv2.CAP_FFMPEG)
|
||||
# video.set(
|
||||
# cv2.CAP_PROP_POS_FRAMES,
|
||||
# (video.get(cv2.CAP_PROP_FRAME_COUNT) // 2),
|
||||
# )
|
||||
# success, frame = video.read()
|
||||
# frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
||||
# image = Image.fromarray(frame)
|
||||
# if success:
|
||||
# self.preview_img.hide()
|
||||
# self.preview_vid.play(filepath, QSize(image.width, image.height))
|
||||
# self.resizeEvent(
|
||||
# QResizeEvent(
|
||||
# QSize(image.width, image.height),
|
||||
# QSize(image.width, image.height),
|
||||
# )
|
||||
# )
|
||||
# self.preview_vid.show()
|
||||
|
||||
# # Stats for specific file types are displayed here.
|
||||
# if image and (
|
||||
# MediaCategories.is_ext_in_category(
|
||||
# ext, MediaCategories.IMAGE_RASTER_TYPES, mime_fallback=True
|
||||
# )
|
||||
# or MediaCategories.is_ext_in_category(
|
||||
# ext, MediaCategories.VIDEO_TYPES, mime_fallback=True
|
||||
# )
|
||||
# or MediaCategories.is_ext_in_category(
|
||||
# ext, MediaCategories.IMAGE_RAW_TYPES, mime_fallback=True
|
||||
# )
|
||||
# ):
|
||||
# self.dimensions_label.setText(
|
||||
# f"{ext.upper()[1:]} • {format_size(filepath.stat().st_size)}\n"
|
||||
# f"{image.width} x {image.height} px"
|
||||
# )
|
||||
# elif MediaCategories.is_ext_in_category(
|
||||
# ext, MediaCategories.FONT_TYPES, mime_fallback=True
|
||||
# ):
|
||||
# try:
|
||||
# font = ImageFont.truetype(filepath)
|
||||
# self.dimensions_label.setText(
|
||||
# f"{ext.upper()[1:]} • {format_size(filepath.stat().st_size)}\n"
|
||||
# f"{font.getname()[0]} ({font.getname()[1]}) "
|
||||
# )
|
||||
# except OSError:
|
||||
# self.dimensions_label.setText(
|
||||
# f"{ext.upper()[1:]} • {format_size(filepath.stat().st_size)}"
|
||||
# )
|
||||
# logger.info(
|
||||
# f"[PreviewPanel][ERROR] Couldn't read font file: {filepath}"
|
||||
# )
|
||||
# else:
|
||||
# self.dimensions_label.setText(f"{ext.upper()[1:]}")
|
||||
# self.dimensions_label.setText(
|
||||
# f"{ext.upper()[1:]} • {format_size(filepath.stat().st_size)}"
|
||||
# )
|
||||
# self.update_date_label(filepath)
|
||||
|
||||
# if not filepath.is_file():
|
||||
# raise FileNotFoundError
|
||||
|
||||
# except (FileNotFoundError, cv2.error) as e:
|
||||
# self.dimensions_label.setText(f"{ext.upper()[1:]}")
|
||||
# logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
|
||||
# self.update_date_label()
|
||||
# except (
|
||||
# UnidentifiedImageError,
|
||||
# DecompressionBombError,
|
||||
# ) as e:
|
||||
# self.dimensions_label.setText(
|
||||
# f"{ext.upper()[1:]} • {format_size(filepath.stat().st_size)}"
|
||||
# )
|
||||
# logger.error("Couldn't render thumbnail", filepath=filepath, error=e)
|
||||
# self.update_date_label(filepath)
|
||||
|
||||
# if self.preview_img.is_connected:
|
||||
# self.preview_img.clicked.disconnect()
|
||||
# self.preview_img.clicked.connect(lambda checked=False, pth=filepath: open_file(pth)) #noqa: E501
|
||||
# self.preview_img.is_connected = True
|
||||
|
||||
# self.selected = self.driver.selected
|
||||
# logger.info(
|
||||
# "rendering item fields",
|
||||
# item=item.id,
|
||||
# fields=[x.type_key for x in item.fields],
|
||||
# )
|
||||
# for idx, field in enumerate(item.fields):
|
||||
# self.write_container(idx, field)
|
||||
|
||||
# # Hide leftover containers
|
||||
# if len(self.containers) > len(item.fields):
|
||||
# for i, c in enumerate(self.containers):
|
||||
# if i > (len(item.fields) - 1):
|
||||
# c.setHidden(True)
|
||||
|
||||
# self.add_field_button.setHidden(False)
|
||||
|
||||
# # Multiple Selected Items
|
||||
# elif len(self.driver.selected) > 1:
|
||||
# self.preview_img.show()
|
||||
# self.preview_gif.hide()
|
||||
# self.preview_vid.stop()
|
||||
# self.preview_vid.hide()
|
||||
# self.media_player.stop()
|
||||
# self.media_player.hide()
|
||||
# self.update_date_label()
|
||||
# if self.selected != self.driver.selected:
|
||||
# self.file_label.setText(f"<b>{len(self.driver.selected)}</b> Items Selected")
|
||||
# self.file_label.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
# self.file_label.set_file_path("")
|
||||
# self.dimensions_label.setText("")
|
||||
|
||||
# self.preview_img.setContextMenuPolicy(Qt.ContextMenuPolicy.NoContextMenu)
|
||||
# self.preview_img.setCursor(Qt.CursorShape.ArrowCursor)
|
||||
|
||||
# ratio = self.devicePixelRatio()
|
||||
# self.thumb_renderer.render(
|
||||
# time.time(),
|
||||
# "",
|
||||
# (512, 512),
|
||||
# ratio,
|
||||
# is_loading=True,
|
||||
# update_on_ratio_change=True,
|
||||
# )
|
||||
# if self.preview_img.is_connected:
|
||||
# self.preview_img.clicked.disconnect()
|
||||
# self.preview_img.is_connected = False
|
||||
|
||||
# # fill shared fields from first item
|
||||
# first_item = self.driver.frame_content[self.driver.selected[0]]
|
||||
# common_fields = [f for f in first_item.fields]
|
||||
# mixed_fields = []
|
||||
|
||||
# # iterate through other items
|
||||
# for grid_idx in self.driver.selected[1:]:
|
||||
# item = self.driver.frame_content[grid_idx]
|
||||
# item_field_types = {f.type_key for f in item.fields}
|
||||
# for f in common_fields[:]:
|
||||
# if f.type_key not in item_field_types:
|
||||
# common_fields.remove(f)
|
||||
# mixed_fields.append(f)
|
||||
|
||||
# self.common_fields = common_fields
|
||||
# self.mixed_fields = sorted(mixed_fields, key=lambda x: x.type.position)
|
||||
|
||||
# self.selected = list(self.driver.selected)
|
||||
# logger.info(
|
||||
# "update_widgets common_fields",
|
||||
# common_fields=self.common_fields,
|
||||
# )
|
||||
# for i, f in enumerate(self.common_fields):
|
||||
# self.write_container(i, f)
|
||||
|
||||
# logger.info(
|
||||
# "update_widgets mixed_fields",
|
||||
# mixed_fields=self.mixed_fields,
|
||||
# start=len(self.common_fields),
|
||||
# )
|
||||
# for i, f in enumerate(self.mixed_fields, start=len(self.common_fields)):
|
||||
# self.write_container(i, f, is_mixed=True)
|
||||
|
||||
# # Hide leftover containers
|
||||
# if len(self.containers) > len(self.common_fields) + len(self.mixed_fields):
|
||||
# for i, c in enumerate(self.containers):
|
||||
# if i > (len(self.common_fields) + len(self.mixed_fields) - 1):
|
||||
# c.setHidden(True)
|
||||
|
||||
# self.add_field_button.setHidden(False)
|
||||
|
||||
# self.initialized = True
|
||||
|
||||
# self.setWindowTitle(window_title)
|
||||
# self.show()
|
||||
# return True
|
||||
|
||||
@@ -112,7 +112,6 @@ class FileAttributes(QWidget):
|
||||
|
||||
def update_date_label(self, filepath: Path | None = None) -> None:
|
||||
"""Update the "Date Created" and "Date Modified" file property labels."""
|
||||
logger.info(filepath)
|
||||
if filepath and filepath.is_file():
|
||||
created: dt = None
|
||||
if platform.system() == "Windows" or platform.system() == "Darwin":
|
||||
@@ -139,8 +138,6 @@ class FileAttributes(QWidget):
|
||||
|
||||
def update_stats(self, filepath: Path | None = None, ext: str = ".", stats: dict = None):
|
||||
"""Render the panel widgets with the newest data from the Library."""
|
||||
logger.info("update_stats", selected=filepath)
|
||||
|
||||
if not stats:
|
||||
stats = {}
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import QHBoxLayout, QSplitter, QVBoxLayout, QWidget
|
||||
from src.core.library.alchemy.library import Library
|
||||
from src.core.library.alchemy.models import Entry
|
||||
from src.qt.helpers.qbutton_wrapper import QPushButtonWrapper
|
||||
from src.qt.modals.add_field import AddFieldModal
|
||||
from src.qt.modals.tag_search import TagSearchPanel
|
||||
from src.qt.widgets.panel import PanelModal
|
||||
|
||||
# from src.qt.modals.add_tag import AddTagModal
|
||||
from src.qt.widgets.preview.field_containers import FieldContainers
|
||||
from src.qt.widgets.preview.file_attributes import FileAttributes
|
||||
from src.qt.widgets.preview.preview_thumb import PreviewThumb
|
||||
@@ -34,12 +40,18 @@ class PreviewPanel(QWidget):
|
||||
self.driver: QtDriver = driver
|
||||
self.initialized = False
|
||||
self.is_open: bool = False
|
||||
# self.selected: list[int] = [] # New way of tracking items
|
||||
|
||||
self.thumb = PreviewThumb(library, driver)
|
||||
self.file_attrs = FileAttributes(library, driver)
|
||||
self.fields = FieldContainers(library, driver)
|
||||
|
||||
tsp = TagSearchPanel(self.driver.lib)
|
||||
# tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x))
|
||||
self.add_tag_modal = PanelModal(tsp, "Add Tags", "Add Tags")
|
||||
|
||||
# self.add_tag_modal = AddTagModal(self.lib)
|
||||
self.add_field_modal = AddFieldModal(self.lib)
|
||||
|
||||
preview_section = QWidget()
|
||||
preview_layout = QVBoxLayout(preview_section)
|
||||
preview_layout.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -61,6 +73,22 @@ class PreviewPanel(QWidget):
|
||||
# )
|
||||
# )
|
||||
# )
|
||||
add_buttons_container = QWidget()
|
||||
add_buttons_layout = QHBoxLayout(add_buttons_container)
|
||||
add_buttons_layout.setContentsMargins(0, 0, 0, 0)
|
||||
add_buttons_layout.setSpacing(6)
|
||||
|
||||
self.add_tag_button = QPushButtonWrapper()
|
||||
self.add_tag_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.add_tag_button.setText("Add Tag")
|
||||
|
||||
self.add_field_button = QPushButtonWrapper()
|
||||
self.add_field_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.add_field_button.setText("Add Field")
|
||||
|
||||
add_buttons_layout.addWidget(self.add_tag_button)
|
||||
add_buttons_layout.addWidget(self.add_field_button)
|
||||
|
||||
preview_layout.addWidget(self.thumb)
|
||||
preview_layout.addWidget(self.thumb.media_player)
|
||||
info_layout.addWidget(self.file_attrs)
|
||||
@@ -71,9 +99,10 @@ class PreviewPanel(QWidget):
|
||||
# splitter.addWidget(self.libs_flow_container)
|
||||
splitter.setStretchFactor(1, 2)
|
||||
|
||||
root_layout = QHBoxLayout(self)
|
||||
root_layout = QVBoxLayout(self)
|
||||
root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
root_layout.addWidget(splitter)
|
||||
root_layout.addWidget(add_buttons_container)
|
||||
|
||||
def update_widgets(self) -> bool:
|
||||
"""Render the panel widgets with the newest data from the Library."""
|
||||
@@ -92,11 +121,12 @@ class PreviewPanel(QWidget):
|
||||
ext: str = filepath.suffix.lower()
|
||||
|
||||
stats: dict = self.thumb.update_preview(filepath, ext)
|
||||
logger.info("stats", stats=stats, ext=ext)
|
||||
try:
|
||||
self.file_attrs.update_stats(filepath, ext, stats)
|
||||
self.file_attrs.update_date_label(filepath)
|
||||
self.fields.update_from_entry(entry)
|
||||
self.update_add_tag_button(entry)
|
||||
self.update_add_field_button(entry)
|
||||
except Exception as e:
|
||||
logger.error("[Preview Panel] Error updating selection", error=e)
|
||||
traceback.print_exc()
|
||||
@@ -114,3 +144,42 @@ class PreviewPanel(QWidget):
|
||||
# self.fields.update_widgets()
|
||||
|
||||
return True
|
||||
|
||||
def update_add_field_button(self, entry: Entry):
|
||||
if self.add_field_modal.is_connected:
|
||||
self.add_field_modal.done.disconnect()
|
||||
if self.add_field_button.is_connected:
|
||||
self.add_field_button.clicked.disconnect()
|
||||
|
||||
self.add_field_modal.done.connect(
|
||||
lambda f: (
|
||||
self.fields.add_field_to_selected(f),
|
||||
self.fields.update_from_entry(entry),
|
||||
)
|
||||
)
|
||||
self.add_field_modal.is_connected = True
|
||||
self.add_field_button.clicked.connect(self.add_field_modal.show)
|
||||
|
||||
def update_add_tag_button(self, entry: Entry):
|
||||
# if self.add_tag_modal.is_connected:
|
||||
# self.add_tag_modal.done.disconnect()
|
||||
if self.add_tag_button.is_connected:
|
||||
self.add_tag_button.clicked.disconnect()
|
||||
|
||||
self.add_tag_modal.widget.tag_chosen.connect(
|
||||
lambda t: (
|
||||
self.fields.add_tags_to_selected(t),
|
||||
self.fields.update_from_entry(entry),
|
||||
)
|
||||
)
|
||||
# self.add_tag_modal.is_connected = True
|
||||
|
||||
self.add_tag_button.clicked.connect(
|
||||
lambda: (
|
||||
# self.add_tag_modal.widget.update_tags(),
|
||||
self.add_tag_modal.show(),
|
||||
)
|
||||
)
|
||||
self.add_tag_button.is_connected = True
|
||||
|
||||
# self.add_field_button.clicked.connect(self.add_tag_modal.show)
|
||||
|
||||
@@ -1,192 +1,176 @@
|
||||
# # Copyright (C) 2024 Travis Abendshien (CyanVoxel).
|
||||
# # Licensed under the GPL-3.0 License.
|
||||
# # Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
# Copyright (C) 2025 Travis Abendshien (CyanVoxel).
|
||||
# Licensed under the GPL-3.0 License.
|
||||
# Created for TagStudio: https://github.com/CyanVoxel/TagStudio
|
||||
|
||||
|
||||
# import math
|
||||
# import typing
|
||||
import typing
|
||||
|
||||
# import structlog
|
||||
# from PySide6.QtCore import Qt, Signal
|
||||
# from PySide6.QtWidgets import QPushButton
|
||||
# from src.core.constants import TAG_ARCHIVED, TAG_FAVORITE
|
||||
# from src.core.library import Entry, Tag
|
||||
# from src.core.library.alchemy.enums import FilterState
|
||||
import structlog
|
||||
from PySide6.QtCore import Signal
|
||||
from src.core.constants import TAG_ARCHIVED, TAG_FAVORITE
|
||||
from src.core.library import Tag
|
||||
from src.core.library.alchemy.enums import FilterState
|
||||
from src.qt.flowlayout import FlowLayout
|
||||
from src.qt.modals.build_tag import BuildTagPanel
|
||||
from src.qt.widgets.fields import FieldWidget
|
||||
from src.qt.widgets.panel import PanelModal
|
||||
from src.qt.widgets.tag import TagWidget
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from src.qt.ts_qt import QtDriver
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
# # from src.core.library.alchemy.fields import TagBoxField
|
||||
# from src.qt.flowlayout import FlowLayout
|
||||
# from src.qt.modals.build_tag import BuildTagPanel
|
||||
# from src.qt.modals.tag_search import TagSearchPanel
|
||||
# from src.qt.translations import Translations
|
||||
# from src.qt.widgets.fields import FieldWidget
|
||||
# from src.qt.widgets.panel import PanelModal
|
||||
# from src.qt.widgets.tag import TagWidget
|
||||
class TagBoxWidget(FieldWidget):
|
||||
updated = Signal()
|
||||
error_occurred = Signal(Exception)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
tags: set[Tag],
|
||||
title: str,
|
||||
driver: "QtDriver",
|
||||
) -> None:
|
||||
super().__init__(title)
|
||||
|
||||
# if typing.TYPE_CHECKING:
|
||||
# from src.qt.ts_qt import QtDriver
|
||||
self.tags: set[Tag] = tags
|
||||
self.driver = (
|
||||
driver # Used for creating tag click callbacks that search entries for that tag.
|
||||
)
|
||||
self.setObjectName("tagBox")
|
||||
self.base_layout = FlowLayout()
|
||||
self.base_layout.enable_grid_optimizations(value=False)
|
||||
self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
self.setLayout(self.base_layout)
|
||||
|
||||
# logger = structlog.get_logger(__name__)
|
||||
# self.add_button = QPushButton()
|
||||
# self.add_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
# self.add_button.setMinimumSize(23, 23)
|
||||
# self.add_button.setMaximumSize(23, 23)
|
||||
# self.add_button.setText("+")
|
||||
# self.add_button.setStyleSheet(
|
||||
# f"QPushButton{{"
|
||||
# f"background: #1e1e1e;"
|
||||
# f"color: #FFFFFF;"
|
||||
# f"font-weight: bold;"
|
||||
# f"border-color: #333333;"
|
||||
# f"border-radius: 6px;"
|
||||
# f"border-style:solid;"
|
||||
# f"border-width:{math.ceil(self.devicePixelRatio())}px;"
|
||||
# f"padding-bottom: 5px;"
|
||||
# f"font-size: 20px;"
|
||||
# f"}}"
|
||||
# f"QPushButton::hover"
|
||||
# f"{{"
|
||||
# f"border-color: #CCCCCC;"
|
||||
# f"background: #555555;"
|
||||
# f"}}"
|
||||
# )
|
||||
# tsp = TagSearchPanel(self.driver.lib)
|
||||
# tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x))
|
||||
# self.add_modal = PanelModal(tsp, title, "Add Tags")
|
||||
# self.add_button.clicked.connect(
|
||||
# lambda: (
|
||||
# tsp.update_tags(),
|
||||
# self.add_modal.show(),
|
||||
# )
|
||||
# )
|
||||
|
||||
self.set_tags(self.tags)
|
||||
|
||||
# class TagBoxWidget(FieldWidget):
|
||||
# updated = Signal()
|
||||
# error_occurred = Signal(Exception)
|
||||
def set_tags(self, tags: typing.Iterable[Tag]):
|
||||
tags_ = sorted(list(tags), key=lambda tag: tag.name)
|
||||
logger.info("[TagBoxWidget] Tags:", tags=tags)
|
||||
# is_recycled = False
|
||||
while self.base_layout.itemAt(0):
|
||||
self.base_layout.takeAt(0).widget().deleteLater()
|
||||
# is_recycled = True
|
||||
|
||||
# def __init__(
|
||||
# self,
|
||||
# field: TagBoxField,
|
||||
# title: str,
|
||||
# driver: "QtDriver",
|
||||
# ) -> None:
|
||||
# super().__init__(title)
|
||||
for tag in tags_:
|
||||
tag_widget = TagWidget(tag, has_edit=True, has_remove=True)
|
||||
tag_widget.on_click.connect(
|
||||
lambda tag_id=tag.id: (
|
||||
self.driver.main_window.searchField.setText(f"tag_id:{tag_id}"),
|
||||
self.driver.filter_items(FilterState.from_tag_id(tag_id)),
|
||||
)
|
||||
)
|
||||
|
||||
# assert isinstance(field, TagBoxField), f"field is {type(field)}"
|
||||
tag_widget.on_remove.connect(
|
||||
lambda tag_id=tag.id: (
|
||||
self.remove_tag(tag_id),
|
||||
self.driver.preview_panel.update_widgets(),
|
||||
)
|
||||
)
|
||||
tag_widget.on_edit.connect(lambda t=tag: self.edit_tag(t))
|
||||
self.base_layout.addWidget(tag_widget)
|
||||
|
||||
# self.field = field
|
||||
# self.driver = (
|
||||
# driver # Used for creating tag click callbacks that search entries for that tag.
|
||||
# )
|
||||
# self.setObjectName("tagBox")
|
||||
# self.base_layout = FlowLayout()
|
||||
# self.base_layout.enable_grid_optimizations(value=False)
|
||||
# self.base_layout.setContentsMargins(0, 0, 0, 0)
|
||||
# self.setLayout(self.base_layout)
|
||||
# # Move or add the '+' button.
|
||||
# if is_recycled:
|
||||
# self.base_layout.addWidget(self.base_layout.takeAt(0).widget())
|
||||
# else:
|
||||
# self.base_layout.addWidget(self.add_button)
|
||||
|
||||
# Handles an edge case where there are no more tags and the '+' button
|
||||
# doesn't move all the way to the left.
|
||||
if self.base_layout.itemAt(0) and not self.base_layout.itemAt(1):
|
||||
self.base_layout.update()
|
||||
|
||||
# self.add_button = QPushButton()
|
||||
# self.add_button.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
# self.add_button.setMinimumSize(23, 23)
|
||||
# self.add_button.setMaximumSize(23, 23)
|
||||
# self.add_button.setText("+")
|
||||
# self.add_button.setStyleSheet(
|
||||
# f"QPushButton{{"
|
||||
# f"background: #1e1e1e;"
|
||||
# f"color: #FFFFFF;"
|
||||
# f"font-weight: bold;"
|
||||
# f"border-color: #333333;"
|
||||
# f"border-radius: 6px;"
|
||||
# f"border-style:solid;"
|
||||
# f"border-width:{math.ceil(self.devicePixelRatio())}px;"
|
||||
# f"padding-bottom: 5px;"
|
||||
# f"font-size: 20px;"
|
||||
# f"}}"
|
||||
# f"QPushButton::hover"
|
||||
# f"{{"
|
||||
# f"border-color: #CCCCCC;"
|
||||
# f"background: #555555;"
|
||||
# f"}}"
|
||||
# )
|
||||
# tsp = TagSearchPanel(self.driver.lib)
|
||||
# tsp.tag_chosen.connect(lambda x: self.add_tag_callback(x))
|
||||
# self.add_modal = PanelModal(tsp, title)
|
||||
# Translations.translate_with_setter(self.add_modal.setWindowTitle, "tag.add.plural")
|
||||
# self.add_button.clicked.connect(
|
||||
# lambda: (
|
||||
# tsp.update_tags(),
|
||||
# self.add_modal.show(),
|
||||
# )
|
||||
# )
|
||||
def edit_tag(self, tag: Tag):
|
||||
assert isinstance(tag, Tag), f"tag is {type(tag)}"
|
||||
build_tag_panel = BuildTagPanel(self.driver.lib, tag=tag)
|
||||
|
||||
self.edit_modal = PanelModal(
|
||||
build_tag_panel,
|
||||
tag.name, # TODO - display name including subtags
|
||||
"Edit Tag",
|
||||
done_callback=self.driver.preview_panel.update_widgets,
|
||||
has_save=True,
|
||||
)
|
||||
# TODO - this was update_tag()
|
||||
self.edit_modal.saved.connect(
|
||||
lambda: self.driver.lib.update_tag(
|
||||
build_tag_panel.build_tag(),
|
||||
subtag_ids=set(build_tag_panel.subtag_ids),
|
||||
alias_names=set(build_tag_panel.alias_names),
|
||||
alias_ids=set(build_tag_panel.alias_ids),
|
||||
)
|
||||
)
|
||||
self.edit_modal.show()
|
||||
|
||||
# self.set_tags(field.tags)
|
||||
def add_tag_callback(self, tag_id: int):
|
||||
logger.info("[TagBoxWidget] add_tag_callback", tag_id=tag_id, selected=self.driver.selected)
|
||||
|
||||
# def set_field(self, field: TagBoxField):
|
||||
# self.field = field
|
||||
# tag = self.driver.lib.get_tag(tag_id=tag_id)
|
||||
for entry_id in self.driver.selected:
|
||||
# entry: Entry = self.driver.frame_content[entry.id]
|
||||
self.driver.lib.add_tags_to_entry(entry_id, tag_id)
|
||||
|
||||
# def set_tags(self, tags: typing.Iterable[Tag]):
|
||||
# tags_ = sorted(list(tags), key=lambda tag: tag.name)
|
||||
# is_recycled = False
|
||||
# while self.base_layout.itemAt(0) and self.base_layout.itemAt(1):
|
||||
# self.base_layout.takeAt(0).widget().deleteLater()
|
||||
# is_recycled = True
|
||||
if not self.driver.lib.add_tags_to_entry(entry_id, tag_id):
|
||||
# TODO - add some visible error
|
||||
self.error_occurred.emit(Exception("Failed to add tag"))
|
||||
|
||||
# for tag in tags_:
|
||||
# tag_widget = TagWidget(tag, has_edit=True, has_remove=True)
|
||||
# tag_widget.on_click.connect(
|
||||
# lambda tag_id=tag.id: (
|
||||
# self.driver.main_window.searchField.setText(f"tag_id:{tag_id}"),
|
||||
# self.driver.filter_items(FilterState.from_tag_id(tag_id)),
|
||||
# )
|
||||
# )
|
||||
self.updated.emit()
|
||||
|
||||
if tag_id in (TAG_FAVORITE, TAG_ARCHIVED):
|
||||
self.driver.update_badges()
|
||||
|
||||
# tag_widget.on_remove.connect(
|
||||
# lambda tag_id=tag.id: (
|
||||
# self.remove_tag(tag_id),
|
||||
# self.driver.preview_panel.update_widgets(),
|
||||
# )
|
||||
# )
|
||||
# tag_widget.on_edit.connect(lambda t=tag: self.edit_tag(t))
|
||||
# self.base_layout.addWidget(tag_widget)
|
||||
def edit_tag_callback(self, tag: Tag):
|
||||
self.driver.lib.update_tag(tag)
|
||||
|
||||
# # Move or add the '+' button.
|
||||
# if is_recycled:
|
||||
# self.base_layout.addWidget(self.base_layout.takeAt(0).widget())
|
||||
# else:
|
||||
# self.base_layout.addWidget(self.add_button)
|
||||
def remove_tag(self, tag_id: int):
|
||||
logger.info(
|
||||
"[TagBoxWidget] remove_tag",
|
||||
selected=self.driver.selected,
|
||||
# field_type=self.field.type,
|
||||
)
|
||||
|
||||
# # Handles an edge case where there are no more tags and the '+' button
|
||||
# # doesn't move all the way to the left.
|
||||
# if self.base_layout.itemAt(0) and not self.base_layout.itemAt(1):
|
||||
# self.base_layout.update()
|
||||
for entry_id in self.driver.selected:
|
||||
# entry = self.driver.frame_content[entry_id]
|
||||
self.driver.lib.remove_tags_from_entry(entry_id, tag_id)
|
||||
# self.driver.lib.remove_field_tag(entry, tag_id, self.field.type_key)
|
||||
|
||||
self.updated.emit()
|
||||
|
||||
# self.edit_modal = PanelModal(
|
||||
# build_tag_panel,
|
||||
# title=tag.name, # TODO - display name including subtags
|
||||
# done_callback=self.driver.preview_panel.update_widgets,
|
||||
# has_save=True,
|
||||
# )
|
||||
# Translations.translate_with_setter(self.edit_modal.setWindowTitle, "tag.edit")
|
||||
# # TODO - this was update_tag()
|
||||
# self.edit_modal.saved.connect(
|
||||
# lambda: self.driver.lib.update_tag(
|
||||
# build_tag_panel.build_tag(),
|
||||
# subtag_ids=set(build_tag_panel.subtag_ids),
|
||||
# alias_names=set(build_tag_panel.alias_names),
|
||||
# alias_ids=set(build_tag_panel.alias_ids),
|
||||
# )
|
||||
# )
|
||||
# self.edit_modal.show()
|
||||
|
||||
# def edit_tag(self, tag: Tag):
|
||||
# assert isinstance(tag, Tag), f"tag is {type(tag)}"
|
||||
# build_tag_panel = BuildTagPanel(self.driver.lib, tag=tag)
|
||||
|
||||
|
||||
# def add_tag_callback(self, tag_id: int):
|
||||
# logger.info("add_tag_callback", tag_id=tag_id, selected=self.driver.selected)
|
||||
|
||||
# tag = self.driver.lib.get_tag(tag_id=tag_id)
|
||||
# for idx in self.driver.selected:
|
||||
# entry: Entry = self.driver.frame_content[idx]
|
||||
|
||||
# if not self.driver.lib.add_field_tag(entry, tag, self.field.type_key):
|
||||
# # TODO - add some visible error
|
||||
# self.error_occurred.emit(Exception("Failed to add tag"))
|
||||
|
||||
# self.updated.emit()
|
||||
|
||||
# if tag_id in (TAG_FAVORITE, TAG_ARCHIVED):
|
||||
# self.driver.update_badges()
|
||||
|
||||
# def edit_tag_callback(self, tag: Tag):
|
||||
# self.driver.lib.update_tag(tag)
|
||||
|
||||
# def remove_tag(self, tag_id: int):
|
||||
# logger.info(
|
||||
# "remove_tag",
|
||||
# selected=self.driver.selected,
|
||||
# field_type=self.field.type,
|
||||
# )
|
||||
|
||||
# for grid_idx in self.driver.selected:
|
||||
# entry = self.driver.frame_content[grid_idx]
|
||||
# self.driver.lib.remove_field_tag(entry, tag_id, self.field.type_key)
|
||||
|
||||
# self.updated.emit()
|
||||
|
||||
# if tag_id in (TAG_FAVORITE, TAG_ARCHIVED):
|
||||
# self.driver.update_badges()
|
||||
if tag_id in (TAG_FAVORITE, TAG_ARCHIVED):
|
||||
self.driver.update_badges()
|
||||
|
||||
Reference in New Issue
Block a user