mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Details display in annots browser
This commit is contained in:
parent
2c530d7c78
commit
88b749c875
@ -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)
|
||||||
|
@ -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> </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> </div>
|
||||||
|
<div> </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)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user