Start work on annotations browser action

This commit is contained in:
Kovid Goyal 2020-06-25 13:58:16 +05:30
parent ded52279fc
commit 5a0da12942
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 173 additions and 122 deletions

View File

@ -871,6 +871,12 @@ class ActionPolish(InterfaceActionBase):
description = _('Fine tune your e-books')
class ActionBrowseAnnotations(InterfaceActionBase):
name = 'Browse Annotations'
actual_plugin = 'calibre.gui2.actions.browse_annots:BrowseAnnotationsAction'
description = _('Browse highlights and bookmarks from all books in the library')
class ActionEditToC(InterfaceActionBase):
name = 'Edit ToC'
actual_plugin = 'calibre.gui2.actions.toc_edit:ToCEditAction'
@ -1095,7 +1101,7 @@ plugins += [ActionAdd, ActionFetchAnnotations, ActionGenerateCatalog,
ActionCopyToLibrary, ActionTweakEpub, ActionUnpackBook, ActionNextMatch, ActionStore,
ActionPluginUpdater, ActionPickRandom, ActionEditToC, ActionSortBy,
ActionMarkBooks, ActionEmbed, ActionTemplateTester, ActionTagMapper, ActionAuthorMapper,
ActionVirtualLibrary]
ActionVirtualLibrary, ActionBrowseAnnotations]
# }}}

View File

@ -0,0 +1,31 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals
from calibre.gui2.actions import InterfaceAction
class BrowseAnnotationsAction(InterfaceAction):
name = 'Browse Annotations'
action_spec = (_('Browse annotations'), 'polish.png',
_('Browse highlights and bookmarks from all books in the library'), _('B'))
dont_add_to = frozenset(('context-menu-device',))
action_type = 'current'
def genesis(self):
self.qaction.triggered.connect(self.show_browser)
self._browser = None
@property
def browser(self):
if self._browser is None:
from calibre.gui2.library.annotations import AnnotationsBrowser
self._browser = AnnotationsBrowser(self.gui)
return self._browser
def show_browser(self):
self.browser.show_dialog()

View File

@ -16,7 +16,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.viewer.search import ResultsDelegate, SearchBox
from calibre.gui2.viewer.widgets import ResultsDelegate, SearchBox
from calibre.gui2.widgets2 import Dialog
@ -437,6 +437,7 @@ class AnnotationsBrowser(Dialog):
self.exec_()
else:
self.show()
self.raise_()
if __name__ == '__main__':

View File

