Tag browser: Allow adding/removing tags/authors/etc. to the currently selected book by right clicking on that tag and choosing "Apply to selected books". Fixes #1878308 [[Enhancement] Drag to remove tags](https://bugs.launchpad.net/calibre/+bug/1878308)

This commit is contained in:
Kovid Goyal 2020-05-14 18:17:29 +05:30
parent 986aff2890
commit a2b61da9c7
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 88 additions and 6 deletions

View File

@ -22,7 +22,7 @@ from calibre.ebooks.metadata import title_sort
from calibre.gui2.dialogs.tag_categories import TagCategories
from calibre.gui2.dialogs.tag_list_editor import TagListEditor
from calibre.gui2.dialogs.edit_authors_dialog import EditAuthorsDialog
from polyglot.builtins import unicode_type
from polyglot.builtins import unicode_type, iteritems
class TagBrowserMixin(object): # {{{
@ -87,6 +87,7 @@ class TagBrowserMixin(object): # {{{
self.tags_view.restriction_error.connect(self.do_restriction_error,
type=Qt.QueuedConnection)
self.tags_view.tag_item_delete.connect(self.do_tag_item_delete)
self.tags_view.apply_tag_to_selected.connect(self.apply_tag_to_selected)
self.populate_tb_manage_menu(db)
self.tags_view.model().user_categories_edited.connect(self.user_categories_edited,
type=Qt.QueuedConnection)
@ -299,6 +300,48 @@ class TagBrowserMixin(object): # {{{
self.do_tag_item_renamed()
self.tags_view.recount()
def apply_tag_to_selected(self, field_name, item_name, remove):
db = self.current_db.new_api
fm = db.field_metadata.get(field_name)
if fm is None:
return
book_ids = self.library_view.get_selected_ids()
if not book_ids:
return error_dialog(self.library_view, _('No books selected'), _(
'You must select some books to apply {} to').format(item_name), show=True)
existing_values = db.all_field_for(field_name, book_ids)
series_index_field = None
if fm['datatype'] == 'series':
series_index_field = field_name + '_index'
changes = {}
for book_id, existing in iteritems(existing_values):
if isinstance(existing, tuple):
existing = list(existing)
if remove:
try:
existing.remove(item_name)
except ValueError:
continue
changes[book_id] = existing
else:
if item_name not in existing:
changes[book_id] = existing + [item_name]
else:
if remove:
if existing == item_name:
changes[book_id] = None
else:
if existing != item_name:
changes[book_id] = item_name
if changes:
db.set_field(field_name, changes)
if series_index_field is not None:
for book_id in changes:
si = db.get_next_series_num_for(item_name, field=field_name)
db.set_field(series_index_field, {book_id: si})
self.library_view.model().refresh_ids(set(changes), current_row=self.library_view.currentIndex().row())
self.tags_view.recount_with_position_based_index()
def do_tag_item_renamed(self):
# Clean up library view and search
# get information to redo the selection

View File

@ -154,6 +154,7 @@ class TagsView(QTreeView): # {{{
drag_drop_finished = pyqtSignal(object)
restriction_error = pyqtSignal()
tag_item_delete = pyqtSignal(object, object, object, object)
apply_tag_to_selected = pyqtSignal(object, object, object)
def __init__(self, parent=None):
QTreeView.__init__(self, parent=None)
@ -177,6 +178,7 @@ class TagsView(QTreeView): # {{{
self.search_icon = QIcon(I('search.png'))
self.search_copy_icon = QIcon(I("search_copy_saved.png"))
self.user_category_icon = QIcon(I('tb_folder.png'))
self.edit_metadata_icon = QIcon(I('edit_input.png'))
self.delete_icon = QIcon(I('list_remove.png'))
self.rename_icon = QIcon(I('edit-undo.png'))
@ -510,13 +512,33 @@ class TagsView(QTreeView): # {{{
gprefs['tags_browser_partition_method'] = category
elif action == 'defaults':
self.hidden_categories.clear()
elif action == 'add_tag':
item = self.model().get_node(index)
if item is not None:
self.apply_to_selected_books(item)
return
elif action == 'remove_tag':
item = self.model().get_node(index)
if item is not None:
self.apply_to_selected_books(item, True)
return
self.db.new_api.set_pref('tag_browser_hidden_categories', list(self.hidden_categories))
if reset_filter_categories:
self._model.set_categories_filter(None)
self._model.rebuild_node_tree()
except:
except Exception:
import traceback
traceback.print_exc()
return
def apply_to_selected_books(self, item, remove=False):
if item.type != item.TAG:
return
tag = item.tag
if not tag.category or not tag.original_name:
return
self.apply_tag_to_selected.emit(tag.category, tag.original_name, remove)
def show_context_menu(self, point):
def display_name(tag):
ans = tag.name
@ -551,6 +573,7 @@ class TagsView(QTreeView): # {{{
# Verify that we are working with a field that we know something about
if key not in self.db.field_metadata:
return True
fm = self.db.field_metadata[key]
# Did the user click on a leaf node?
if tag:
@ -589,9 +612,9 @@ class TagsView(QTreeView): # {{{
# is_editable is also overloaded to mean 'can be added
# to a User category'
m = self.context_menu.addMenu(self.user_category_icon,
_('Add %s to User category')%display_name(tag))
nt = self.model().user_category_node_tree
m = QMenu(_('Add %s to User category')%display_name(tag), self.context_menu)
m.setIcon(self.user_category_icon)
added = [False]
def add_node_tree(tree_dict, m, path):
p = path[:]
@ -602,12 +625,28 @@ class TagsView(QTreeView): # {{{
partial(self.context_menu_handler,
'add_to_category',
category='.'.join(p), index=tag_item))
added[0] = True
if len(tree_dict[k]):
tm = m.addMenu(self.user_category_icon,
_('Children of %s')%n)
add_node_tree(tree_dict[k], tm, p)
p.pop()
add_node_tree(nt, m, [])
add_node_tree(self.model().user_category_node_tree, m, [])
if added[0]:
self.context_menu.addMenu(m)
# is_editable also means the tag can be applied/removed
# from selected books
if fm['datatype'] != 'rating':
m = self.context_menu.addMenu(self.edit_metadata_icon,
_('Apply %s to selected books')%display_name(tag))
m.addAction(QIcon(I('plus.png')),
_('Add %s to selected books') % display_name(tag),
partial(self.context_menu_handler, action='add_tag', index=index))
m.addAction(QIcon(I('minus.png')),
_('Remove %s from selected books') % display_name(tag),
partial(self.context_menu_handler, action='remove_tag', index=index))
elif key == 'search' and tag.is_searchable:
self.context_menu.addAction(self.rename_icon,
_('Rename %s')%display_name(tag),