Refactor keyboard handling in the viewer

No longer uses deprecated APIs
This commit is contained in:
Kovid Goyal 2019-08-23 09:13:52 +05:30
parent 2548babf59
commit 82fcdf6272
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 228 additions and 86 deletions

View File

@ -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):

View File

@ -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:

View File

@ -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':

View File

@ -0,0 +1,139 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2019, Kovid Goyal <kovid at kovidgoyal.net>
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]