Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy and Paste + Shortcuts #79

Merged
merged 8 commits into from
Jul 19, 2024
Merged
150 changes: 150 additions & 0 deletions tagstudio/src/qt/ts_qt.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"""A Qt driver for TagStudio."""

import ctypes
import copy
import logging
import math
import os
Expand Down Expand Up @@ -64,6 +65,7 @@
TS_FOLDER_NAME,
VERSION_BRANCH,
VERSION,
TEXT_FIELDS,
TAG_FAVORITE,
TAG_ARCHIVED,
)
Expand Down Expand Up @@ -299,6 +301,9 @@ def start(self) -> None:
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)
Expand Down Expand Up @@ -392,6 +397,36 @@ def start(self) -> None:

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(
Expand Down Expand Up @@ -505,6 +540,8 @@ def start(self) -> None:
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)
Expand Down Expand Up @@ -700,6 +737,10 @@ def close_library(self):
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)
Expand Down Expand Up @@ -950,6 +991,114 @@ def run_macro(self, name: str, entry_id: int):
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:
Expand Down Expand Up @@ -1197,6 +1346,7 @@ def select_item(self, type: ItemType, id: int, append: bool, bridge: bool):
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):
Expand Down