mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-01 15:49:09 +00:00
refactor: use entry IDs instead of objects and indices
- fixes preview panel not updating after entry edits - fixes slow selection performance - fixes double render call
This commit is contained in:
@@ -420,7 +420,7 @@ class Library:
|
||||
return entry
|
||||
|
||||
def get_entry_full(self, entry_id: int) -> Entry | None:
|
||||
"""Load entry an join with all joins and all tags."""
|
||||
"""Load entry and join with all joins and all tags."""
|
||||
with Session(self.engine) as session:
|
||||
statement = select(Entry).where(Entry.id == entry_id)
|
||||
statement = (
|
||||
@@ -454,7 +454,9 @@ class Library:
|
||||
if with_joins:
|
||||
# load Entry with all joins and all tags
|
||||
stmt = (
|
||||
stmt.outerjoin(Entry.text_fields).outerjoin(Entry.datetime_fields)
|
||||
stmt.outerjoin(Entry.text_fields)
|
||||
.outerjoin(Entry.datetime_fields)
|
||||
.outerjoin(Entry.tags)
|
||||
# .outerjoin(Entry.tag_box_fields)
|
||||
)
|
||||
stmt = stmt.options(
|
||||
@@ -935,6 +937,7 @@ class Library:
|
||||
return None
|
||||
|
||||
def add_tags_to_entry(self, entry_id: int, tag_ids: int | list[int] | set[int]) -> bool:
|
||||
"""Add one or more tags to an entry."""
|
||||
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else tag_ids
|
||||
with Session(self.engine, expire_on_commit=False) as session:
|
||||
try:
|
||||
@@ -943,6 +946,30 @@ class Library:
|
||||
session.flush()
|
||||
session.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
logger.warning("[add_tags_to_entry]", warning=e)
|
||||
session.rollback()
|
||||
return False
|
||||
|
||||
def remove_tags_from_entry(self, entry_id: int, tag_ids: int | list[int] | set[int]) -> bool:
|
||||
"""Remove one or more tags from an entry."""
|
||||
tag_ids_ = [tag_ids] if isinstance(tag_ids, int) else tag_ids
|
||||
with Session(self.engine, expire_on_commit=False) as session:
|
||||
try:
|
||||
for tag_id in tag_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.commit()
|
||||
session.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
logger.exception(e)
|
||||
session.rollback()
|
||||
|
||||
@@ -146,7 +146,7 @@ class Entry(Base):
|
||||
return fields
|
||||
|
||||
@property
|
||||
def is_favorited(self) -> bool:
|
||||
def is_favorite(self) -> bool:
|
||||
return any(tag.id == TAG_FAVORITE for tag in self.tags)
|
||||
|
||||
@property
|
||||
|
||||
@@ -16,7 +16,6 @@ import sys
|
||||
import time
|
||||
import webbrowser
|
||||
from collections.abc import Sequence
|
||||
from itertools import zip_longest
|
||||
from pathlib import Path
|
||||
from queue import Queue
|
||||
|
||||
@@ -53,8 +52,6 @@ from PySide6.QtWidgets import (
|
||||
QWidget,
|
||||
)
|
||||
from src.core.constants import (
|
||||
TAG_ARCHIVED,
|
||||
TAG_FAVORITE,
|
||||
VERSION,
|
||||
VERSION_BRANCH,
|
||||
)
|
||||
@@ -137,7 +134,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.rm: ResourceManager = ResourceManager()
|
||||
self.args = args
|
||||
self.filter = FilterState.show_all()
|
||||
self.frame_content: list[Entry] = []
|
||||
self.frame_content: list[int] = [] # List of Entry IDs on the current page
|
||||
self.pages_count = 0
|
||||
|
||||
self.scrollbar_pos = 0
|
||||
@@ -151,9 +148,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.thumb_job_queue: Queue = Queue()
|
||||
self.thumb_threads: list[Consumer] = []
|
||||
self.thumb_cutoff: float = time.time()
|
||||
|
||||
# grid indexes of selected items
|
||||
self.selected: list[int] = []
|
||||
self.selected: list[int] = [] # Selected Entry IDs
|
||||
|
||||
self.SIGTERM.connect(self.handle_sigterm)
|
||||
|
||||
@@ -589,11 +584,13 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.preview_panel.update_widgets()
|
||||
|
||||
def toggle_libs_list(self, value: bool):
|
||||
if value:
|
||||
self.preview_panel.libs_flow_container.show()
|
||||
else:
|
||||
self.preview_panel.libs_flow_container.hide()
|
||||
self.preview_panel.update()
|
||||
# TODO: Reimplement or remove
|
||||
# if value:
|
||||
# self.preview_panel.libs_flow_container.show()
|
||||
# else:
|
||||
# self.preview_panel.libs_flow_container.hide()
|
||||
# self.preview_panel.update()
|
||||
pass
|
||||
|
||||
def show_grid_filenames(self, value: bool):
|
||||
for thumb in self.item_thumbs:
|
||||
@@ -695,10 +692,12 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.modal.show()
|
||||
|
||||
def select_all_action_callback(self):
|
||||
self.selected = list(range(0, len(self.frame_content)))
|
||||
|
||||
for grid_idx in self.selected:
|
||||
self.item_thumbs[grid_idx].thumb_button.set_selected(True)
|
||||
"""Set the selection to all visible items."""
|
||||
self.selected.clear()
|
||||
for item in self.item_thumbs:
|
||||
if item.mode and item.item_id not in self.selected:
|
||||
self.selected.append(item.item_id)
|
||||
item.thumb_button.set_selected(True)
|
||||
|
||||
self.set_macro_menu_viability()
|
||||
self.preview_panel.update_widgets()
|
||||
@@ -829,14 +828,14 @@ class QtDriver(DriverMixin, QObject):
|
||||
# sleep(5)
|
||||
# pb.deleteLater()
|
||||
|
||||
def run_macros(self, name: MacroID, grid_idx: list[int]):
|
||||
def run_macros(self, name: MacroID, entry_ids: list[int]):
|
||||
"""Run a specific Macro on a group of given entry_ids."""
|
||||
for gid in grid_idx:
|
||||
self.run_macro(name, gid)
|
||||
for entry_id in entry_ids:
|
||||
self.run_macro(name, entry_id)
|
||||
|
||||
def run_macro(self, name: MacroID, grid_idx: int):
|
||||
def run_macro(self, name: MacroID, entry_id: int):
|
||||
"""Run a specific Macro on an Entry given a Macro name."""
|
||||
entry: Entry = self.frame_content[grid_idx]
|
||||
entry: Entry = self.lib.get_entry(entry_id)
|
||||
full_path = self.lib.library_dir / entry.path
|
||||
source = "" if entry.path.parent == Path(".") else entry.path.parts[0].lower()
|
||||
|
||||
@@ -845,14 +844,14 @@ class QtDriver(DriverMixin, QObject):
|
||||
source=source,
|
||||
macro=name,
|
||||
entry_id=entry.id,
|
||||
grid_idx=grid_idx,
|
||||
grid_idx=entry_id,
|
||||
)
|
||||
|
||||
if name == MacroID.AUTOFILL:
|
||||
for macro_id in MacroID:
|
||||
if macro_id == MacroID.AUTOFILL:
|
||||
continue
|
||||
self.run_macro(macro_id, grid_idx)
|
||||
self.run_macro(macro_id, entry_id)
|
||||
|
||||
elif name == MacroID.SIDECAR:
|
||||
parsed_items = TagStudioCore.get_gdl_sidecar(full_path, source)
|
||||
@@ -949,13 +948,12 @@ class QtDriver(DriverMixin, QObject):
|
||||
layout.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# TODO - init after library is loaded, it can have different page_size
|
||||
for grid_idx in range(self.filter.page_size):
|
||||
for _ in range(self.filter.page_size):
|
||||
item_thumb = ItemThumb(
|
||||
None,
|
||||
self.lib,
|
||||
self,
|
||||
(self.thumb_size, self.thumb_size),
|
||||
grid_idx,
|
||||
bool(
|
||||
self.settings.value(SettingItems.SHOW_FILENAMES, defaultValue=True, type=bool)
|
||||
),
|
||||
@@ -972,44 +970,55 @@ class QtDriver(DriverMixin, QObject):
|
||||
sa.setWidgetResizable(True)
|
||||
sa.setWidget(self.flow_container)
|
||||
|
||||
def select_item(self, grid_index: int, append: bool, bridge: bool):
|
||||
def select_item(self, item_id: int, append: bool, bridge: bool):
|
||||
"""Select one or more items in the Thumbnail Grid."""
|
||||
logger.info("selecting item", grid_index=grid_index, append=append, bridge=bridge)
|
||||
logger.info("[QtDriver] Selecting Items:", item_id=item_id, append=append, bridge=bridge)
|
||||
if append:
|
||||
if grid_index not in self.selected:
|
||||
self.selected.append(grid_index)
|
||||
self.item_thumbs[grid_index].thumb_button.set_selected(True)
|
||||
if item_id not in self.selected:
|
||||
self.selected.append(item_id)
|
||||
for it in self.item_thumbs:
|
||||
if it.item_id == item_id:
|
||||
it.thumb_button.set_selected(True)
|
||||
else:
|
||||
self.selected.remove(grid_index)
|
||||
self.item_thumbs[grid_index].thumb_button.set_selected(False)
|
||||
self.selected.remove(item_id)
|
||||
for it in self.item_thumbs:
|
||||
if it.item_id == item_id:
|
||||
it.thumb_button.set_selected(False)
|
||||
|
||||
elif bridge and self.selected:
|
||||
select_from = min(self.selected)
|
||||
select_to = max(self.selected)
|
||||
contents = self.frame_content
|
||||
last_index = self.frame_content.index(self.selected[-1])
|
||||
current_index = self.frame_content.index(item_id)
|
||||
index_range: list = contents[
|
||||
min(last_index, current_index) : max(last_index, current_index) + 1
|
||||
]
|
||||
# Preserve bridge direction for correct appending order.
|
||||
if last_index < current_index:
|
||||
index_range.reverse()
|
||||
for entry_id in index_range:
|
||||
for it in self.item_thumbs:
|
||||
if it.item_id == entry_id:
|
||||
it.thumb_button.set_selected(True)
|
||||
if entry_id not in self.selected:
|
||||
self.selected.append(entry_id)
|
||||
|
||||
if select_to < grid_index:
|
||||
index_range = range(select_from, grid_index + 1)
|
||||
else:
|
||||
index_range = range(grid_index, select_to + 1)
|
||||
|
||||
self.selected = list(index_range)
|
||||
|
||||
for selected_idx in self.selected:
|
||||
self.item_thumbs[selected_idx].thumb_button.set_selected(True)
|
||||
else:
|
||||
self.selected = [grid_index]
|
||||
for thumb_idx, item_thumb in enumerate(self.item_thumbs):
|
||||
item_matched = thumb_idx == grid_index
|
||||
item_thumb.thumb_button.set_selected(item_matched)
|
||||
|
||||
# NOTE: By using the preview panel's "set_tags_updated_slot" method,
|
||||
# only the last of multiple identical item selections are connected.
|
||||
# If attaching the slot to multiple duplicate selections is needed,
|
||||
# just bypass the method and manually disconnect and connect the slots.
|
||||
if len(self.selected) == 1:
|
||||
self.selected.clear()
|
||||
self.selected.append(item_id)
|
||||
for it in self.item_thumbs:
|
||||
if it.item_id == id:
|
||||
self.preview_panel.set_tags_updated_slot(it.refresh_badge)
|
||||
if it.item_id == item_id:
|
||||
it.thumb_button.set_selected(True)
|
||||
else:
|
||||
it.thumb_button.set_selected(False)
|
||||
|
||||
# # NOTE: By using the preview panel's "set_tags_updated_slot" method,
|
||||
# # only the last of multiple identical item selections are connected.
|
||||
# # If attaching the slot to multiple duplicate selections is needed,
|
||||
# # just bypass the method and manually disconnect and connect the slots.
|
||||
# if len(self.selected) == 1:
|
||||
# for it in self.item_thumbs:
|
||||
# if it.item_id == item_id:
|
||||
# self.preview_panel.set_tags_updated_slot(it.refresh_badge)
|
||||
|
||||
self.set_macro_menu_viability()
|
||||
self.preview_panel.update_widgets()
|
||||
@@ -1106,18 +1115,26 @@ class QtDriver(DriverMixin, QObject):
|
||||
self.main_window.update()
|
||||
|
||||
is_grid_thumb = True
|
||||
# Show loading placeholder icons
|
||||
for entry, item_thumb in zip_longest(self.frame_content, self.item_thumbs):
|
||||
if not entry:
|
||||
logger.info("[QtDriver] Loading Entries...")
|
||||
# TODO: Grab all entries at once
|
||||
entries: list[Entry] = [self.lib.get_entry_full(e_id) for e_id in self.frame_content]
|
||||
logger.info("[QtDriver] Building Filenames...")
|
||||
filenames: list[Path] = [self.lib.library_dir / e.path for e in entries]
|
||||
logger.info("[QtDriver] Done! Processing ItemThumbs...")
|
||||
for index, item_thumb in enumerate(self.item_thumbs, start=0):
|
||||
entry = None
|
||||
try:
|
||||
entry = entries[index]
|
||||
except IndexError:
|
||||
item_thumb.hide()
|
||||
continue
|
||||
|
||||
if not entry:
|
||||
continue
|
||||
item_thumb.set_mode(ItemType.ENTRY)
|
||||
item_thumb.set_item_id(entry)
|
||||
item_thumb.set_item_id(entry.id)
|
||||
|
||||
# TODO - show after item is rendered
|
||||
item_thumb.show()
|
||||
|
||||
is_loading = True
|
||||
self.thumb_job_queue.put(
|
||||
(
|
||||
@@ -1127,29 +1144,29 @@ class QtDriver(DriverMixin, QObject):
|
||||
)
|
||||
|
||||
# Show rendered thumbnails
|
||||
for idx, (entry, item_thumb) in enumerate(
|
||||
zip_longest(self.frame_content, self.item_thumbs)
|
||||
):
|
||||
for index, item_thumb in enumerate(self.item_thumbs, start=0):
|
||||
entry = None
|
||||
try:
|
||||
entry = entries[index]
|
||||
except IndexError:
|
||||
item_thumb.hide()
|
||||
continue
|
||||
if not entry:
|
||||
continue
|
||||
|
||||
filepath = self.lib.library_dir / entry.path
|
||||
is_loading = False
|
||||
|
||||
self.thumb_job_queue.put(
|
||||
(
|
||||
item_thumb.renderer.render,
|
||||
(time.time(), filepath, base_size, ratio, is_loading, is_grid_thumb),
|
||||
(time.time(), filenames[index], base_size, ratio, is_loading, is_grid_thumb),
|
||||
)
|
||||
)
|
||||
|
||||
entry_tag_ids = {tag.id for tag in entry.tags}
|
||||
item_thumb.assign_badge(BadgeType.ARCHIVED, TAG_ARCHIVED in entry_tag_ids)
|
||||
item_thumb.assign_badge(BadgeType.FAVORITE, TAG_FAVORITE in entry_tag_ids)
|
||||
item_thumb.assign_badge(BadgeType.ARCHIVED, entry.is_archived)
|
||||
item_thumb.assign_badge(BadgeType.FAVORITE, entry.is_favorite)
|
||||
item_thumb.update_clickable(
|
||||
clickable=(
|
||||
lambda checked=False, index=idx: self.select_item(
|
||||
index,
|
||||
lambda checked=False, item_id=entry.id: self.select_item(
|
||||
item_id,
|
||||
append=(
|
||||
QGuiApplication.keyboardModifiers()
|
||||
== Qt.KeyboardModifier.ControlModifier
|
||||
@@ -1165,24 +1182,15 @@ class QtDriver(DriverMixin, QObject):
|
||||
is_selected = (item_thumb.mode, item_thumb.item_id) in self.selected
|
||||
item_thumb.thumb_button.set_selected(is_selected)
|
||||
|
||||
self.thumb_job_queue.put(
|
||||
(
|
||||
item_thumb.renderer.render,
|
||||
(time.time(), filepath, base_size, ratio, False, True),
|
||||
)
|
||||
)
|
||||
|
||||
def update_badges(self, grid_item_ids: Sequence[int] = None):
|
||||
if not grid_item_ids:
|
||||
def update_badges(self, item_ids: Sequence[int] = None):
|
||||
if not item_ids:
|
||||
# no items passed, update all items in grid
|
||||
grid_item_ids = range(min(len(self.item_thumbs), len(self.frame_content)))
|
||||
item_ids = range(min(len(self.item_thumbs), len(self.frame_content)))
|
||||
|
||||
logger.info("updating badges for items", grid_item_ids=grid_item_ids)
|
||||
|
||||
for grid_idx in grid_item_ids:
|
||||
# get the entry from grid to avoid loading from db again
|
||||
entry = self.frame_content[grid_idx]
|
||||
self.item_thumbs[grid_idx].refresh_badge(entry)
|
||||
item_ids_ = set(item_ids)
|
||||
for it in self.item_thumbs:
|
||||
if it.item_id in item_ids_:
|
||||
it.refresh_badge()
|
||||
|
||||
def filter_items(self, filter: FilterState | None = None) -> None:
|
||||
if not self.lib.library_dir:
|
||||
@@ -1217,7 +1225,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
)
|
||||
|
||||
# update page content
|
||||
self.frame_content = results.items
|
||||
self.frame_content = [item.id for item in results.items]
|
||||
self.update_thumbs()
|
||||
|
||||
# update pagination
|
||||
|
||||
@@ -24,7 +24,7 @@ from src.core.constants import (
|
||||
TAG_ARCHIVED,
|
||||
TAG_FAVORITE,
|
||||
)
|
||||
from src.core.library import Entry, ItemType, Library
|
||||
from src.core.library import ItemType, Library
|
||||
from src.core.media_types import MediaCategories, MediaType
|
||||
from src.qt.flowlayout import FlowWidget
|
||||
from src.qt.helpers.file_opener import FileOpenerHelper
|
||||
@@ -118,11 +118,9 @@ class ItemThumb(FlowWidget):
|
||||
library: Library,
|
||||
driver: "QtDriver",
|
||||
thumb_size: tuple[int, int],
|
||||
grid_idx: int,
|
||||
show_filename_label: bool = False,
|
||||
):
|
||||
super().__init__()
|
||||
self.grid_idx = grid_idx
|
||||
self.lib = library
|
||||
self.mode: ItemType = mode
|
||||
self.driver = driver
|
||||
@@ -205,10 +203,10 @@ class ItemThumb(FlowWidget):
|
||||
self.thumb_button = ThumbButton(self.thumb_container, thumb_size)
|
||||
self.renderer = ThumbRenderer()
|
||||
self.renderer.updated.connect(
|
||||
lambda ts, i, s, fn, ext: (
|
||||
self.update_thumb(ts, image=i),
|
||||
self.update_size(ts, size=s),
|
||||
self.set_filename_text(fn),
|
||||
lambda timestamp, image, size, filename, ext: (
|
||||
self.update_thumb(timestamp, image=image),
|
||||
self.update_size(timestamp, size=size),
|
||||
self.set_filename_text(filename),
|
||||
self.set_extension(ext),
|
||||
)
|
||||
)
|
||||
@@ -399,6 +397,7 @@ class ItemThumb(FlowWidget):
|
||||
self.count_badge.setHidden(True)
|
||||
|
||||
def set_filename_text(self, filename: Path | str | None):
|
||||
self.set_item_path(filename)
|
||||
self.file_label.setText(str(filename))
|
||||
|
||||
def set_filename_visibility(self, set_visible: bool):
|
||||
@@ -444,24 +443,19 @@ class ItemThumb(FlowWidget):
|
||||
self.thumb_button.pressed.connect(clickable)
|
||||
self.thumb_button.is_connected = True
|
||||
|
||||
def refresh_badge(self, entry: Entry | None = None):
|
||||
def refresh_badge(self, entry_id: int | None = None):
|
||||
entry = self.lib.get_entry_full(self.item_id)
|
||||
if not entry:
|
||||
if not self.item_id:
|
||||
logger.error("missing both entry and item_id")
|
||||
return None
|
||||
|
||||
entry = self.lib.get_entry(self.item_id)
|
||||
if not entry:
|
||||
logger.error("Entry not found", item_id=self.item_id)
|
||||
return
|
||||
|
||||
return
|
||||
self.assign_badge(BadgeType.ARCHIVED, entry.is_archived)
|
||||
self.assign_badge(BadgeType.FAVORITE, entry.is_favorited)
|
||||
self.assign_badge(BadgeType.FAVORITE, entry.is_favorite)
|
||||
|
||||
def set_item_id(self, entry: Entry):
|
||||
filepath = self.lib.library_dir / entry.path
|
||||
self.opener.set_filepath(filepath)
|
||||
self.item_id = entry.id
|
||||
def set_item_id(self, item_id: int):
|
||||
self.item_id = item_id
|
||||
|
||||
def set_item_path(self, path: Path | str | None):
|
||||
"""Set the absolute filepath for the item. Used for locating on disk."""
|
||||
self.opener.set_filepath(path)
|
||||
|
||||
def assign_badge(self, badge_type: BadgeType, value: bool) -> None:
|
||||
mode = self.mode
|
||||
@@ -500,18 +494,15 @@ class ItemThumb(FlowWidget):
|
||||
tag_id = BADGE_TAGS[badge_type]
|
||||
|
||||
# check if current item is selected. if so, update all selected items
|
||||
if self.grid_idx in self.driver.selected:
|
||||
update_items = self.driver.selected
|
||||
if self.item_id in self.driver.selected:
|
||||
items_to_update = self.driver.selected
|
||||
else:
|
||||
update_items = [self.grid_idx]
|
||||
items_to_update = [self.item_id]
|
||||
|
||||
for idx in update_items:
|
||||
entry = self.driver.frame_content[idx]
|
||||
self.toggle_item_tag(entry.id, toggle_value, tag_id)
|
||||
# update the entry
|
||||
self.driver.frame_content[idx] = self.lib.get_entry_full(entry.id)
|
||||
for item_id in items_to_update:
|
||||
self.toggle_item_tag(item_id, toggle_value, tag_id)
|
||||
|
||||
self.driver.update_badges(update_items)
|
||||
self.driver.update_badges(items_to_update)
|
||||
|
||||
def toggle_item_tag(
|
||||
self,
|
||||
@@ -524,8 +515,7 @@ class ItemThumb(FlowWidget):
|
||||
if toggle_value:
|
||||
self.lib.add_tags_to_entry(entry_id, tag_id)
|
||||
else:
|
||||
# TODO: Implement
|
||||
self.lib.remove_tag_from_entry(entry_id, tag_id)
|
||||
self.lib.remove_tags_from_entry(entry_id, tag_id)
|
||||
|
||||
if self.driver.preview_panel.is_open:
|
||||
self.driver.preview_panel.update_widgets()
|
||||
@@ -538,13 +528,13 @@ class ItemThumb(FlowWidget):
|
||||
paths = []
|
||||
mimedata = QMimeData()
|
||||
|
||||
selected_idxs = self.driver.selected
|
||||
if self.grid_idx not in selected_idxs:
|
||||
selected_idxs = [self.grid_idx]
|
||||
selected_ids = self.driver.selected
|
||||
if self.item_id not in selected_ids:
|
||||
selected_ids = [self.item_id]
|
||||
|
||||
for grid_idx in selected_idxs:
|
||||
id = self.driver.item_thumbs[grid_idx].item_id
|
||||
entry = self.lib.get_entry(id)
|
||||
for selected_id in selected_ids:
|
||||
item_id = self.driver.item_thumbs[selected_id].item_id
|
||||
entry = self.lib.get_entry(item_id)
|
||||
if not entry:
|
||||
continue
|
||||
|
||||
@@ -554,4 +544,4 @@ class ItemThumb(FlowWidget):
|
||||
mimedata.setUrls(paths)
|
||||
drag.setMimeData(mimedata)
|
||||
drag.exec(Qt.DropAction.CopyAction)
|
||||
logger.info("dragged files to external program", thumbnail_indexs=selected_idxs)
|
||||
logger.info("dragged files to external program", thumbnail_indexs=selected_ids)
|
||||
|
||||
@@ -119,7 +119,7 @@ class FieldContainers(QWidget):
|
||||
|
||||
def update_from_entry(self, entry: Entry):
|
||||
"""Update tags and fields from a single Entry source."""
|
||||
self.selected = [entry]
|
||||
self.selected = [self.lib.get_entry_full(entry.id)]
|
||||
logger.info(
|
||||
"[Field Containers] Updating Selection",
|
||||
entry=entry,
|
||||
@@ -127,33 +127,17 @@ class FieldContainers(QWidget):
|
||||
tags=entry.tags,
|
||||
)
|
||||
|
||||
# # 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]
|
||||
|
||||
# for index in self.driver.selected:
|
||||
# self.driver.frame_content[index] = self.lib.get_entry(self.selected[0].id)
|
||||
|
||||
for idx, field in enumerate(entry.fields):
|
||||
entry_ = self.selected[0]
|
||||
for idx, field in enumerate(entry_.fields):
|
||||
self.write_container(idx, field, is_mixed=False)
|
||||
if entry.tags:
|
||||
if entry_.tags:
|
||||
# TODO: Display the tag categories
|
||||
pass
|
||||
|
||||
# Hide leftover containers
|
||||
if len(self.containers) > len(entry.fields):
|
||||
if len(self.containers) > len(entry_.fields):
|
||||
for i, c in enumerate(self.containers):
|
||||
if i > (len(entry.fields) - 1):
|
||||
if i > (len(entry_.fields) - 1):
|
||||
c.setHidden(True)
|
||||
|
||||
self.add_field_button.setHidden(False)
|
||||
@@ -170,17 +154,16 @@ class FieldContainers(QWidget):
|
||||
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_widgets())
|
||||
# )
|
||||
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)
|
||||
for grid_idx in self.selected:
|
||||
entry = self.driver.frame_content[grid_idx]
|
||||
for entry in self.selected:
|
||||
for field_item in field_list:
|
||||
self.lib.add_entry_field_type(
|
||||
entry.id,
|
||||
@@ -393,12 +376,7 @@ class FieldContainers(QWidget):
|
||||
def remove_field(self, field: BaseField):
|
||||
"""Remove a field from all selected Entries."""
|
||||
logger.info("removing field", field=field, selected=self.selected)
|
||||
entry_ids = []
|
||||
|
||||
for grid_idx in self.selected:
|
||||
entry = self.driver.frame_content[grid_idx]
|
||||
entry_ids.append(entry.id)
|
||||
|
||||
entry_ids = [e.id for e in self.selected]
|
||||
self.lib.remove_entry_field(field, entry_ids)
|
||||
|
||||
# # if the field is meta tags, update the badges
|
||||
@@ -412,12 +390,7 @@ class FieldContainers(QWidget):
|
||||
(TextField, DatetimeField), # , TagBoxField)
|
||||
), f"instance: {type(field)}"
|
||||
|
||||
entry_ids = []
|
||||
# for grid_idx in self.selected:
|
||||
# entry = self.driver.frame_content[grid_idx]
|
||||
# entry_ids.append(entry.id)
|
||||
for entry in self.selected:
|
||||
entry_ids.append(entry.id)
|
||||
entry_ids = [e.id for e in self.selected]
|
||||
|
||||
assert entry_ids, "No entries selected"
|
||||
self.lib.update_entry_field(
|
||||
|
||||
@@ -200,7 +200,6 @@ class FileAttributes(QWidget):
|
||||
|
||||
# Format and display any stat variables
|
||||
def add_newline(stats_label_text: str) -> str:
|
||||
logger.info(stats_label_text[-2:])
|
||||
if stats_label_text and stats_label_text[-2:] != "\n":
|
||||
return stats_label_text + "\n"
|
||||
return stats_label_text
|
||||
|
||||
@@ -8,7 +8,7 @@ from pathlib import Path
|
||||
|
||||
import structlog
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
from PySide6.QtWidgets import QHBoxLayout, QSplitter, QWidget
|
||||
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.widgets.preview.field_containers import FieldContainers
|
||||
@@ -40,13 +40,15 @@ class PreviewPanel(QWidget):
|
||||
self.file_attrs = FileAttributes(library, driver)
|
||||
self.fields = FieldContainers(library, driver)
|
||||
|
||||
# info_section = QWidget()
|
||||
# info_layout = QVBoxLayout(info_section)
|
||||
# info_layout.setContentsMargins(0, 0, 0, 0)
|
||||
# info_layout.setSpacing(6)
|
||||
preview_section = QWidget()
|
||||
preview_layout = QVBoxLayout(preview_section)
|
||||
preview_layout.setContentsMargins(0, 0, 0, 0)
|
||||
preview_layout.setSpacing(6)
|
||||
|
||||
# info_layout.addWidget(self.file_attrs)
|
||||
# info_layout.addWidget(self.fields.scroll_area)
|
||||
info_section = QWidget()
|
||||
info_layout = QVBoxLayout(info_section)
|
||||
info_layout.setContentsMargins(0, 0, 0, 0)
|
||||
info_layout.setSpacing(6)
|
||||
|
||||
splitter = QSplitter()
|
||||
splitter.setOrientation(Qt.Orientation.Vertical)
|
||||
@@ -59,35 +61,24 @@ class PreviewPanel(QWidget):
|
||||
# )
|
||||
# )
|
||||
# )
|
||||
preview_layout.addWidget(self.thumb)
|
||||
preview_layout.addWidget(self.thumb.media_player)
|
||||
info_layout.addWidget(self.file_attrs)
|
||||
info_layout.addWidget(self.fields)
|
||||
|
||||
# splitter.addWidget(self.thumb.image_container)
|
||||
# splitter.addWidget(self.thumb.media_player)
|
||||
splitter.addWidget(self.thumb)
|
||||
splitter.addWidget(self.thumb.media_player)
|
||||
splitter.addWidget(self.file_attrs)
|
||||
splitter.addWidget(self.fields)
|
||||
splitter.addWidget(preview_section)
|
||||
splitter.addWidget(info_section)
|
||||
# splitter.addWidget(self.libs_flow_container)
|
||||
splitter.setStretchFactor(3, 2)
|
||||
splitter.setStretchFactor(1, 2)
|
||||
|
||||
root_layout = QHBoxLayout(self)
|
||||
root_layout.setContentsMargins(0, 0, 0, 0)
|
||||
root_layout.addWidget(splitter)
|
||||
|
||||
def update_selected_entry(self, driver: "QtDriver"):
|
||||
for grid_idx in driver.selected:
|
||||
entry = driver.frame_content[grid_idx]
|
||||
result = self.lib.get_entry_full(entry.id)
|
||||
logger.info(
|
||||
"found item",
|
||||
grid_idx=grid_idx,
|
||||
lookup_id=entry.id,
|
||||
)
|
||||
self.driver.frame_content[grid_idx] = result
|
||||
|
||||
def update_widgets(self) -> bool:
|
||||
"""Render the panel widgets with the newest data from the Library."""
|
||||
# No Items Selected
|
||||
items: list[Entry] = [self.driver.frame_content[x] for x in self.driver.selected]
|
||||
# items: list[Entry] = [self.driver.frame_content[x] for x in self.driver.selected]
|
||||
if len(self.driver.selected) == 0:
|
||||
# TODO: Clear everything to default
|
||||
# self.file_attrs.update_blank()
|
||||
@@ -96,7 +87,7 @@ class PreviewPanel(QWidget):
|
||||
|
||||
# One Item Selected
|
||||
elif len(self.driver.selected) == 1:
|
||||
entry: Entry = items[0]
|
||||
entry: Entry = self.lib.get_entry_full(self.driver.selected[0])
|
||||
filepath: Path = self.lib.library_dir / entry.path
|
||||
ext: str = filepath.suffix.lower()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user