def __init__(self, parent=None): self._host_widget = None self.callback_id_counter = count() self.callback_map = {} self.current_cfi = self.current_content_file = None RestartingWebEngineView.__init__(self, parent) self.tts = TTS(self) self.tts.settings_changed.connect(self.tts_settings_changed) self.tts.event_received.connect(self.tts_event_received) self.dead_renderer_error_shown = False self.render_process_failed.connect(self.render_process_died) w = QApplication.instance().desktop().availableGeometry(self).width() QApplication.instance().palette_changed.connect(self.palette_changed) self.show_home_page_on_ready = True self._size_hint = QSize(int(w/3), int(w/2)) self._page = WebPage(self) self._page.linkHovered.connect(self.link_hovered) self.view_is_ready = False self.bridge.bridge_ready.connect(self.on_bridge_ready) self.bridge.view_created.connect(self.on_view_created) self.bridge.content_file_changed.connect(self.on_content_file_changed) self.bridge.set_session_data.connect(self.set_session_data) self.bridge.set_local_storage.connect(self.set_local_storage) self.bridge.reload_book.connect(self.reload_book) self.bridge.toggle_toc.connect(self.toggle_toc) self.bridge.show_search.connect(self.show_search) self.bridge.search_result_not_found.connect(self.search_result_not_found) self.bridge.find_next.connect(self.find_next) self.bridge.toggle_bookmarks.connect(self.toggle_bookmarks) self.bridge.toggle_highlights.connect(self.toggle_highlights) self.bridge.new_bookmark.connect(self.new_bookmark) self.bridge.toggle_inspector.connect(self.toggle_inspector) self.bridge.toggle_lookup.connect(self.toggle_lookup) self.bridge.quit.connect(self.quit) self.bridge.update_current_toc_nodes.connect(self.update_current_toc_nodes) self.bridge.toggle_full_screen.connect(self.toggle_full_screen) self.bridge.ask_for_open.connect(self.ask_for_open) self.bridge.selection_changed.connect(self.selection_changed) self.bridge.autoscroll_state_changed.connect(self.autoscroll_state_changed) self.bridge.read_aloud_state_changed.connect(self.read_aloud_state_changed) self.bridge.view_image.connect(self.view_image) self.bridge.copy_image.connect(self.copy_image) self.bridge.overlay_visibility_changed.connect(self.overlay_visibility_changed) self.bridge.reference_mode_changed.connect(self.reference_mode_changed) self.bridge.show_loading_message.connect(self.show_loading_message) self.bridge.show_error.connect(self.show_error) self.bridge.print_book.connect(self.print_book) self.bridge.clear_history.connect(self.clear_history) self.bridge.reset_interface.connect(self.reset_interface) self.bridge.quit.connect(self.quit) self.bridge.customize_toolbar.connect(self.customize_toolbar) self.bridge.scrollbar_context_menu.connect(self.scrollbar_context_menu) self.bridge.close_prep_finished.connect(self.close_prep_finished) self.bridge.highlights_changed.connect(self.highlights_changed) self.bridge.open_url.connect(safe_open_url) self.bridge.speak_simple_text.connect(self.tts.speak_simple_text) self.bridge.tts.connect(self.tts.action) self.bridge.export_shortcut_map.connect(self.set_shortcut_map) self.shortcut_map = {} self.bridge.report_cfi.connect(self.call_callback) self.bridge.change_background_image.connect(self.change_background_image) self.pending_bridge_ready_actions = {} self.setPage(self._page) self.setAcceptDrops(False) self.setUrl(QUrl('{}://{}/'.format(FAKE_PROTOCOL, FAKE_HOST))) self.urlChanged.connect(self.url_changed) if parent is not None: self.inspector = Inspector(parent.inspector_dock.toggleViewAction(), self) parent.inspector_dock.setWidget(self.inspector)
class WebView(RestartingWebEngineView): cfi_changed = pyqtSignal(object) reload_book = pyqtSignal() toggle_toc = pyqtSignal() show_search = pyqtSignal(object, object) search_result_not_found = pyqtSignal(object) find_next = pyqtSignal(object) toggle_bookmarks = pyqtSignal() toggle_highlights = pyqtSignal() new_bookmark = pyqtSignal(object) toggle_inspector = pyqtSignal() toggle_lookup = pyqtSignal(object) quit = pyqtSignal() update_current_toc_nodes = pyqtSignal(object) toggle_full_screen = pyqtSignal() ask_for_open = pyqtSignal(object) selection_changed = pyqtSignal(object, object) autoscroll_state_changed = pyqtSignal(object) read_aloud_state_changed = pyqtSignal(object) view_image = pyqtSignal(object) copy_image = pyqtSignal(object) overlay_visibility_changed = pyqtSignal(object) reference_mode_changed = pyqtSignal(object) show_loading_message = pyqtSignal(object) show_error = pyqtSignal(object, object, object) print_book = pyqtSignal() reset_interface = pyqtSignal() quit = pyqtSignal() customize_toolbar = pyqtSignal() scrollbar_context_menu = pyqtSignal(object, object, object) close_prep_finished = pyqtSignal(object) highlights_changed = pyqtSignal(object) shortcuts_changed = pyqtSignal(object) paged_mode_changed = pyqtSignal() standalone_misc_settings_changed = pyqtSignal(object) view_created = pyqtSignal(object) def __init__(self, parent=None): self._host_widget = None self.callback_id_counter = count() self.callback_map = {} self.current_cfi = self.current_content_file = None RestartingWebEngineView.__init__(self, parent) self.tts = TTS(self) self.tts.settings_changed.connect(self.tts_settings_changed) self.tts.event_received.connect(self.tts_event_received) self.dead_renderer_error_shown = False self.render_process_failed.connect(self.render_process_died) w = QApplication.instance().desktop().availableGeometry(self).width() QApplication.instance().palette_changed.connect(self.palette_changed) self.show_home_page_on_ready = True self._size_hint = QSize(int(w/3), int(w/2)) self._page = WebPage(self) self._page.linkHovered.connect(self.link_hovered) self.view_is_ready = False self.bridge.bridge_ready.connect(self.on_bridge_ready) self.bridge.view_created.connect(self.on_view_created) self.bridge.content_file_changed.connect(self.on_content_file_changed) self.bridge.set_session_data.connect(self.set_session_data) self.bridge.set_local_storage.connect(self.set_local_storage) self.bridge.reload_book.connect(self.reload_book) self.bridge.toggle_toc.connect(self.toggle_toc) self.bridge.show_search.connect(self.show_search) self.bridge.search_result_not_found.connect(self.search_result_not_found) self.bridge.find_next.connect(self.find_next) self.bridge.toggle_bookmarks.connect(self.toggle_bookmarks) self.bridge.toggle_highlights.connect(self.toggle_highlights) self.bridge.new_bookmark.connect(self.new_bookmark) self.bridge.toggle_inspector.connect(self.toggle_inspector) self.bridge.toggle_lookup.connect(self.toggle_lookup) self.bridge.quit.connect(self.quit) self.bridge.update_current_toc_nodes.connect(self.update_current_toc_nodes) self.bridge.toggle_full_screen.connect(self.toggle_full_screen) self.bridge.ask_for_open.connect(self.ask_for_open) self.bridge.selection_changed.connect(self.selection_changed) self.bridge.autoscroll_state_changed.connect(self.autoscroll_state_changed) self.bridge.read_aloud_state_changed.connect(self.read_aloud_state_changed) self.bridge.view_image.connect(self.view_image) self.bridge.copy_image.connect(self.copy_image) self.bridge.overlay_visibility_changed.connect(self.overlay_visibility_changed) self.bridge.reference_mode_changed.connect(self.reference_mode_changed) self.bridge.show_loading_message.connect(self.show_loading_message) self.bridge.show_error.connect(self.show_error) self.bridge.print_book.connect(self.print_book) self.bridge.clear_history.connect(self.clear_history) self.bridge.reset_interface.connect(self.reset_interface) self.bridge.quit.connect(self.quit) self.bridge.customize_toolbar.connect(self.customize_toolbar) self.bridge.scrollbar_context_menu.connect(self.scrollbar_context_menu) self.bridge.close_prep_finished.connect(self.close_prep_finished) self.bridge.highlights_changed.connect(self.highlights_changed) self.bridge.open_url.connect(safe_open_url) self.bridge.speak_simple_text.connect(self.tts.speak_simple_text) self.bridge.tts.connect(self.tts.action) self.bridge.export_shortcut_map.connect(self.set_shortcut_map) self.shortcut_map = {} self.bridge.report_cfi.connect(self.call_callback) self.bridge.change_background_image.connect(self.change_background_image) self.pending_bridge_ready_actions = {} self.setPage(self._page) self.setAcceptDrops(False) self.setUrl(QUrl('{}://{}/'.format(FAKE_PROTOCOL, FAKE_HOST))) self.urlChanged.connect(self.url_changed) if parent is not None: self.inspector = Inspector(parent.inspector_dock.toggleViewAction(), self) parent.inspector_dock.setWidget(self.inspector) def link_hovered(self, url): if url == 'javascript:void(0)': url = '' self.generic_action('show-status-message', {'text': url}) def shutdown(self): self.tts.shutdown() def set_shortcut_map(self, smap): self.shortcut_map = smap self.shortcuts_changed.emit(smap) def url_changed(self, url): if url.hasFragment(): frag = url.fragment(QUrl.ComponentFormattingOption.FullyDecoded) if frag and frag.startswith('bookpos='): cfi = frag[len('bookpos='):] if cfi: self.current_cfi = cfi self.cfi_changed.emit(cfi) @property def host_widget(self): ans = self._host_widget if ans is not None and not sip.isdeleted(ans): return ans def render_process_died(self): if self.dead_renderer_error_shown: return self.dead_renderer_error_shown = True error_dialog(self, _('Render process crashed'), _( 'The Qt WebEngine Render process has crashed.' ' You should try restarting the viewer.') , show=True) def event(self, event): if event.type() == QEvent.Type.ChildPolished: child = event.child() if 'HostView' in child.metaObject().className(): self._host_widget = child self._host_widget.setFocus(Qt.FocusReason.OtherFocusReason) return QWebEngineView.event(self, event) def sizeHint(self): return self._size_hint def refresh(self): self.pageAction(QWebEnginePage.WebAction.ReloadAndBypassCache).trigger() @property def bridge(self): return self._page.bridge def on_bridge_ready(self): f = QApplication.instance().font() fi = QFontInfo(f) family = f.family() if family in ('.AppleSystemUIFont', 'MS Shell Dlg 2'): family = 'system-ui' ui_data = { 'all_font_families': QFontDatabase().families(), 'ui_font_family': family, 'ui_font_sz': '{}px'.format(fi.pixelSize()), 'show_home_page_on_ready': self.show_home_page_on_ready, 'system_colors': system_colors(), 'QT_VERSION': QT_VERSION, 'short_time_fmt': QLocale.system().timeFormat(QLocale.FormatType.ShortFormat), } self.bridge.create_view( vprefs['session_data'], vprefs['local_storage'], field_metadata.all_metadata(), ui_data) for func, args in iteritems(self.pending_bridge_ready_actions): getattr(self.bridge, func)(*args) def on_view_created(self, data): self.view_created.emit(data) self.view_is_ready = True def on_content_file_changed(self, data): self.current_content_file = data def start_book_load(self, initial_position=None, highlights=None, current_book_data=None): key = (set_book_path.path,) book_url = link_prefix_for_location_links(add_open_at=False) self.execute_when_ready('start_book_load', key, initial_position, set_book_path.pathtoebook, highlights or [], book_url) def execute_when_ready(self, action, *args): if self.bridge.ready: getattr(self.bridge, action)(*args) else: self.pending_bridge_ready_actions[action] = args 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) def set_session_data(self, key, val): if key == '*' and val is None: vprefs['session_data'] = {} apply_font_settings(self) self.paged_mode_changed.emit() self.standalone_misc_settings_changed.emit() elif key != '*': sd = vprefs['session_data'] sd[key] = val vprefs['session_data'] = sd if key in ('standalone_font_settings', 'base_font_size'): apply_font_settings(self) elif key == 'read_mode': self.paged_mode_changed.emit() elif key == 'standalone_misc_settings': self.standalone_misc_settings_changed.emit(val) def set_local_storage(self, key, val): if key == '*' and val is None: vprefs['local_storage'] = {} elif key != '*': sd = vprefs['local_storage'] sd[key] = val vprefs['local_storage'] = sd def do_callback(self, func_name, callback): cid = unicode_type(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) def show_home_page(self): self.execute_when_ready('show_home_page') def change_background_image(self, img_id): files = choose_images(self, 'viewer-background-image', _('Choose background image'), formats=['png', 'gif', 'jpg', 'jpeg']) if files: img = files[0] with open(img, 'rb') as src, open(os.path.join(viewer_config_dir, 'bg-image.data'), 'wb') as dest: dest.write(as_bytes(guess_type(img)[0] or 'image/jpeg') + b'|') shutil.copyfileobj(src, dest) background_image.ans = None self.execute_when_ready('background_image_changed', img_id) def goto_frac(self, frac): self.execute_when_ready('goto_frac', frac) def clear_history(self): self._page.history().clear() def clear_caches(self): self._page.profile().clearHttpCache() def trigger_shortcut(self, which): self.execute_when_ready('trigger_shortcut', which) def show_search_result(self, sr): self.execute_when_ready('show_search_result', sr) def palette_changed(self): self.execute_when_ready('set_system_palette', system_colors()) def prepare_for_close(self): self.execute_when_ready('prepare_for_close') def highlight_action(self, uuid, which): self.execute_when_ready('highlight_action', uuid, which) self.setFocus(Qt.FocusReason.OtherFocusReason) def generic_action(self, which, data): self.execute_when_ready('generic_action', which, data) def tts_event_received(self, which, data): self.execute_when_ready('tts_event', which, data) def tts_settings_changed(self, ui_settings): self.execute_when_ready('tts_event', 'configured', ui_settings)