@ -3,22 +3,20 @@
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
import json
import re
from collections import Counter, OrderedDict
from threading import Thread
import regex
from PyQt5.Qt import (
QAction, QCheckBox, QComboBox, QFont, QFontMetrics, QHBoxLayout, QIcon, QLabel,
QStyle, QStyledItemDelegate, Qt, QToolButton, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget, pyqtSignal
QCheckBox, QComboBox, QFont, QHBoxLayout, QIcon, QLabel, Qt, QToolButton,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget, pyqtSignal
)
from calibre.ebooks.conversion.search_replace import REGEX_FLAGS
from calibre.gui2 import QT_HIDDEN_CLEAR_ACTION, warning_dialog
from calibre.gui2 import warning_dialog
from calibre.gui2.progress_indicator import ProgressIndicator
from calibre.gui2.viewer.web_view import get_data, get_manifest, vprefs
from calibre.gui2.widgets2 import HistoryComboBox
from calibre.gui2.viewer.widgets import ResultsDelegate, SearchBox
from polyglot.builtins import iteritems, map, unicode_type
from polyglot.functools import lru_cache
from polyglot.queue import Queue
@ -293,31 +291,6 @@ def search_in_name(name, search_query, ctx_size=50):
yield before, match.group(), after, start
class SearchBox(HistoryComboBox):
history_saved = pyqtSignal(object, object)
cleared = pyqtSignal()
def __init__(self, parent=None):
HistoryComboBox.__init__(self, parent)
self.lineEdit().setPlaceholderText(_('Search'))
self.lineEdit().setClearButtonEnabled(True)
ac = self.lineEdit().findChild(QAction, QT_HIDDEN_CLEAR_ACTION)
if ac is not None:
ac.triggered.connect(self.cleared)
def save_history(self):
ret = HistoryComboBox.save_history(self)
self.history_saved.emit(self.text(), self.history)
return ret
def contextMenuEvent(self, event):
menu = self.lineEdit().createStandardContextMenu()
menu.addSeparator()
menu.addAction(_('Clear search history'), self.clear_history)
menu.exec_(event.globalPos())
class SearchInput(QWidget): # {{{
do_search = pyqtSignal(object)
@ -447,95 +420,6 @@ class SearchInput(QWidget): # {{{
# }}}
class ResultsDelegate(QStyledItemDelegate): # {{{
add_ellipsis = True
def result_data(self, result):
if not isinstance(result, SearchResult):
return None, None, None, None
return result.is_hidden, result.before, result.text, result.text
def paint(self, painter, option, index):
QStyledItemDelegate.paint(self, painter, option, index)
result = index.data(Qt.UserRole)
is_hidden, result_before, result_text, result_after = self.result_data(result)
if result_text is None:
return
painter.save()
try:
p = option.palette
c = p.HighlightedText if option.state & QStyle.State_Selected else p.Text
group = (p.Active if option.state & QStyle.State_Active else p.Inactive)
c = p.color(group, c)
painter.setPen(c)
font = option.font
emphasis_font = QFont(font)
emphasis_font.setBold(True)
flags = Qt.AlignTop | Qt.TextSingleLine | Qt.TextIncludeTrailingSpaces
rect = option.rect.adjusted(option.decorationSize.width() + 4 if is_hidden else 0, 0, 0, 0)
painter.setClipRect(rect)
before = re.sub(r'\s+', ' ', result_before)
before_width = 0
if before:
before_width = painter.boundingRect(rect, flags, before).width()
after = re.sub(r'\s+', ' ', result_after.rstrip())
after_width = 0
if after:
after_width = painter.boundingRect(rect, flags, after).width()
ellipsis_width = painter.boundingRect(rect, flags, '...').width()
painter.setFont(emphasis_font)
text = re.sub(r'\s+', ' ', result_text)
match_width = painter.boundingRect(rect, flags, text).width()
if match_width >= rect.width() - 3 * ellipsis_width:
efm = QFontMetrics(emphasis_font)
text = efm.elidedText(text, Qt.ElideRight, rect.width())
painter.drawText(rect, flags, text)
else:
self.draw_match(
painter, flags, before, text, after, rect, before_width, match_width, after_width, ellipsis_width, emphasis_font, font)
except Exception:
import traceback
traceback.print_exc()
painter.restore()
def draw_match(self, painter, flags, before, text, after, rect, before_width, match_width, after_width, ellipsis_width, emphasis_font, normal_font):
extra_width = int(rect.width() - match_width)
if before_width < after_width:
left_width = min(extra_width // 2, before_width)
right_width = extra_width - left_width
else:
right_width = min(extra_width // 2, after_width)
left_width = min(before_width, extra_width - right_width)
x = rect.left()
nfm = QFontMetrics(normal_font)
if before_width and left_width:
r = rect.adjusted(0, 0, 0, 0)
r.setRight(x + left_width)
painter.setFont(normal_font)
ebefore = nfm.elidedText(before, Qt.ElideLeft, left_width)
if self.add_ellipsis and ebefore == before:
ebefore = '' + before[1:]
r.setLeft(x)
x += painter.drawText(r, flags, ebefore).width()
painter.setFont(emphasis_font)
r = rect.adjusted(0, 0, 0, 0)
r.setLeft(x)
painter.drawText(r, flags, text).width()
x += match_width
if after_width and right_width:
painter.setFont(normal_font)
r = rect.adjusted(0, 0, 0, 0)
r.setLeft(x)
eafter = nfm.elidedText(after, Qt.ElideRight, right_width)
if self.add_ellipsis and eafter == after:
eafter = after[:-1] + ''
painter.setFont(normal_font)
painter.drawText(r, flags, eafter)
# }}}
class Results(QTreeWidget): # {{{
show_search_result = pyqtSignal(object)

View File

@ -0,0 +1,129 @@
#!/usr/bin/env python2
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from __future__ import absolute_import, division, print_function, unicode_literals
import re
from PyQt5.Qt import (
QAction, QFont, QFontMetrics, QStyle, QStyledItemDelegate, Qt, pyqtSignal
)
from calibre.gui2 import QT_HIDDEN_CLEAR_ACTION
from calibre.gui2.widgets2 import HistoryComboBox
class ResultsDelegate(QStyledItemDelegate): # {{{
add_ellipsis = True
def result_data(self, result):
if not hasattr(result, 'is_hidden'):
return None, None, None, None
return result.is_hidden, result.before, result.text, result.text
def paint(self, painter, option, index):
QStyledItemDelegate.paint(self, painter, option, index)
result = index.data(Qt.UserRole)
is_hidden, result_before, result_text, result_after = self.result_data(result)
if result_text is None:
return
painter.save()
try:
p = option.palette
c = p.HighlightedText if option.state & QStyle.State_Selected else p.Text
group = (p.Active if option.state & QStyle.State_Active else p.Inactive)
c = p.color(group, c)
painter.setPen(c)
font = option.font
emphasis_font = QFont(font)
emphasis_font.setBold(True)
flags = Qt.AlignTop | Qt.TextSingleLine | Qt.TextIncludeTrailingSpaces
rect = option.rect.adjusted(option.decorationSize.width() + 4 if is_hidden else 0, 0, 0, 0)
painter.setClipRect(rect)
before = re.sub(r'\s+', ' ', result_before)
before_width = 0
if before:
before_width = painter.boundingRect(rect, flags, before).width()
after = re.sub(r'\s+', ' ', result_after.rstrip())
after_width = 0
if after:
after_width = painter.boundingRect(rect, flags, after).width()
ellipsis_width = painter.boundingRect(rect, flags, '...').width()
painter.setFont(emphasis_font)
text = re.sub(r'\s+', ' ', result_text)
match_width = painter.boundingRect(rect, flags, text).width()
if match_width >= rect.width() - 3 * ellipsis_width:
efm = QFontMetrics(emphasis_font)
text = efm.elidedText(text, Qt.ElideRight, rect.width())
painter.drawText(rect, flags, text)
else:
self.draw_match(
painter, flags, before, text, after, rect, before_width, match_width, after_width, ellipsis_width, emphasis_font, font)
except Exception:
import traceback
traceback.print_exc()
painter.restore()
def draw_match(self, painter, flags, before, text, after, rect, before_width, match_width, after_width, ellipsis_width, emphasis_font, normal_font):
extra_width = int(rect.width() - match_width)
if before_width < after_width:
left_width = min(extra_width // 2, before_width)
right_width = extra_width - left_width
else:
right_width = min(extra_width // 2, after_width)
left_width = min(before_width, extra_width - right_width)
x = rect.left()
nfm = QFontMetrics(normal_font)
if before_width and left_width:
r = rect.adjusted(0, 0, 0, 0)
r.setRight(x + left_width)
painter.setFont(normal_font)
ebefore = nfm.elidedText(before, Qt.ElideLeft, left_width)
if self.add_ellipsis and ebefore == before:
ebefore = '' + before[1:]
r.setLeft(x)
x += painter.drawText(r, flags, ebefore).width()
painter.setFont(emphasis_font)
r = rect.adjusted(0, 0, 0, 0)
r.setLeft(x)
painter.drawText(r, flags, text).width()
x += match_width
if after_width and right_width:
painter.setFont(normal_font)
r = rect.adjusted(0, 0, 0, 0)
r.setLeft(x)
eafter = nfm.elidedText(after, Qt.ElideRight, right_width)
if self.add_ellipsis and eafter == after:
eafter = after[:-1] + ''
painter.setFont(normal_font)
painter.drawText(r, flags, eafter)
# }}}
class SearchBox(HistoryComboBox): # {{{
history_saved = pyqtSignal(object, object)
cleared = pyqtSignal()
def __init__(self, parent=None):
HistoryComboBox.__init__(self, parent)
self.lineEdit().setPlaceholderText(_('Search'))
self.lineEdit().setClearButtonEnabled(True)
ac = self.lineEdit().findChild(QAction, QT_HIDDEN_CLEAR_ACTION)
if ac is not None:
ac.triggered.connect(self.cleared)
def save_history(self):
ret = HistoryComboBox.save_history(self)
self.history_saved.emit(self.text(), self.history)
return ret
def contextMenuEvent(self, event):
menu = self.lineEdit().createStandardContextMenu()
menu.addSeparator()
menu.addAction(_('Clear search history'), self.clear_history)
menu.exec_(event.globalPos())
# }}}