Viewer: Ensure last read position is fully accurate

This is particularly important when quitting while autoscroll is active
as CFI is not updated during autoscroll.
This commit is contained in:
Kovid Goyal 2020-02-19 21:31:53 +05:30
parent 9730226f6e
commit 2be73652a6
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 49 additions and 6 deletions

View File

@ -14,7 +14,7 @@ from threading import Thread
from PyQt5.Qt import ( from PyQt5.Qt import (
QApplication, QCursor, QDockWidget, QEvent, QMenu, QMimeData, QModelIndex, QApplication, QCursor, QDockWidget, QEvent, QMenu, QMimeData, QModelIndex,
QPixmap, Qt, QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal QPixmap, Qt, QTimer, QToolBar, QUrl, QVBoxLayout, QWidget, pyqtSignal
) )
from calibre import prints from calibre import prints
@ -87,7 +87,7 @@ class EbookViewer(MainWindow):
def __init__(self, open_at=None, continue_reading=None, force_reload=False): def __init__(self, open_at=None, continue_reading=None, force_reload=False):
MainWindow.__init__(self, None) MainWindow.__init__(self, None)
self.shutting_down = False self.shutting_down = self.close_forced = False
self.force_reload = force_reload self.force_reload = force_reload
connect_lambda(self.book_preparation_started, self, lambda self: self.loading_overlay(_( connect_lambda(self.book_preparation_started, self, lambda self: self.loading_overlay(_(
'Preparing book for first read, please wait')), type=Qt.QueuedConnection) 'Preparing book for first read, please wait')), type=Qt.QueuedConnection)
@ -172,6 +172,7 @@ class EbookViewer(MainWindow):
self.web_view.quit.connect(self.quit, type=Qt.QueuedConnection) self.web_view.quit.connect(self.quit, type=Qt.QueuedConnection)
self.web_view.shortcuts_changed.connect(self.shortcuts_changed) self.web_view.shortcuts_changed.connect(self.shortcuts_changed)
self.web_view.scrollbar_context_menu.connect(self.scrollbar_context_menu) self.web_view.scrollbar_context_menu.connect(self.scrollbar_context_menu)
self.web_view.close_prep_finished.connect(self.close_prep_finished)
self.actions_toolbar.initialize(self.web_view, self.search_dock.toggleViewAction()) self.actions_toolbar.initialize(self.web_view, self.search_dock.toggleViewAction())
self.setCentralWidget(self.web_view) self.setCentralWidget(self.web_view)
self.loading_overlay = LoadingOverlay(self) self.loading_overlay = LoadingOverlay(self)
@ -568,7 +569,24 @@ class EbookViewer(MainWindow):
def quit(self): def quit(self):
self.close() self.close()
def force_close(self):
if not self.close_forced:
self.close_forced = True
self.quit()
def close_prep_finished(self, cfi):
if cfi:
self.cfi_changed(cfi)
self.force_close()
def closeEvent(self, ev): def closeEvent(self, ev):
if self.current_book_data and self.web_view.view_is_ready and not self.close_forced:
ev.ignore()
if not self.shutting_down:
self.shutting_down = True
QTimer.singleShot(2000, self.force_close)
self.web_view.prepare_for_close()
return
self.shutting_down = True self.shutting_down = True
self.search_widget.shutdown() self.search_widget.shutdown()
try: try:

View File

@ -273,6 +273,7 @@ class ViewerBridge(Bridge):
quit = from_js() quit = from_js()
customize_toolbar = from_js() customize_toolbar = from_js()
scrollbar_context_menu = from_js(object, object, object) scrollbar_context_menu = from_js(object, object, object)
close_prep_finished = from_js(object)
create_view = to_js() create_view = to_js()
start_book_load = to_js() start_book_load = to_js()
@ -286,6 +287,7 @@ class ViewerBridge(Bridge):
trigger_shortcut = to_js() trigger_shortcut = to_js()
set_system_palette = to_js() set_system_palette = to_js()
show_search_result = to_js() show_search_result = to_js()
prepare_for_close = to_js()
def apply_font_settings(page_or_view): def apply_font_settings(page_or_view):
@ -445,6 +447,7 @@ class WebView(RestartingWebEngineView):
quit = pyqtSignal() quit = pyqtSignal()
customize_toolbar = pyqtSignal() customize_toolbar = pyqtSignal()
scrollbar_context_menu = pyqtSignal(object, object, object) scrollbar_context_menu = pyqtSignal(object, object, object)
close_prep_finished = pyqtSignal(object)
shortcuts_changed = pyqtSignal(object) shortcuts_changed = pyqtSignal(object)
paged_mode_changed = pyqtSignal() paged_mode_changed = pyqtSignal()
standalone_misc_settings_changed = pyqtSignal(object) standalone_misc_settings_changed = pyqtSignal(object)
@ -463,6 +466,7 @@ class WebView(RestartingWebEngineView):
self.show_home_page_on_ready = True self.show_home_page_on_ready = True
self._size_hint = QSize(int(w/3), int(w/2)) self._size_hint = QSize(int(w/3), int(w/2))
self._page = WebPage(self) self._page = WebPage(self)
self.view_is_ready = False
self.bridge.bridge_ready.connect(self.on_bridge_ready) self.bridge.bridge_ready.connect(self.on_bridge_ready)
self.bridge.view_created.connect(self.on_view_created) self.bridge.view_created.connect(self.on_view_created)
self.bridge.content_file_changed.connect(self.on_content_file_changed) self.bridge.content_file_changed.connect(self.on_content_file_changed)
@ -494,6 +498,7 @@ class WebView(RestartingWebEngineView):
self.bridge.quit.connect(self.quit) self.bridge.quit.connect(self.quit)
self.bridge.customize_toolbar.connect(self.customize_toolbar) self.bridge.customize_toolbar.connect(self.customize_toolbar)
self.bridge.scrollbar_context_menu.connect(self.scrollbar_context_menu) self.bridge.scrollbar_context_menu.connect(self.scrollbar_context_menu)
self.bridge.close_prep_finished.connect(self.close_prep_finished)
self.bridge.export_shortcut_map.connect(self.set_shortcut_map) self.bridge.export_shortcut_map.connect(self.set_shortcut_map)
self.shortcut_map = {} self.shortcut_map = {}
self.bridge.report_cfi.connect(self.call_callback) self.bridge.report_cfi.connect(self.call_callback)
@ -578,6 +583,7 @@ class WebView(RestartingWebEngineView):
def on_view_created(self, data): def on_view_created(self, data):
self.view_created.emit(data) self.view_created.emit(data)
self.view_is_ready = True
def on_content_file_changed(self, data): def on_content_file_changed(self, data):
self.current_content_file = data self.current_content_file = data
@ -669,3 +675,6 @@ class WebView(RestartingWebEngineView):
def palette_changed(self): def palette_changed(self):
self.execute_when_ready('set_system_palette', system_colors()) self.execute_when_ready('set_system_palette', system_colors())
def prepare_for_close(self):
self.execute_when_ready('prepare_for_close')

