Wire up the bookmarks panel fully

This commit is contained in:
Kovid Goyal 2019-08-09 10:08:02 +05:30
parent 13be0d6712
commit eeca114876
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 106 additions and 9 deletions

View File

@ -7,14 +7,14 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import json
from PyQt5.Qt import (
QAction, QGridLayout, QIcon, QItemSelectionModel, QLabel, QListWidget,
QListWidgetItem, QPushButton, Qt, QWidget, pyqtSignal
QAction, QGridLayout, QIcon, QInputDialog, QItemSelectionModel, QLabel,
QListWidget, QListWidgetItem, QPushButton, Qt, QWidget, pyqtSignal
)
from calibre.gui2 import choose_files, choose_save_file
from calibre.gui2.viewer.annotations import serialize_annotation
from calibre.srv.render_book import parse_annotation
from calibre.utils.date import EPOCH
from calibre.utils.date import EPOCH, utcnow
from calibre.utils.icu import sort_key
from polyglot.builtins import range, unicode_type
@ -122,7 +122,7 @@ class BookmarkManager(QWidget):
def item_activated(self, item):
bm = self.item_to_bm(item)
self.activated.emit(bm)
self.activated.emit(bm['pos'])
def set_bookmarks(self, bookmarks=()):
self.bookmarks_list.clear()
@ -146,12 +146,20 @@ class BookmarkManager(QWidget):
for i in range(self.bookmarks_list.count()):
yield self.item_to_bm(self.bookmarks_list.item(i))
def uniqify_bookmark_title(self, base):
all_titles = {bm['title'] for bm in self.get_bookmarks()}
c = 0
q = base
while q in all_titles:
c += 1
q = '{} #{}'.format(base, c)
return q
def item_changed(self, item):
self.bookmarks_list.blockSignals(True)
title = unicode_type(item.data(Qt.DisplayRole))
if not title:
title = _('Unknown')
item.setData(Qt.DisplayRole, title)
title = unicode_type(item.data(Qt.DisplayRole)) or _('Unknown')
title = self.uniqify_bookmark_title(title)
item.setData(Qt.DisplayRole, title)
bm = self.item_to_bm(item)
bm['title'] = title
item.setData(Qt.UserRole, self.bm_to_item(bm))
@ -231,7 +239,7 @@ class BookmarkManager(QWidget):
for bm in imported:
if bm['title'] == 'calibre_current_page_bookmark':
continue
epubcfi = 'epubcfi(/{}/{})'.format(bm['spine'], bm['pos'].lstrip('/'))
epubcfi = 'epubcfi(/{}/{})'.format((bm['spine'] + 1) * 2, bm['pos'].lstrip('/'))
q = {'pos_type': 'epubcfi', 'pos': epubcfi, 'timestamp': EPOCH, 'title': bm['title']}
if q not in bookmarks:
bookmarks.append(q)
@ -254,3 +262,22 @@ class BookmarkManager(QWidget):
import_old_bookmarks(imported)
else:
import_current_bookmarks(imported)
def create_new_bookmark(self, pos_data):
title, ok = QInputDialog.getText(self, _('Add bookmark'),
_('Enter title for bookmark:'))
title = unicode_type(title).strip()
if not ok or not title:
return
title = self.uniqify_bookmark_title(title)
bm = {
'title': title,
'pos_type': 'epubcfi',
'pos': pos_data['cfi'],
'timestamp': utcnow()
}
bookmarks = self.get_bookmarks()
bookmarks.append(bm)
self.set_bookmarks(bookmarks)
self.set_current_bookmark(bm)
self.edited.emit(bookmarks)

View File

@ -73,6 +73,11 @@ class EbookViewer(MainWindow):
self.bookmarks_dock = create_dock(_('Bookmarks'), 'bookmarks-dock', Qt.RightDockWidgetArea)
self.bookmarks_widget = w = BookmarkManager(self)
connect_lambda(
w.create_requested, self,
lambda self: self.web_view.get_current_cfi(self.bookmarks_widget.create_new_bookmark))
self.bookmarks_widget.edited.connect(self.bookmarks_edited)
self.bookmarks_widget.activated.connect(self.bookmark_activated)
self.bookmarks_dock.setWidget(w)
self.inspector_dock = create_dock(_('Inspector'), 'inspector', Qt.RightDockWidgetArea)
@ -144,6 +149,12 @@ class EbookViewer(MainWindow):
def toc_searched(self, index):
item = self.toc_model.itemFromIndex(index)
self.web_view.goto_toc_node(item.node_id)
def bookmarks_edited(self, bookmarks):
self.current_book_data['annotations_map']['bookmark'] = bookmarks
def bookmark_activated(self, cfi):
self.web_view.goto_cfi(cfi)
# }}}
# Load book {{{
@ -194,6 +205,7 @@ class EbookViewer(MainWindow):
toc = manifest.get('toc')
self.toc_model = TOC(toc)
self.toc.setModel(self.toc_model)
self.bookmarks_widget.set_bookmarks(self.current_book_data['annotations_map']['bookmark'])
def load_book_annotations(self):
amap = self.current_book_data['annotations_map']

View File

