mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-01-28 22:01:24 +00:00
feat: port copy/paste fields and tags to sql (#722)
* implement copy/paste for fields and tags * fix tests * fixed badge refresh on pasting * renamed translation field * ignore duplicate tags on insert * fixes * break into several statements to satisfy tests * chore: format with ruff
This commit is contained in:
@@ -186,6 +186,8 @@
|
||||
"select.add_tag_to_selected": "Add Tag to Selected",
|
||||
"select.all": "Select All",
|
||||
"select.clear": "Clear Selection",
|
||||
"edit.copy_fields": "Copy Fields",
|
||||
"edit.paste_fields": "Paste Fields",
|
||||
"settings.clear_thumb_cache.title": "Clear Thumbnail Cache",
|
||||
"settings.open_library_on_start": "Open Library on Start",
|
||||
"settings.show_filenames_in_grid": "Show Filenames in Grid",
|
||||
|
||||
@@ -1016,19 +1016,21 @@ class Library:
|
||||
|
||||
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
|
||||
tag_ids = [tag_ids] if isinstance(tag_ids, int) else tag_ids
|
||||
with Session(self.engine, expire_on_commit=False) as session:
|
||||
try:
|
||||
# TODO: Optimize this by using a single query to update.
|
||||
for tag_id in tag_ids_:
|
||||
for tag_id in tag_ids:
|
||||
try:
|
||||
session.add(TagEntry(tag_id=tag_id, entry_id=entry_id))
|
||||
session.flush()
|
||||
except IntegrityError:
|
||||
session.rollback()
|
||||
try:
|
||||
session.commit()
|
||||
return True
|
||||
except IntegrityError as e:
|
||||
logger.warning("[add_tags_to_entry]", warning=e)
|
||||
session.rollback()
|
||||
return False
|
||||
return True
|
||||
|
||||
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."""
|
||||
|
||||
@@ -426,6 +426,34 @@ class QtDriver(DriverMixin, QObject):
|
||||
clear_select_action.setToolTip("Esc")
|
||||
edit_menu.addAction(clear_select_action)
|
||||
|
||||
self.copy_buffer: dict = {"fields": [], "tags": []}
|
||||
|
||||
self.copy_fields_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(self.copy_fields_action, "edit.copy_fields")
|
||||
self.copy_fields_action.triggered.connect(self.copy_fields_action_callback)
|
||||
self.copy_fields_action.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier),
|
||||
QtCore.Qt.Key.Key_C,
|
||||
)
|
||||
)
|
||||
self.copy_fields_action.setToolTip("Ctrl+C")
|
||||
self.copy_fields_action.setEnabled(False)
|
||||
edit_menu.addAction(self.copy_fields_action)
|
||||
|
||||
self.paste_fields_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(self.paste_fields_action, "edit.paste_fields")
|
||||
self.paste_fields_action.triggered.connect(self.paste_fields_action_callback)
|
||||
self.paste_fields_action.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier),
|
||||
QtCore.Qt.Key.Key_V,
|
||||
)
|
||||
)
|
||||
self.paste_fields_action.setToolTip("Ctrl+V")
|
||||
self.paste_fields_action.setEnabled(False)
|
||||
edit_menu.addAction(self.paste_fields_action)
|
||||
|
||||
self.add_tag_to_selected_action = QAction(menu_bar)
|
||||
Translations.translate_qobject(
|
||||
self.add_tag_to_selected_action, "select.add_tag_to_selected"
|
||||
@@ -814,7 +842,9 @@ class QtDriver(DriverMixin, QObject):
|
||||
item.thumb_button.set_selected(True)
|
||||
|
||||
self.set_macro_menu_viability()
|
||||
self.set_clipboard_menu_viability()
|
||||
self.set_add_to_selected_visibility()
|
||||
|
||||
self.preview_panel.update_widgets(update_preview=False)
|
||||
|
||||
def clear_select_action_callback(self):
|
||||
@@ -824,6 +854,7 @@ class QtDriver(DriverMixin, QObject):
|
||||
item.thumb_button.set_selected(False)
|
||||
|
||||
self.set_macro_menu_viability()
|
||||
self.set_clipboard_menu_viability()
|
||||
self.preview_panel.update_widgets()
|
||||
|
||||
def add_tags_to_selected_callback(self, tag_ids: list[int]):
|
||||
@@ -1100,6 +1131,36 @@ class QtDriver(DriverMixin, QObject):
|
||||
sa.setWidgetResizable(True)
|
||||
sa.setWidget(self.flow_container)
|
||||
|
||||
def copy_fields_action_callback(self):
|
||||
if len(self.selected) > 0:
|
||||
entry = self.lib.get_entry_full(self.selected[0])
|
||||
if entry:
|
||||
self.copy_buffer["fields"] = entry.fields
|
||||
self.copy_buffer["tags"] = [tag.id for tag in entry.tags]
|
||||
self.set_clipboard_menu_viability()
|
||||
|
||||
def paste_fields_action_callback(self):
|
||||
for id in self.selected:
|
||||
entry = self.lib.get_entry_full(id, with_fields=True, with_tags=False)
|
||||
if not entry:
|
||||
continue
|
||||
existing_fields = entry.fields
|
||||
for field in self.copy_buffer["fields"]:
|
||||
exists = False
|
||||
for e in existing_fields:
|
||||
if field.type_key == e.type_key and field.value == e.value:
|
||||
exists = True
|
||||
if not exists:
|
||||
self.lib.add_field_to_entry(id, field_id=field.type_key, value=field.value)
|
||||
self.lib.add_tags_to_entry(id, self.copy_buffer["tags"])
|
||||
if len(self.selected) > 1:
|
||||
if TAG_ARCHIVED in self.copy_buffer["tags"]:
|
||||
self.update_badges({BadgeType.ARCHIVED: True}, origin_id=0, add_tags=False)
|
||||
if TAG_FAVORITE in self.copy_buffer["tags"]:
|
||||
self.update_badges({BadgeType.FAVORITE: True}, origin_id=0, add_tags=False)
|
||||
else:
|
||||
self.preview_panel.update_widgets()
|
||||
|
||||
def toggle_item_selection(self, item_id: int, append: bool, bridge: bool):
|
||||
"""Toggle the selection of an item in the Thumbnail Grid.
|
||||
|
||||
@@ -1170,12 +1231,24 @@ class QtDriver(DriverMixin, QObject):
|
||||
it.thumb_button.set_selected(False)
|
||||
|
||||
self.set_macro_menu_viability()
|
||||
self.set_clipboard_menu_viability()
|
||||
self.set_add_to_selected_visibility()
|
||||
|
||||
self.preview_panel.update_widgets()
|
||||
|
||||
def set_macro_menu_viability(self):
|
||||
self.autofill_action.setDisabled(not self.selected)
|
||||
|
||||
def set_clipboard_menu_viability(self):
|
||||
if len(self.selected) == 1:
|
||||
self.copy_fields_action.setEnabled(True)
|
||||
else:
|
||||
self.copy_fields_action.setEnabled(False)
|
||||
if self.selected and (self.copy_buffer["fields"] or self.copy_buffer["tags"]):
|
||||
self.paste_fields_action.setEnabled(True)
|
||||
else:
|
||||
self.paste_fields_action.setEnabled(False)
|
||||
|
||||
def set_add_to_selected_visibility(self):
|
||||
if not self.add_tag_to_selected_action:
|
||||
return
|
||||
|
||||
@@ -146,6 +146,10 @@ def qt_driver(qtbot, library):
|
||||
driver.item_thumbs = []
|
||||
driver.autofill_action = Mock()
|
||||
|
||||
driver.copy_buffer = {"fields": [], "tags": []}
|
||||
driver.copy_fields_action = Mock()
|
||||
driver.paste_fields_action = Mock()
|
||||
|
||||
driver.lib = library
|
||||
# TODO - downsize this method and use it
|
||||
# driver.start()
|
||||
|
||||
Reference in New Issue
Block a user