View File

@ -172,7 +172,7 @@ class View:
self.current_progress_frac = self.current_file_progress_frac = 0 self.current_progress_frac = self.current_file_progress_frac = 0
self.current_toc_node = self.current_toc_toplevel_node = None self.current_toc_node = self.current_toc_toplevel_node = None
self.report_cfi_callbacks = {} self.report_cfi_callbacks = {}
self.show_chrome_counter = 0 self.get_cfi_counter = 0
self.show_loading_callback_timer = None self.show_loading_callback_timer = None
self.timer_ids = {'clock': 0} self.timer_ids = {'clock': 0}
self.book_scrollbar = BookScrollbar(self) self.book_scrollbar = BookScrollbar(self)
@ -521,12 +521,11 @@ class View:
self.iframe.contentWindow.focus() self.iframe.contentWindow.focus()
def show_chrome(self, data): def show_chrome(self, data):
self.show_chrome_counter += 1
elements = {} elements = {}
if data and data.elements: if data and data.elements:
elements = data.elements elements = data.elements
initial_panel = data?.initial_panel or None initial_panel = data?.initial_panel or None
self.get_current_cfi('show-chrome-' + self.show_chrome_counter, self.do_show_chrome.bind(None, elements, initial_panel)) self.get_current_cfi('show-chrome', self.do_show_chrome.bind(None, elements, initial_panel))
def do_show_chrome(self, elements, initial_panel, request_id, cfi_data): def do_show_chrome(self, elements, initial_panel, request_id, cfi_data):
self.hide_overlays() self.hide_overlays()
@ -536,6 +535,13 @@ class View:
else: else:
self.overlay.show(elements) self.overlay.show(elements)
def prepare_for_close(self):
def close_prepared(request_id, cfi_data):
ui_operations.close_prep_finished(cfi_data.cfi)
self.get_current_cfi('prepare-close', close_prepared)
def show_search(self): def show_search(self):
self.hide_overlays() self.hide_overlays()
if runtime.is_standalone_viewer: if runtime.is_standalone_viewer:
@ -903,6 +909,8 @@ class View:
self.goto_named_destination(toc_node.dest, toc_node.frag) self.goto_named_destination(toc_node.dest, toc_node.frag)
def get_current_cfi(self, request_id, callback): def get_current_cfi(self, request_id, callback):
self.get_cfi_counter += 1
request_id += ':' + self.get_cfi_counter
self.report_cfi_callbacks[request_id] = callback self.report_cfi_callbacks[request_id] = callback
self.iframe_wrapper.send_message('get_current_cfi', request_id=request_id) self.iframe_wrapper.send_message('get_current_cfi', request_id=request_id)
@ -923,7 +931,7 @@ class View:
def on_report_cfi(self, data): def on_report_cfi(self, data):
cb = self.report_cfi_callbacks[data.request_id] cb = self.report_cfi_callbacks[data.request_id]
if cb: if cb:
cb(data.request_id, { cb(data.request_id.rpartition(':')[0], {
'cfi': data.cfi, 'cfi': data.cfi,
'progress_frac': data.progress_frac, 'progress_frac': data.progress_frac,
'file_progress_frac': data.file_progress_frac, 'file_progress_frac': data.file_progress_frac,

View File

@ -293,6 +293,12 @@ def show_search_result(sr):
if view: if view:
view.show_search_result(sr) view.show_search_result(sr)
@from_python
def prepare_for_close():
if view:
view.prepare_for_close()
else:
ui_operations.close_prep_finished(None)
def onerror(msg, script_url, line_number, column_number, error_object): def onerror(msg, script_url, line_number, column_number, error_object):
if not error_object: if not error_object:
@ -405,6 +411,8 @@ if window is window.top:
to_python.search_result_not_found(sr) to_python.search_result_not_found(sr)
ui_operations.scrollbar_context_menu = def(x, y, frac): ui_operations.scrollbar_context_menu = def(x, y, frac):
to_python.scrollbar_context_menu(x, y, frac) to_python.scrollbar_context_menu(x, y, frac)
ui_operations.close_prep_finished = def(cfi):
to_python.close_prep_finished(cfi)
document.body.appendChild(E.div(id='view')) document.body.appendChild(E.div(id='view'))
window.onerror = onerror window.onerror = onerror