diff --git a/src/pyj/read_book/flow_mode.pyj b/src/pyj/read_book/flow_mode.pyj index 085e918e2c..586cca3979 100644 --- a/src/pyj/read_book/flow_mode.pyj +++ b/src/pyj/read_book/flow_mode.pyj @@ -5,10 +5,9 @@ from __python__ import bound_methods, hash_literals from select import word_at_point from dom import set_css -from keycodes import get_key from read_book.globals import get_boss from read_book.viewport import scroll_viewport -from utils import document_height, document_width, viewport_to_document +from utils import document_height, viewport_to_document def flow_to_scroll_fraction(frac): @@ -127,37 +126,38 @@ def scroll_by_page(backward): window.scrollBy(0, -h if backward else h) -def flow_onkeydown(evt): - handled = False - key = get_key(evt) - if key is 'up' or key is 'down': - handled = True - if evt.ctrlKey: - goto_boundary(-1 if key is 'up' else 1) - else: - smooth_y_scroll(key is 'up') - elif (key is 'left' or key is 'right') and not evt.altKey: - handled = True - if evt.ctrlKey: - scroll_viewport.scroll_to(0 if key is 'left' else document_width(), window.pageYOffset) - else: - window.scrollBy(-15 if key is 'left' else 15, 0) - elif key is 'home' or key is 'end': - handled = True - get_boss().report_human_scroll() - clear_small_scrolls() - if evt.ctrlKey: - get_boss().send_message('goto_doc_boundary', start=key is 'home') - else: - if key is 'home': - scroll_viewport.scroll_to(window.pageXOffset, 0) - else: - scroll_viewport.scroll_to(window.pageXOffset, document_height()) - elif key is 'pageup' or key is 'pagedown' or key is 'space': - handled = True - scroll_by_page(key is 'pageup') - if handled: - evt.preventDefault() +def handle_shortcut(sc_name, evt): + if sc_name is 'down': + smooth_y_scroll(True) + return True + if sc_name is 'up': + smooth_y_scroll(True) + return True + if sc_name is 'start_of_file': + goto_boundary(-1) + return True + if sc_name is 'end_of_file': + goto_boundary(1) + return True + if sc_name is 'left': + window.scrollBy(-15) + return True + if sc_name is 'right': + window.scrollBy(-15) + return True + if sc_name is 'start_of_book': + get_boss().send_message('goto_doc_boundary', start=True) + return True + if sc_name is 'end_of_book': + get_boss().send_message('goto_doc_boundary', start=False) + return True + if sc_name is 'pageup': + scroll_by_page(True) + return True + if sc_name is 'pagedown': + scroll_by_page(False) + return True + return False def layout(is_single_page): diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 1c14332113..1e43c2d673 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -8,8 +8,8 @@ from gettext import gettext as _ from iframe_comm import IframeClient from read_book.cfi import at_current, scroll_to as scroll_to_cfi from read_book.flow_mode import ( - anchor_funcs as flow_anchor_funcs, flow_onkeydown, flow_onwheel, - flow_to_scroll_fraction, handle_gesture as flow_handle_gesture, + anchor_funcs as flow_anchor_funcs, flow_onwheel, flow_to_scroll_fraction, + handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut, layout as flow_layout, scroll_by_page as flow_scroll_by_page ) from read_book.footnotes import is_footnote_link @@ -20,17 +20,18 @@ from read_book.globals import ( from read_book.mathjax import apply_mathjax from read_book.paged_mode import ( anchor_funcs as paged_anchor_funcs, calc_columns_per_screen, - handle_gesture as paged_handle_gesture, jump_to_cfi as paged_jump_to_cfi, - layout as paged_layout, onkeydown as paged_onkeydown, onwheel as paged_onwheel, - progress_frac, reset_paged_mode_globals, scroll_by_page as paged_scroll_by_page, - scroll_to_elem, scroll_to_fraction as paged_scroll_to_fraction, - snap_to_selection + handle_gesture as paged_handle_gesture, handle_shortcut as paged_handle_shortcut, + jump_to_cfi as paged_jump_to_cfi, layout as paged_layout, + onwheel as paged_onwheel, progress_frac, reset_paged_mode_globals, + scroll_by_page as paged_scroll_by_page, scroll_to_elem, + scroll_to_fraction as paged_scroll_to_fraction, snap_to_selection ) from read_book.resources import finalize_resources, unserialize_html from read_book.settings import ( apply_colors, apply_font_size, apply_settings, apply_stylesheet, opts, update_settings ) +from read_book.shortcuts import create_shortcut_map, shortcut_for_key_event from read_book.toc import update_visible_toc_anchors from read_book.touch import create_handlers as create_touch_handlers from read_book.viewport import scroll_viewport @@ -138,7 +139,7 @@ class IframeBoss: if current_layout_mode() is 'flow': self.do_layout = flow_layout self.handle_wheel = flow_onwheel - self.handle_keydown = flow_onkeydown + self.handle_navigation_shortcut = flow_handle_shortcut self._handle_gesture = flow_handle_gesture self.to_scroll_fraction = flow_to_scroll_fraction self.jump_to_cfi = scroll_to_cfi @@ -146,12 +147,13 @@ class IframeBoss: else: self.do_layout = paged_layout self.handle_wheel = paged_onwheel - self.handle_keydown = paged_onkeydown + self.handle_navigation_shortcut = paged_handle_shortcut self.to_scroll_fraction = paged_scroll_to_fraction self.jump_to_cfi = paged_jump_to_cfi self._handle_gesture = paged_handle_gesture self.anchor_funcs = paged_anchor_funcs update_settings(data.settings) + self.keyboard_shortcut_map = create_shortcut_map() set_current_spine_item({'name':data.name, 'is_first':index is 0, 'is_last':index is spine.length - 1, 'initial_position':data.initial_position}) self.last_cfi = None for name in self.blob_url_map: @@ -343,8 +345,14 @@ class IframeBoss: self.handle_wheel(evt) def onkeydown(self, evt): + if current_layout_mode() is not 'flow' and evt.key is 'Tab': + # Prevent the TAB key from shifting focus as it causes partial scrolling + evt.preventDefault() if self.content_ready: - self.handle_keydown(evt) + sc_name = shortcut_for_key_event(evt, self.keyboard_shortcut_map) + if sc_name: + if self.handle_navigation_shortcut(sc_name, evt): + evt.preventDefault() def oncontextmenu(self, evt): if self.content_ready: diff --git a/src/pyj/read_book/paged_mode.pyj b/src/pyj/read_book/paged_mode.pyj index 69b25d73ba..73e58a8d4e 100644 --- a/src/pyj/read_book/paged_mode.pyj +++ b/src/pyj/read_book/paged_mode.pyj @@ -7,7 +7,6 @@ from elementmaker import E from select import word_at_point from dom import set_css -from keycodes import get_key from read_book.cfi import ( at_current as cfi_at_current, at_point as cfi_at_point, scroll_to as cfi_scroll_to @@ -211,15 +210,6 @@ def layout(is_single_page): if is_single_page: is_full_screen_layout = True - # Prevent the TAB key from shifting focus as it causes partial scrolling - document.documentElement.addEventListener( - 'keydown', - def (evt): - if get_key(evt) is 'tab': - evt.preventDefault() - , {'passive': False} - ) - # Some browser engine, WebKit at least, adjust column widths to please # themselves, unless the container width is an exact multiple, so we check # for that and manually set the container widths. @@ -485,39 +475,44 @@ def scroll_by_page(backward, by_screen): get_boss().report_human_scroll() scroll_to_xpos(pos) -def onkeydown(evt): - handled = False - key = get_key(evt) - if key is 'up' or key is 'down': - handled = True - if evt.ctrlKey: - get_boss().report_human_scroll() - scroll_to_offset(0 if key is 'left' else document_width()) - else: - scroll_by_page(key is 'up', True) - elif (key is 'left' or key is 'right') and not evt.altKey: - handled = True - if evt.ctrlKey: - get_boss().report_human_scroll() - scroll_to_offset(0 if key is 'left' else document_width()) - else: - scroll_by_page(key is 'left', False) - elif key is 'home' or key is 'end': - handled = True - if evt.ctrlKey: - get_boss().report_human_scroll() - get_boss().send_message('goto_doc_boundary', start=key is 'home') - else: - if key is 'home': - get_boss().report_human_scroll() - scroll_to_offset(0) - else: - scroll_to_offset(document_width()) - elif key is 'pageup' or key is 'pagedown' or key is 'space': - handled = True - scroll_by_page(key is 'pageup', True) - if handled: - evt.preventDefault() + +def handle_shortcut(sc_name, evt): + if sc_name is 'up': + scroll_by_page(True, True) + return True + if sc_name is 'down': + scroll_by_page(False, True) + return True + if sc_name is 'start_of_file': + get_boss().report_human_scroll() + scroll_to_offset(0) + return True + if sc_name is 'end_of_file': + get_boss().report_human_scroll() + scroll_to_offset(document_width()) + return True + if sc_name is 'left': + scroll_by_page(True, False) + return True + if sc_name is 'right': + scroll_by_page(False, False) + return True + if sc_name is 'start_of_book': + get_boss().report_human_scroll() + get_boss().send_message('goto_doc_boundary', start=True) + return True + if sc_name is 'end_of_book': + get_boss().report_human_scroll() + get_boss().send_message('goto_doc_boundary', start=False) + return True + if sc_name is 'pageup': + scroll_by_page(True, True) + return True + if sc_name is 'pagedown': + scroll_by_page(False, True) + return True + return False + def handle_gesture(gesture): if gesture.type is 'swipe': diff --git a/src/pyj/read_book/shortcuts.pyj b/src/pyj/read_book/shortcuts.pyj new file mode 100644 index 0000000000..9cbb4c4dec --- /dev/null +++ b/src/pyj/read_book/shortcuts.pyj @@ -0,0 +1,139 @@ +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2019, Kovid Goyal +from __python__ import bound_methods, hash_literals + +from gettext import gettext as _ + + +def parse_key_repr(sc): + parts = sc.split('+') + if sc.endsWith('++'): + parts = parts[:-2] + parts.push('+') + key = parts[-1] + ans = {'key': key, 'altKey': False, 'ctrlKey': False, 'metaKey': False, 'shiftKey': False} + for modifier in parts[:-1]: + q = modifier.toLowerCase() + if q is 'ctrl': + ans.ctrlKey = True + elif q is 'alt': + ans.altKey = True + elif q is 'meta': + ans.metaKey = True + elif q is 'shift': + ans.shiftKey = True + return ans + + +def desc(sc, group, short, long): + if jstype(sc) is 'string': + sc = v'[sc]' + pkey = v'[]' + for x in sc: + pkey.push(parse_key_repr(x)) + return {'short': short, 'long': long, 'shortcuts': pkey} + + +def serialize_keyevent(evt): + return { + 'key': evt.key, 'altKey': evt.altKey, 'ctrlKey': evt.ctrlKey, + 'metaKey': evt.metaKey, 'shiftKey': evt.shiftKey + } + + +def unserialize_keyevent(sc): + return sc + + +def keyevent_to_index(evt): + parts = v'[]' + for mod in v"['altKey', 'ctrlKey', 'metaKey', 'shiftKey']": + parts.push('y' if evt[mod] else 'n') + return parts.join('') + evt.key + + +SHORTCUTS = { + 'start_of_file': desc( + v"['Ctrl+ArrowUp', 'Ctrl+ArrowLeft', 'Home']", + 'scroll', + _('Scroll to the beginning of the current file'), + _('When the e-book is made of of multiple individual files, scroll to the start of the current file.'), + ), + + 'start_of_book': desc( + 'Ctrl+Home', + 'scroll', + _('Scroll to the beginning of the book'), + ), + + 'end_of_book': desc( + 'Ctrl+End', + 'scroll', + _('Scroll to the end of the book'), + ), + + 'end_of_file': desc( + v"['Ctrl+ArrowDown', 'Ctrl+ArrowRight', 'End']", + 'scroll', + _('Scroll to the end of the current file'), + _('When the e-book is made of of multiple individual files, scroll to the end of the current file.'), + ), + + 'up': desc( + 'ArrowUp', + 'scroll', + _('Scroll backwards smoothly (by screen-fulls in paged mode)'), + _('Scroll backwards, smoothly in flow mode and by screen fulls in paged mode'), + ), + + 'down': desc( + 'ArrowDown', + 'scroll', + _('Scroll forwards smoothly (by screen-fulls in paged mode)'), + _('Scroll forwards, smoothly in flow mode and by screen fulls in paged mode'), + ), + + 'left': desc( + 'ArrowLeft', + 'scroll', + _('Scroll left'), + _('Scroll leftwards by a little in flow mode and by a page in paged mode'), + ), + + 'right': desc( + 'ArrowRight', + 'scroll', + _('Scroll right'), + _('Scroll rightwards by a little in flow mode and by a page in paged mode'), + ), + + 'pageup': desc( + 'PageUp', + 'scroll', + _('Scroll backwards by screen-fulls'), + ), + + 'pagedown': desc( + v"[' ', 'PageDown']", + 'scroll', + _('Scroll forwards by screen-fulls'), + ), + +} + + +def create_shortcut_map(custom_shortcuts): + ans = {} + for sc_name in Object.keys(SHORTCUTS): + entry = SHORTCUTS[sc_name] + shortcuts = entry.shortcuts + if custom_shortcuts and custom_shortcuts[sc_name]: + shortcuts = custom_shortcuts[sc_name] + for sc in shortcuts: + ans[keyevent_to_index(sc)] = sc_name + return ans + + +def shortcut_for_key_event(evt, shortcut_map): + idx = keyevent_to_index(evt) + return shortcut_map[idx]