mirror of
https://github.com/TagStudioDev/TagStudio.git
synced 2026-02-01 07:39:10 +00:00
Copy and Paste + Shortcuts (#79)
* Fixed merge conflicts * fixed format? * Improve readability (Apply suggestions from code review) Co-authored-by: yed podtrzitko <yedpodtrzitko@users.noreply.github.com> * bug fix: Copy last selected not first * Fix copy entanglement; Fix paste overwriting * Change multi-selection copy to merge data - Multi-selection copy now merges fields of all selected entries - Action states are now handled --------- Co-authored-by: yed podtrzitko <yedpodtrzitko@users.noreply.github.com> Co-authored-by: Travis Abendshien <46939827+CyanVoxel@users.noreply.github.com>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
"""A Qt driver for TagStudio."""
|
||||
|
||||
import ctypes
|
||||
import copy
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
@@ -64,6 +65,7 @@ from src.core.constants import (
|
||||
TS_FOLDER_NAME,
|
||||
VERSION_BRANCH,
|
||||
VERSION,
|
||||
TEXT_FIELDS,
|
||||
TAG_FAVORITE,
|
||||
TAG_ARCHIVED,
|
||||
)
|
||||
@@ -299,6 +301,9 @@ class QtDriver(QObject):
|
||||
icon.addFile(str(icon_path))
|
||||
app.setWindowIcon(icon)
|
||||
|
||||
self.copied_fields: list[dict] = []
|
||||
self.is_buffer_merged: bool = False
|
||||
|
||||
menu_bar = QMenuBar(self.main_window)
|
||||
self.main_window.setMenuBar(menu_bar)
|
||||
menu_bar.setNativeMenuBar(True)
|
||||
@@ -392,6 +397,36 @@ class QtDriver(QObject):
|
||||
|
||||
edit_menu.addSeparator()
|
||||
|
||||
# NOTE: Name is set in update_clipboard_actions()
|
||||
self.copy_entry_fields_action = QAction(menu_bar)
|
||||
self.copy_entry_fields_action.triggered.connect(
|
||||
lambda: self.copy_entry_fields_callback()
|
||||
)
|
||||
self.copy_entry_fields_action.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier),
|
||||
QtCore.Qt.Key.Key_C,
|
||||
)
|
||||
)
|
||||
self.copy_entry_fields_action.setToolTip("Ctrl+C")
|
||||
edit_menu.addAction(self.copy_entry_fields_action)
|
||||
|
||||
# NOTE: Name is set in update_clipboard_actions()
|
||||
self.paste_entry_fields_action = QAction(menu_bar)
|
||||
self.paste_entry_fields_action.triggered.connect(
|
||||
self.paste_entry_fields_callback
|
||||
)
|
||||
self.paste_entry_fields_action.setShortcut(
|
||||
QtCore.QKeyCombination(
|
||||
QtCore.Qt.KeyboardModifier(QtCore.Qt.KeyboardModifier.ControlModifier),
|
||||
QtCore.Qt.Key.Key_V,
|
||||
)
|
||||
)
|
||||
self.paste_entry_fields_action.setToolTip("Ctrl+V")
|
||||
edit_menu.addAction(self.paste_entry_fields_action)
|
||||
|
||||
edit_menu.addSeparator()
|
||||
|
||||
select_all_action = QAction("Select All", menu_bar)
|
||||
select_all_action.triggered.connect(self.select_all_action_callback)
|
||||
select_all_action.setShortcut(
|
||||
@@ -505,6 +540,8 @@ class QtDriver(QObject):
|
||||
help_menu.addAction(self.repo_action)
|
||||
self.set_macro_menu_viability()
|
||||
|
||||
self.update_clipboard_actions()
|
||||
|
||||
menu_bar.addMenu(file_menu)
|
||||
menu_bar.addMenu(edit_menu)
|
||||
menu_bar.addMenu(tools_menu)
|
||||
@@ -700,6 +737,10 @@ class QtDriver(QObject):
|
||||
self.cur_frame_idx = -1
|
||||
self.cur_query = ""
|
||||
self.selected.clear()
|
||||
self.copied_fields.clear()
|
||||
self.is_buffer_merged = False
|
||||
self.update_clipboard_actions()
|
||||
self.set_macro_menu_viability()
|
||||
self.preview_panel.update_widgets()
|
||||
self.filter_items()
|
||||
self.main_window.toggle_landing_page(True)
|
||||
@@ -950,6 +991,114 @@ class QtDriver(QObject):
|
||||
mode="replace",
|
||||
)
|
||||
|
||||
def copy_entry_fields_callback(self):
|
||||
"""Copies fields from selected Entries into to buffer."""
|
||||
merged_fields: list[dict] = []
|
||||
merged_count: int = 0
|
||||
for item_type, item_id in self.selected:
|
||||
if item_type == ItemType.ENTRY:
|
||||
entry = self.lib.get_entry(item_id)
|
||||
|
||||
if len(entry.fields) > 0:
|
||||
merged_count += 1
|
||||
|
||||
for field in entry.fields:
|
||||
field_id: int = self.lib.get_field_attr(field, "id")
|
||||
content = self.lib.get_field_attr(field, "content")
|
||||
|
||||
if self.lib.get_field_obj(int(field_id))["type"] == "tag_box":
|
||||
existing_fields: list[int] = self.lib.get_field_index_in_entry(
|
||||
entry, field_id
|
||||
)
|
||||
if existing_fields and merged_fields:
|
||||
for i in content:
|
||||
field_index = copy.deepcopy(existing_fields[0])
|
||||
if i not in merged_fields[field_index][field_id]:
|
||||
merged_fields[field_index][field_id].append(
|
||||
copy.deepcopy(i)
|
||||
)
|
||||
else:
|
||||
merged_fields.append(copy.deepcopy({field_id: content}))
|
||||
|
||||
if self.lib.get_field_obj(int(field_id))["type"] in TEXT_FIELDS:
|
||||
if {field_id: content} not in merged_fields:
|
||||
merged_fields.append(copy.deepcopy({field_id: content}))
|
||||
|
||||
# Only set merged state to True if multiple Entries with actual field data were copied.
|
||||
if merged_count > 1:
|
||||
self.is_buffer_merged = True
|
||||
else:
|
||||
self.is_buffer_merged = False
|
||||
|
||||
self.copied_fields = merged_fields
|
||||
self.update_clipboard_actions()
|
||||
|
||||
def paste_entry_fields_callback(self):
|
||||
"""Pastes buffered fields into currently selected Entries."""
|
||||
# Code ported from ts_cli.py
|
||||
if self.copied_fields:
|
||||
for item_type, item_id in self.selected:
|
||||
if item_type == ItemType.ENTRY:
|
||||
entry = self.lib.get_entry(item_id)
|
||||
|
||||
for field in self.copied_fields:
|
||||
field_id: int = self.lib.get_field_attr(field, "id")
|
||||
content = self.lib.get_field_attr(field, "content")
|
||||
|
||||
if self.lib.get_field_obj(int(field_id))["type"] == "tag_box":
|
||||
existing_fields: list[int] = (
|
||||
self.lib.get_field_index_in_entry(entry, field_id)
|
||||
)
|
||||
if existing_fields:
|
||||
self.lib.update_entry_field(
|
||||
item_id, existing_fields[0], content, "append"
|
||||
)
|
||||
else:
|
||||
self.lib.add_field_to_entry(item_id, field_id)
|
||||
self.lib.update_entry_field(
|
||||
item_id, -1, content, "append"
|
||||
)
|
||||
|
||||
if self.lib.get_field_obj(int(field_id))["type"] in TEXT_FIELDS:
|
||||
if not self.lib.does_field_content_exist(
|
||||
item_id, field_id, content
|
||||
):
|
||||
self.lib.add_field_to_entry(item_id, field_id)
|
||||
self.lib.update_entry_field(
|
||||
item_id, -1, content, "replace"
|
||||
)
|
||||
|
||||
self.preview_panel.update_widgets()
|
||||
self.update_badges()
|
||||
self.update_clipboard_actions()
|
||||
|
||||
def update_clipboard_actions(self):
|
||||
"""Updates the text and enabled state of the field copy & paste actions."""
|
||||
# Buffer State Dependant
|
||||
if self.copied_fields:
|
||||
self.paste_entry_fields_action.setDisabled(False)
|
||||
else:
|
||||
self.paste_entry_fields_action.setDisabled(True)
|
||||
self.paste_entry_fields_action.setText("&Paste Fields")
|
||||
|
||||
# Selection Count Dependant
|
||||
if len(self.selected) <= 0:
|
||||
self.copy_entry_fields_action.setDisabled(True)
|
||||
self.paste_entry_fields_action.setDisabled(True)
|
||||
self.copy_entry_fields_action.setText("&Copy Fields")
|
||||
if len(self.selected) == 1:
|
||||
self.copy_entry_fields_action.setDisabled(False)
|
||||
self.copy_entry_fields_action.setText("&Copy Fields")
|
||||
elif len(self.selected) > 1:
|
||||
self.copy_entry_fields_action.setDisabled(False)
|
||||
self.copy_entry_fields_action.setText("&Copy Combined Fields")
|
||||
|
||||
# Merged State Dependant
|
||||
if self.is_buffer_merged:
|
||||
self.paste_entry_fields_action.setText("&Paste Combined Fields")
|
||||
else:
|
||||
self.paste_entry_fields_action.setText("&Paste Fields")
|
||||
|
||||
def mouse_navigation(self, event: QMouseEvent):
|
||||
# print(event.button())
|
||||
if event.button() == Qt.MouseButton.ForwardButton:
|
||||
@@ -1197,6 +1346,7 @@ class QtDriver(QObject):
|
||||
self.preview_panel.set_tags_updated_slot(it.update_badges)
|
||||
|
||||
self.set_macro_menu_viability()
|
||||
self.update_clipboard_actions()
|
||||
self.preview_panel.update_widgets()
|
||||
|
||||
def set_macro_menu_viability(self):
|
||||
|
||||
Reference in New Issue
Block a user