mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-07 10:14:46 -04:00
Allow editing highlight notes from annotations browser
This commit is contained in:
parent
b90e6964d0
commit
9da5c6eb57
@ -98,3 +98,17 @@ def merge_annotations(annots, annots_map, merge_last_read=True):
|
||||
if not b:
|
||||
continue
|
||||
changed, annots_map[annot_type] = merge_annots_with_identical_field(a or [], b, field=field)
|
||||
|
||||
|
||||
def annot_db_data(annot):
|
||||
aid = text = None
|
||||
atype = annot['type'].lower()
|
||||
if atype == 'bookmark':
|
||||
aid = text = annot['title']
|
||||
elif atype == 'highlight':
|
||||
aid = annot['uuid']
|
||||
text = annot.get('highlighted_text') or ''
|
||||
notes = annot.get('notes') or ''
|
||||
if notes:
|
||||
text += '\n\x1f\n' + notes
|
||||
return aid, text
|
||||
|
@ -19,6 +19,7 @@ from calibre.constants import (iswindows, filesystem_encoding,
|
||||
preferred_encoding)
|
||||
from calibre.ptempfile import PersistentTemporaryFile, TemporaryFile
|
||||
from calibre.db import SPOOL_SIZE
|
||||
from calibre.db.annotations import annot_db_data
|
||||
from calibre.db.schema_upgrades import SchemaUpgrade
|
||||
from calibre.db.delete_service import delete_service
|
||||
from calibre.db.errors import NoSuchFormat
|
||||
@ -27,7 +28,7 @@ from calibre.ebooks.metadata import title_sort, author_to_author_sort
|
||||
from calibre.utils import pickle_binary_string, unpickle_binary_string
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.config import to_json, from_json, prefs, tweaks
|
||||
from calibre.utils.date import utcfromtimestamp, parse_date
|
||||
from calibre.utils.date import utcfromtimestamp, parse_date, utcnow, EPOCH
|
||||
from calibre.utils.filenames import (
|
||||
is_case_sensitive, samefile, hardlink_file, ascii_filename,
|
||||
WindowsAtomicFolderMove, atomic_rename, remove_dir_if_empty,
|
||||
@ -301,15 +302,8 @@ def save_annotations_for_book(cursor, book_id, fmt, annots_list, user_type='loca
|
||||
fmt = fmt.upper()
|
||||
for annot, timestamp_in_secs in annots_list:
|
||||
atype = annot['type'].lower()
|
||||
if atype == 'bookmark':
|
||||
aid = text = annot['title']
|
||||
elif atype == 'highlight':
|
||||
aid = annot['uuid']
|
||||
text = annot.get('highlighted_text') or ''
|
||||
notes = annot.get('notes') or ''
|
||||
if notes:
|
||||
text += '\n\x1f\n' + notes
|
||||
else:
|
||||
aid, text = annot_db_data(annot)
|
||||
if aid is None:
|
||||
continue
|
||||
data.append((book_id, fmt, user_type, user, timestamp_in_secs, aid, atype, json.dumps(annot), text))
|
||||
cursor.execute('INSERT OR IGNORE INTO annotations_dirtied (book) VALUES (?)', (book_id,))
|
||||
@ -1835,9 +1829,11 @@ class DB(object):
|
||||
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 = []
|
||||
now = utcnow()
|
||||
ts = now.isoformat()
|
||||
timestamp = (now - EPOCH).total_seconds()
|
||||
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,)
|
||||
@ -1847,18 +1843,32 @@ class DB(object):
|
||||
except Exception:
|
||||
removals.append((annot_id,))
|
||||
continue
|
||||
new_annot = {'removed': True, 'timestamp': utcnow().isoformat(), 'type': annot_type}
|
||||
now = utcnow()
|
||||
new_annot = {'removed': True, 'timestamp': ts, '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))
|
||||
replacements.append((json.dumps(new_annot), timestamp, annot_id))
|
||||
if replacements:
|
||||
self.executemany('UPDATE annotations SET annot_data=?, searchable_text="" WHERE id=?', replacements)
|
||||
self.executemany('UPDATE annotations SET annot_data=?, timestamp=?, searchable_text="" WHERE id=?', replacements)
|
||||
if removals:
|
||||
self.executemany('DELETE FROM annotations WHERE id=?', removals)
|
||||
|
||||
def update_annotations(self, annot_id_map):
|
||||
now = utcnow()
|
||||
ts = now.isoformat()
|
||||
timestamp = (now - EPOCH).total_seconds()
|
||||
with self.conn:
|
||||
for annot_id, annot in annot_id_map.items():
|
||||
atype = annot['type']
|
||||
aid, text = annot_db_data(annot)
|
||||
if aid is not None:
|
||||
annot['timestamp'] = ts
|
||||
self.execute('UPDATE annotations SET annot_data=?, timestamp=?, annot_type=?, searchable_text=?, annot_id=? WHERE id=?',
|
||||
(json.dumps(annot), timestamp, atype, text, aid, annot_id))
|
||||
|
||||
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'
|
||||
|
@ -2337,6 +2337,10 @@ class Cache(object):
|
||||
def delete_annotations(self, annot_ids):
|
||||
self.backend.delete_annotations(annot_ids)
|
||||
|
||||
@write_api
|
||||
def update_annotations(self, annot_id_map):
|
||||
self.backend.update_annotations(annot_id_map)
|
||||
|
||||
@write_api
|
||||
def restore_annotations(self, book_id, annotations):
|
||||
from calibre.utils.iso8601 import parse_iso8601
|
||||
|
@ -7,8 +7,9 @@ from textwrap import fill
|
||||
|
||||
from PyQt5.Qt import (
|
||||
QApplication, QCheckBox, QComboBox, QCursor, QDateTime, QFont, QHBoxLayout,
|
||||
QIcon, QLabel, QPalette, QPushButton, QSize, QSplitter, Qt, QTextBrowser, QTimer,
|
||||
QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal
|
||||
QIcon, QInputDialog, QLabel, QPalette, QPushButton, QSize, QSplitter, Qt,
|
||||
QTextBrowser, QTimer, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
|
||||
QWidget, pyqtSignal
|
||||
)
|
||||
|
||||
from calibre import prepare_string_for_xml
|
||||
@ -348,6 +349,7 @@ class DetailsPanel(QWidget):
|
||||
|
||||
open_annotation = pyqtSignal(object, object, object)
|
||||
show_book = pyqtSignal(object, object)
|
||||
edit_annotation = pyqtSignal(object, object)
|
||||
delete_annotation = pyqtSignal(object)
|
||||
|
||||
def __init__(self, parent):
|
||||
@ -371,6 +373,10 @@ class DetailsPanel(QWidget):
|
||||
|
||||
h = QHBoxLayout()
|
||||
l.addLayout(h)
|
||||
self.edit_button = ob = QPushButton(QIcon(I('edit_input.png')), _('Edit'), self)
|
||||
ob.setToolTip(_('Edit the notes if any for this highlight'))
|
||||
ob.clicked.connect(self.edit_result)
|
||||
h.addWidget(ob)
|
||||
self.delete_button = ob = QPushButton(QIcon(I('trash.png')), _('Delete'), self)
|
||||
ob.setToolTip(_('Delete this annotation'))
|
||||
ob.clicked.connect(self.delete_result)
|
||||
@ -388,6 +394,11 @@ class DetailsPanel(QWidget):
|
||||
r = self.current_result
|
||||
self.delete_annotation.emit(r['id'])
|
||||
|
||||
def edit_result(self):
|
||||
if self.current_result is not None:
|
||||
r = self.current_result
|
||||
self.edit_annotation.emit(r['id'], r['annotation'])
|
||||
|
||||
def show_in_library(self):
|
||||
if self.current_result is not None:
|
||||
self.show_book.emit(self.current_result['book_id'], self.current_result['format'])
|
||||
@ -395,16 +406,24 @@ class DetailsPanel(QWidget):
|
||||
def sizeHint(self):
|
||||
return QSize(450, 600)
|
||||
|
||||
def set_controls_visibility(self, visible):
|
||||
self.text_browser.setVisible(visible)
|
||||
self.open_button.setVisible(visible)
|
||||
self.library_button.setVisible(visible)
|
||||
self.delete_button.setVisible(visible)
|
||||
self.edit_button.setVisible(visible)
|
||||
|
||||
def update_notes(self, annot):
|
||||
if self.current_result:
|
||||
self.current_result['annotation'] = annot
|
||||
self.show_result(self.current_result)
|
||||
|
||||
def show_result(self, result_or_none):
|
||||
self.current_result = r = result_or_none
|
||||
if r is None:
|
||||
self.text_browser.setVisible(False)
|
||||
self.open_button.setVisible(False)
|
||||
self.library_button.setVisible(False)
|
||||
self.set_controls_visibility(False)
|
||||
return
|
||||
self.text_browser.setVisible(True)
|
||||
self.open_button.setVisible(True)
|
||||
self.library_button.setVisible(True)
|
||||
self.set_controls_visibility(True)
|
||||
db = current_db()
|
||||
book_id = r['book_id']
|
||||
title, authors = db.field_for('title', book_id), db.field_for('authors', book_id)
|
||||
@ -427,7 +446,9 @@ class DetailsPanel(QWidget):
|
||||
|
||||
if annot['type'] == 'bookmark':
|
||||
p(annot['title'])
|
||||
self.edit_button.setEnabled(False)
|
||||
elif annot['type'] == 'highlight':
|
||||
self.edit_button.setEnabled(True)
|
||||
p(annot['highlighted_text'])
|
||||
notes = annot.get('notes')
|
||||
if notes:
|
||||
@ -509,6 +530,7 @@ class AnnotationsBrowser(Dialog):
|
||||
dp.open_annotation.connect(self.do_open_annotation)
|
||||
dp.show_book.connect(self.show_book)
|
||||
dp.delete_annotation.connect(self.delete_annotation)
|
||||
dp.edit_annotation.connect(self.edit_annotation)
|
||||
bp.current_result_changed.connect(dp.show_result)
|
||||
|
||||
h = QHBoxLayout()
|
||||
@ -538,6 +560,21 @@ class AnnotationsBrowser(Dialog):
|
||||
def delete_annotation(self, annot_id):
|
||||
self.delete_annotations(frozenset({annot_id}))
|
||||
|
||||
def edit_annotation(self, annot_id, annot):
|
||||
if annot.get('type') != 'highlight':
|
||||
return error_dialog(self, _('Cannot edit'), _(
|
||||
'Editing is only supported for the notes associated with highlights'), show=True)
|
||||
notes = annot.get('notes')
|
||||
notes, ok = QInputDialog.getMultiLineText(self, _('Edit notes for highlight'), '', notes)
|
||||
if ok:
|
||||
if notes and notes.strip():
|
||||
annot['notes'] = notes.strip()
|
||||
else:
|
||||
annot.pop('notes', None)
|
||||
db = current_db()
|
||||
db.update_annotations({annot_id: annot})
|
||||
self.details_panel.update_notes(annot)
|
||||
|
||||
def show_dialog(self):
|
||||
if self.parent() is None:
|
||||
self.browse_panel.effective_query_changed()
|
||||
|
Loading…
x
Reference in New Issue
Block a user