From b4731c552cf4ef46705cad8a6d90234ee6875dd8 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Tue, 15 Dec 2020 14:12:37 +0530 Subject: [PATCH] Wire up the hints overlay --- src/pyj/read_book/hints.pyj | 47 +++++++++++++++++++++++++++++++++ src/pyj/read_book/iframe.pyj | 22 +++++++++++---- src/pyj/read_book/shortcuts.pyj | 6 +++++ src/pyj/read_book/view.pyj | 20 ++++++++++++++ 4 files changed, 90 insertions(+), 5 deletions(-) diff --git a/src/pyj/read_book/hints.pyj b/src/pyj/read_book/hints.pyj index 0c8b4f8065..7046ba5007 100644 --- a/src/pyj/read_book/hints.pyj +++ b/src/pyj/read_book/hints.pyj @@ -3,6 +3,53 @@ from __python__ import bound_methods, hash_literals +class Hints: + + def __init__(self, view): + self.view = view + container = self.container + container.setAttribute('tabindex', '0') + container.style.overflow = 'hidden' + container.addEventListener('keydown', self.on_keydown, {'passive': False}) + container.addEventListener('click', self.container_clicked, {'passive': False}) + + @property + def container(self): + return document.getElementById('book-hints-overlay') + + @property + def is_visible(self): + return self.container.style.display is not 'none' + + def focus(self): + self.container.focus() + + def hide(self): + if self.is_visible: + self.container.style.display = 'none' + self.send_message('hide') + + def show(self): + if not self.is_visible: + self.container.style.display = 'block' + self.focus() + self.send_message('show') + + def on_keydown(self, ev): + ev.preventDefault(), ev.stopPropagation() + + def container_clicked(self, ev): + ev.stopPropagation(), ev.preventDefault() + self.hide() + + def send_message(self, type, **kw): + self.view.iframe_wrapper.send_message('hints', type=type, **kw) + + def handle_message(self, msg): + if msg.type is 'shown': + self.hints_map = msg.hints_map + + def is_visible(a): if not a.offsetParent: return False diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index e85163bd12..bd4cb51710 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -11,6 +11,7 @@ from range_utils import ( last_span_for_crw, reset_highlight_counter, select_crw, unwrap_all_crw, unwrap_crw, wrap_text_in_range ) +from read_book.hints import hint_visible_links, unhint_links from read_book.cfi import cfi_for_selection, range_from_cfi from read_book.extract import get_elements from read_book.find import ( @@ -148,6 +149,7 @@ class IframeBoss: 'handle_navigation_shortcut': self.on_handle_navigation_shortcut, 'annotations': self.annotations_msg_received, 'tts': self.tts_msg_received, + 'hints': self.hints_msg_received, 'copy_selection': self.copy_selection, 'replace_highlights': self.replace_highlights, 'clear_selection': def(): window.getSelection().removeAllRanges();, @@ -228,6 +230,7 @@ class IframeBoss: set_toc_anchor_map() self.replace_history_on_next_cfi_update = True self.book = current_book.book = data.book + self.link_attr = 'data-' + self.book.manifest.link_uid self.reference_mode_enabled = data.reference_mode_enabled self.is_titlepage = data.is_titlepage spine = self.book.manifest.spine @@ -650,9 +653,9 @@ class IframeBoss: def send_message(self, action, **data): self.comm.send_message(action, data) + def connect_links(self): - link_attr = 'data-' + self.book.manifest.link_uid - for a in document.body.querySelectorAll(f'a[{link_attr}]'): + for a in document.body.querySelectorAll(f'a[{self.link_attr}]'): a.addEventListener('click', self.link_activated) if runtime.is_standalone_viewer: # links with a target get turned into requests to open a new window by Qt @@ -669,11 +672,10 @@ class IframeBoss: self.send_message('view_image', calibre_src=img.dataset.calibreSrc) def link_activated(self, evt): - link_attr = 'data-' + self.book.manifest.link_uid try: - data = JSON.parse(evt.currentTarget.getAttribute(link_attr)) + data = JSON.parse(evt.currentTarget.getAttribute(self.link_attr)) except: - print('WARNING: Failed to parse link data {}, ignoring'.format(evt.currentTarget?.getAttribute?(link_attr))) + print('WARNING: Failed to parse link data {}, ignoring'.format(evt.currentTarget?.getAttribute?(self.link_attr))) return name, frag = data.name, data.frag if not name: @@ -917,5 +919,15 @@ class IframeBoss: if select_tts_mark(occurrence_number): self.ensure_selection_visible() + def hints_msg_received(self, data): + if data.type is 'show': + # clear selection so that it does not confuse with the hints which use the same colors + window.getSelection().removeAllRanges() + hints_map = hint_visible_links(self.link_attr) + self.send_message('hints', type='shown', hints_map=hints_map) + elif data.type is 'hide': + unhint_links(self.link_attr) + + def main(): main.boss = IframeBoss() diff --git a/src/pyj/read_book/shortcuts.pyj b/src/pyj/read_book/shortcuts.pyj index ce40c04af4..10bb169836 100644 --- a/src/pyj/read_book/shortcuts.pyj +++ b/src/pyj/read_book/shortcuts.pyj @@ -194,6 +194,12 @@ def shortcuts_definition(): _('Read aloud') ), + 'toggle_hints': desc( + 'Alt+f', + 'ui', + _('Follow links with the keyboard') + ), + 'copy_to_clipboard': desc( v"['Ctrl+c', 'Meta+c']", 'ui', diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj index a964034892..b3bca5db5e 100644 --- a/src/pyj/read_book/view.pyj +++ b/src/pyj/read_book/view.pyj @@ -35,6 +35,7 @@ from read_book.scrollbar import BookScrollbar from read_book.search import SearchOverlay, find_in_spine from read_book.selection_bar import SelectionBar from read_book.read_aloud import ReadAloud +from read_book.hints import Hints from read_book.shortcuts import create_shortcut_map from read_book.timers import Timers from read_book.toc import get_current_toc_nodes, update_visible_toc_nodes @@ -245,6 +246,7 @@ class View: self.book_scrollbar.create(), E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-selection-bar-overlay'), # selection bar overlay E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-read-aloud-overlay'), # read aloud overlay + E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-hints-overlay'), # hints overlay E.div(style='position: absolute; top:0; left:0; width: 100%; pointer-events:none; display:none', id='book-search-overlay'), # search overlay E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='book-content-popup-overlay'), # content popup overlay E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; overflow: auto; display:none', id='book-overlay'), # main overlay @@ -317,6 +319,7 @@ class View: self.overlay = Overlay(self) self.selection_bar = SelectionBar(self) self.read_aloud = ReadAloud(self) + self.hints = Hints(self) self.processing_spine_item_display = False self.pending_load = None self.currently_showing = {'selection': {'empty': True}} @@ -349,6 +352,9 @@ class View: def on_tts_message(self, data): self.read_aloud.handle_message(data) + def on_hints_message(self, data): + self.hints.handle_message(data) + def left_margin_clicked(self, event): if event.button is 0: event.preventDefault(), event.stopPropagation() @@ -452,6 +458,7 @@ class View: if visible: self.selection_bar.hide() self.read_aloud.hide() + self.hints.hide() else: self.selection_bar.update_position() @@ -507,6 +514,8 @@ class View: self.toggle_reference_mode() elif data.name is 'read_aloud': self.start_read_aloud() + elif data.name is 'toggle_hints': + self.toggle_hints() elif data.name is 'toggle_read_aloud': self.toggle_read_aloud() elif data.name is 'reload_book': @@ -673,11 +682,14 @@ class View: self.selection_bar.focus() elif self.read_aloud.is_visible: self.read_aloud.focus() + elif self.hints.is_visible: + self.hints.focus() else: self.iframe.contentWindow.focus() def start_read_aloud(self, dont_start_talking): self.selection_bar.hide() + self.hints.hide() self.read_aloud.show() if not dont_start_talking: self.read_aloud.play() @@ -688,6 +700,14 @@ class View: else: self.start_read_aloud() + def toggle_hints(self): + if self.hints.is_visible: + self.hints.hide() + else: + self.selection_bar.hide() + self.read_aloud.hide() + self.hints.show() + def show_chrome(self, data): elements = {} if data and data.elements: