Details display in annots browser

This commit is contained in:
Kovid Goyal 2020-06-18 14:43:08 +05:30
parent 2c530d7c78
commit 88b749c875
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
2 changed files with 123 additions and 7 deletions

View File

@ -308,7 +308,7 @@ def save_annotations_for_book(cursor, book_id, fmt, annots_list, user_type='loca
text = annot.get('highlighted_text') or '' text = annot.get('highlighted_text') or ''
notes = annot.get('notes') or '' notes = annot.get('notes') or ''
if notes: if notes:
text += '\n0x1f\n' + notes text += '\n\x1f\n' + notes
else: else:
continue continue
data.append((book_id, fmt, user_type, user, timestamp_in_secs, aid, atype, json.dumps(annot), text)) data.append((book_id, fmt, user_type, user, timestamp_in_secs, aid, atype, json.dumps(annot), text))
@ -1800,8 +1800,13 @@ class DB(object):
query += ' AND annotations.annot_type = ? ' query += ' AND annotations.annot_type = ? '
data.append(annotation_type) data.append(annotation_type)
query += ' ORDER BY {}.rank '.format(fts_table) query += ' ORDER BY {}.rank '.format(fts_table)
ls = json.loads
try: try:
for (rowid, book_id, fmt, user_type, user, annot_data, text) in self.execute(query, tuple(data)): for (rowid, book_id, fmt, user_type, user, annot_data, text) in self.execute(query, tuple(data)):
try:
parsed_annot = ls(annot_data)
except Exception:
continue
yield { yield {
'id': rowid, 'id': rowid,
'book_id': book_id, 'book_id': book_id,
@ -1809,7 +1814,7 @@ class DB(object):
'user_type': user_type, 'user_type': user_type,
'user': user, 'user': user,
'text': text, 'text': text,
'annotation': annot_data 'annotation': parsed_annot,
} }
except apsw.SQLError as e: except apsw.SQLError as e:
raise FTSQueryError(fts_engine_query, query, e) raise FTSQueryError(fts_engine_query, query, e)

View File

@ -9,11 +9,13 @@ from textwrap import fill
from PyQt5.Qt import ( from PyQt5.Qt import (
QApplication, QCheckBox, QComboBox, QCursor, QFont, QHBoxLayout, QIcon, QLabel, QApplication, QCheckBox, QComboBox, QCursor, QFont, QHBoxLayout, QIcon, QLabel,
QSize, QSplitter, Qt, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QPalette, QSize, QSplitter, Qt, QTextBrowser, QToolButton, QTreeWidget,
QWidget, pyqtSignal QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal
) )
from calibre.gui2 import Application, gprefs from calibre import prepare_string_for_xml
from calibre.ebooks.metadata import fmt_sidx, authors_to_string
from calibre.gui2 import Application, config, gprefs
from calibre.gui2.viewer.search import ResultsDelegate, SearchBox from calibre.gui2.viewer.search import ResultsDelegate, SearchBox
from calibre.gui2.widgets2 import Dialog from calibre.gui2.widgets2 import Dialog
@ -23,6 +25,12 @@ def current_db():
return (getattr(current_db, 'ans', None) or get_gui().current_db).new_api return (getattr(current_db, 'ans', None) or get_gui().current_db).new_api
def annotation_title(atype, singular=False):
if singular:
return {'bookmark': _('Bookmark'), 'highlight': _('Highlight')}.get(atype, atype)
return {'bookmark': _('Bookmarks'), 'highlight': _('Highlights')}.get(atype, atype)
class BusyCursor(object): class BusyCursor(object):
def __enter__(self): def __enter__(self):
@ -54,6 +62,8 @@ class AnnotsResultsDelegate(ResultsDelegate):
class ResultsList(QTreeWidget): class ResultsList(QTreeWidget):
current_result_changed = pyqtSignal(object)
def __init__(self, parent): def __init__(self, parent):
QTreeWidget.__init__(self, parent) QTreeWidget.__init__(self, parent)
self.setHeaderHidden(True) self.setHeaderHidden(True)
@ -61,9 +71,14 @@ class ResultsList(QTreeWidget):
self.setItemDelegate(self.delegate) self.setItemDelegate(self.delegate)
self.section_font = QFont(self.font()) self.section_font = QFont(self.font())
self.section_font.setItalic(True) self.section_font.setItalic(True)
self.currentItemChanged.connect(self.current_item_changed)
self.number_of_results = 0
self.item_map = []
def set_results(self, results): def set_results(self, results):
self.clear() self.clear()
self.number_of_results = 0
self.item_map = []
book_id_map = {} book_id_map = {}
db = current_db() db = current_db()
for result in results: for result in results:
@ -79,8 +94,40 @@ class ResultsList(QTreeWidget):
section.setExpanded(True) section.setExpanded(True)
for result in entry['matches']: for result in entry['matches']:
item = QTreeWidgetItem(section, [' '], 2) item = QTreeWidgetItem(section, [' '], 2)
self.item_map.append(item)
item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemNeverHasChildren) item.setFlags(Qt.ItemIsSelectable | Qt.ItemIsEnabled | Qt.ItemNeverHasChildren)
item.setData(0, Qt.UserRole, result) item.setData(0, Qt.UserRole, result)
item.setData(0, Qt.UserRole + 1, self.number_of_results)
self.number_of_results += 1
if self.item_map:
self.setCurrentItem(self.item_map[0])
def current_item_changed(self, current, previous):
if current is not None:
r = current.data(0, Qt.UserRole)
if isinstance(r, dict):
self.current_result_changed.emit(r)
else:
self.current_result_changed.emit(None)
def show_next(self, backwards=False):
item = self.currentItem()
if item is None:
return
i = int(item.data(0, Qt.UserRole + 1))
i += -1 if backwards else 1
i %= self.number_of_results
self.setCurrentItem(self.item_map[i])
def keyPressEvent(self, ev):
key = ev.key()
if key == Qt.Key_Down:
self.show_next()
return
if key == Qt.Key_Up:
self.show_next(backwards=True)
return
return QTreeWidget.keyPressEvent(self, ev)
class Restrictions(QWidget): class Restrictions(QWidget):
@ -121,8 +168,7 @@ class Restrictions(QWidget):
tb.clear() tb.clear()
tb.addItem(' ', ' ') tb.addItem(' ', ' ')
for atype in db.all_annotation_types(): for atype in db.all_annotation_types():
name = {'bookmark': _('Bookmarks'), 'highlight': _('Highlights')}.get(atype, atype) tb.addItem(annotation_title(atype), atype)
tb.addItem(name, atype)
if before: if before:
row = tb.findData(before) row = tb.findData(before)
if row > -1: if row > -1:
@ -152,6 +198,8 @@ class Restrictions(QWidget):
class BrowsePanel(QWidget): class BrowsePanel(QWidget):
current_result_changed = pyqtSignal(object)
def __init__(self, parent): def __init__(self, parent):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.use_stemmer = parent.use_stemmer self.use_stemmer = parent.use_stemmer
@ -187,6 +235,7 @@ class BrowsePanel(QWidget):
l.addWidget(rs) l.addWidget(rs)
self.results_list = rl = ResultsList(self) self.results_list = rl = ResultsList(self)
rl.current_result_changed.connect(self.current_result_changed)
l.addWidget(rl) l.addWidget(rl)
def re_initialize(self): def re_initialize(self):
@ -243,14 +292,75 @@ class BrowsePanel(QWidget):
self.do_find(backwards=True) self.do_find(backwards=True)
class Details(QTextBrowser):
def __init__(self, parent):
QTextBrowser.__init__(self, parent)
self.setFrameShape(self.NoFrame)
self.setOpenLinks(False)
self.setAttribute(Qt.WA_OpaquePaintEvent, False)
palette = self.palette()
palette.setBrush(QPalette.Base, Qt.transparent)
self.setPalette(palette)
self.setAcceptDrops(False)
class DetailsPanel(QWidget): class DetailsPanel(QWidget):
def __init__(self, parent): def __init__(self, parent):
QWidget.__init__(self, parent) QWidget.__init__(self, parent)
self.current_result = None
l = QVBoxLayout(self)
self.text_browser = tb = Details(self)
l.addWidget(tb)
def sizeHint(self): def sizeHint(self):
return QSize(450, 600) return QSize(450, 600)
def show_result(self, result_or_none):
self.current_result = r = result_or_none
if r is None:
self.text_browser.setVisible(False)
return
self.text_browser.setVisible(True)
db = current_db()
book_id = r['book_id']
title, authors = db.field_for('title', book_id), db.field_for('authors', book_id)
authors = authors_to_string(authors)
series, sidx = db.field_for('series', book_id), db.field_for('series_index', book_id)
series_text = ''
if series:
use_roman_numbers = config['use_roman_numerals_for_series_number']
series_text = '{0} of {1}'.format(fmt_sidx(sidx, use_roman=use_roman_numbers), series)
annot = r['annotation']
atype = annotation_title(annot['type'], singular=True)
book_format = r['format']
annot_text = ''
a = prepare_string_for_xml
for part in r['text'].split('\n\x1f\n'):
segments = []
for bit in part.split('\x1d'):
segments.append(a(bit) + ('</b>' if len(segments) % 2 else '<b>'))
stext = ''.join(segments)
if stext.endswith('<b>'):
stext = stext[:-3]
annot_text += '<div style="text-align:left">' + stext + '</div><div>&nbsp;</div>'
text = '''
<h2 style="text-align: center">{title} [{book_format}]</h2>
<div style="text-align: center">{authors}</div>
<div style="text-align: center">{series}</div>
<div>&nbsp;</div>
<div>&nbsp;</div>
<h2 style="text-align: left">{atype}</h2>
{text}
'''.format(
title=a(title), authors=a(authors), series=a(series_text), book_format=a(book_format),
atype=a(atype), text=annot_text
)
self.text_browser.setHtml(text)
class AnnotationsBrowser(Dialog): class AnnotationsBrowser(Dialog):
@ -281,6 +391,7 @@ class AnnotationsBrowser(Dialog):
self.details_panel = dp = DetailsPanel(self) self.details_panel = dp = DetailsPanel(self)
s.addWidget(dp) s.addWidget(dp)
bp.current_result_changed.connect(dp.show_result)
h = QHBoxLayout() h = QHBoxLayout()
l.addLayout(h) l.addLayout(h)