From 93cb98185fa983ab009982d66b187f73bd1ed881 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 5 Aug 2020 23:32:03 +0530 Subject: [PATCH] Add a button to delete selected annotations in the browse annotations tool --- src/calibre/db/backend.py | 25 ++++++++++++++++++ src/calibre/db/cache.py | 4 +++ src/calibre/gui2/library/annotations.py | 35 ++++++++++++++++++++++++- 3 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/calibre/db/backend.py b/src/calibre/db/backend.py index b9ca4818a0..413696fa05 100644 --- a/src/calibre/db/backend.py +++ b/src/calibre/db/backend.py @@ -1834,6 +1834,31 @@ class DB(object): if not ignore_removed or not annot.get('removed'): yield {'format': fmt, 'user_type': user_type, 'user': user, 'annotation': annot} + def delete_annotations(self, annot_ids): + from calibre.utils.date import utcnow + replacements = [] + removals = [] + for annot_id in annot_ids: + for (raw_annot_data, annot_type) in self.execute( + 'SELECT annot_data, annot_type FROM annotations WHERE id=?', (annot_id,) + ): + try: + annot_data = json.loads(raw_annot_data) + except Exception: + removals.append((annot_id,)) + continue + new_annot = {'removed': True, 'timestamp': utcnow().isoformat(), 'type': annot_type} + uuid = annot_data.get('uuid') + if uuid is not None: + new_annot['uuid'] = uuid + else: + new_annot['title'] = annot_data['title'] + replacements.append((json.dumps(new_annot), annot_id)) + if replacements: + self.executemany('UPDATE annotations SET annot_data=?, searchable_text="" WHERE id=?', replacements) + if removals: + self.executemany('DELETE FROM annotations WHERE id=?', removals) + def all_annotations(self, restrict_to_user=None, limit=None, annotation_type=None, ignore_removed=False): ls = json.loads q = 'SELECT id, book, format, user_type, user, annot_data FROM annotations' diff --git a/src/calibre/db/cache.py b/src/calibre/db/cache.py index 4fd5be0033..0d7f098227 100644 --- a/src/calibre/db/cache.py +++ b/src/calibre/db/cache.py @@ -2333,6 +2333,10 @@ class Cache(object): ignore_removed )) + @write_api + def delete_annotations(self, annot_ids): + self.backend.delete_annotations(annot_ids) + @write_api def restore_annotations(self, book_id, annotations): from calibre.utils.iso8601 import parse_iso8601 diff --git a/src/calibre/gui2/library/annotations.py b/src/calibre/gui2/library/annotations.py index 7b993a2b8b..a88f1a6a59 100644 --- a/src/calibre/gui2/library/annotations.py +++ b/src/calibre/gui2/library/annotations.py @@ -13,7 +13,7 @@ from PyQt5.Qt import ( from calibre import prepare_string_for_xml from calibre.ebooks.metadata import authors_to_string, fmt_sidx -from calibre.gui2 import Application, config, gprefs +from calibre.gui2 import Application, config, error_dialog, gprefs, question_dialog from calibre.gui2.viewer.widgets import ResultsDelegate, SearchBox from calibre.gui2.widgets2 import Dialog @@ -76,6 +76,7 @@ class ResultsList(QTreeWidget): def __init__(self, parent): QTreeWidget.__init__(self, parent) self.setHeaderHidden(True) + self.setSelectionMode(self.ExtendedSelection) self.delegate = AnnotsResultsDelegate(self) self.setItemDelegate(self.delegate) self.section_font = QFont(self.font()) @@ -145,6 +146,11 @@ class ResultsList(QTreeWidget): return return QTreeWidget.keyPressEvent(self, ev) + @property + def selected_annot_ids(self): + for item in self.selectedItems(): + yield item.data(0, Qt.UserRole)['id'] + class Restrictions(QWidget): @@ -310,12 +316,20 @@ class BrowsePanel(QWidget): def effective_query_changed(self): self.do_find() + def refresh(self): + self.current_query = None + self.do_find() + def show_next(self): self.do_find() def show_previous(self): self.do_find(backwards=True) + @property + def selected_annot_ids(self): + return self.results_list.selected_annot_ids + class Details(QTextBrowser): @@ -486,9 +500,28 @@ class AnnotationsBrowser(Dialog): h = QHBoxLayout() l.addLayout(h) h.addWidget(us), h.addStretch(10), h.addWidget(self.bb) + self.delete_button = b = self.bb.addButton(_('Delete selected'), self.bb.ActionRole) + b.setToolTip(_('Delete the selected annotations')) + b.setIcon(QIcon(I('trash.png'))) + b.clicked.connect(self.delete_selected) + + def delete_selected(self): + ids = frozenset(self.browse_panel.selected_annot_ids) + if not ids: + return error_dialog(self, _('No selected annotations'), _( + 'No annotations have been selected'), show=True) + if question_dialog(self, _('Are you sure?'), ngettext( + 'Are you sure you want to permanently delete this annotation?', + 'Are you sure you want to permanently delete these {} annotations?', + len(ids)).format(len(ids)) + ): + db = current_db() + db.delete_annotations(ids) + self.browse_panel.refresh() def show_dialog(self): if self.parent() is None: + self.browse_panel.effective_query_changed() self.exec_() else: self.reinitialize()