@ -6,6 +6,7 @@ from __future__ import absolute_import, division, print_function, unicode_litera
import os
import sys
from itertools import count
from PyQt5.Qt import (
QApplication, QBuffer, QByteArray, QHBoxLayout, QSize, Qt, QTimer, QUrl, QWidget,
@ -177,12 +178,15 @@ class ViewerBridge(Bridge):
toggle_bookmarks = from_js()
update_current_toc_nodes = from_js(object, object)
toggle_full_screen = from_js()
report_cfi = from_js(object, object)
create_view = to_js()
show_preparing_message = to_js()
start_book_load = to_js()
goto_toc_node = to_js()
goto_cfi = to_js()
full_screen_state_changed = to_js()
get_current_cfi = to_js()
class WebPage(QWebEnginePage):
@ -264,6 +268,8 @@ class WebView(RestartingWebEngineView):
def __init__(self, parent=None):
self._host_widget = None
self.callback_id_counter = count()
self.callback_map = {}
self.current_cfi = None
RestartingWebEngineView.__init__(self, parent)
self.dead_renderer_error_shown = False
@ -278,6 +284,7 @@ class WebView(RestartingWebEngineView):
self.bridge.toggle_bookmarks.connect(self.toggle_bookmarks)
self.bridge.update_current_toc_nodes.connect(self.update_current_toc_nodes)
self.bridge.toggle_full_screen.connect(self.toggle_full_screen)
self.bridge.report_cfi.connect(self.call_callback)
self.pending_bridge_ready_actions = {}
self.setPage(self._page)
self.setAcceptDrops(False)
@ -354,6 +361,9 @@ class WebView(RestartingWebEngineView):
def goto_toc_node(self, node_id):
self.execute_when_ready('goto_toc_node', node_id)
def goto_cfi(self, cfi):
self.execute_when_ready('goto_cfi', cfi)
def notify_full_screen_state_change(self, in_fullscreen_mode):
self.execute_when_ready('full_screen_state_changed', in_fullscreen_mode)
@ -364,3 +374,16 @@ class WebView(RestartingWebEngineView):
sd = vprefs['session_data']
sd[key] = val
vprefs['session_data'] = sd
def do_callback(self, func_name, callback):
cid = next(self.callback_id_counter)
self.callback_map[cid] = callback
self.execute_when_ready('get_current_cfi', cid)
def call_callback(self, request_id, data):
callback = self.callback_map.pop(request_id, None)
if callback is not None:
callback(data)
def get_current_cfi(self, callback):
self.do_callback('get_current_cfi', callback)

View File

@ -86,6 +86,7 @@ class IframeBoss:
'gesture_from_margin': self.gesture_from_margin,
'find': self.find,
'window_size': self.received_window_size,
'get_current_cfi': self.get_current_cfi,
}
self.comm = IframeClient(handlers)
self.last_window_ypos = 0
@ -261,6 +262,20 @@ class IframeBoss:
return 0
return self.calculate_progress_frac(current_name, index)
def get_current_cfi(self, data):
cfi = at_current()
if cfi:
spine = self.book.manifest.spine
current_name = current_spine_item().name
index = spine.indexOf(current_name)
if index > -1:
cfi = 'epubcfi(/{}{})'.format(2*(index+1), cfi)
self.send_message(
'report_cfi', cfi=cfi, progress_frac=self.calculate_progress_frac(current_name, index),
file_progress_frac=progress_frac(), request_id=data.request_id)
return
self.send_message('report_cfi', cfi=None, progress_frac=0, file_progress_frac=0)
def update_cfi(self):
cfi = at_current()
if cfi:

View File

@ -157,6 +157,7 @@ class View:
'goto_doc_boundary': def(data): self.goto_doc_boundary(data.start);,
'scroll_to_anchor': self.on_scroll_to_anchor,
'update_cfi': self.on_update_cfi,
'report_cfi': self.on_report_cfi,
'update_toc_position': self.on_update_toc_position,
'content_loaded': self.on_content_loaded,
'show_chrome': self.show_chrome,
@ -503,6 +504,13 @@ class View:
if toc_node:
self.goto_named_destination(toc_node.dest, toc_node.frag)
def get_current_cfi(self, request_id):
self.iframe_wrapper.send_message('get_current_cfi', request_id=request_id)
def on_report_cfi(self, data):
ui_operations.report_cfi(
data.request_id, {'cfi': data.cfi, 'progress_frac': data.progress_frac, 'file_progress_frac': data.file_progress_frac})
def on_update_cfi(self, data):
overlay_shown = not self.processing_spine_item_display and self.overlay.is_visible
if overlay_shown or self.search_overlay.is_visible or self.content_popup_overlay.is_visible:

View File

@ -199,11 +199,21 @@ def goto_toc_node(node_id):
view.goto_toc_node(node_id)
@from_python
def goto_cfi(cfi):
view.goto_bookpos(cfi)
@from_python
def full_screen_state_changed(viewer_in_full_screen):
runtime.viewer_in_full_screen = viewer_in_full_screen
@from_python
def get_current_cfi(request_id):
view.get_current_cfi(request_id)
def onerror(msg, script_url, line_number, column_number, error_object):
if not error_object:
# cross domain error
@ -243,6 +253,8 @@ if window is window.top:
to_python.update_current_toc_nodes(current_node_id, top_level_node_id)
ui_operations.toggle_full_screen = def():
to_python.toggle_full_screen()
ui_operations.report_cfi = def(request_id, data):
to_python.report_cfi(request_id, data)
document.body.appendChild(E.div(id='view'))
window.onerror = onerror
create_modal_container()