mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Refactor keyboard handling in the viewer
No longer uses deprecated APIs
This commit is contained in:
parent
2548babf59
commit
82fcdf6272
@ -5,10 +5,9 @@ from __python__ import bound_methods, hash_literals
|
|||||||
from select import word_at_point
|
from select import word_at_point
|
||||||
|
|
||||||
from dom import set_css
|
from dom import set_css
|
||||||
from keycodes import get_key
|
|
||||||
from read_book.globals import get_boss
|
from read_book.globals import get_boss
|
||||||
from read_book.viewport import scroll_viewport
|
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):
|
def flow_to_scroll_fraction(frac):
|
||||||
@ -127,37 +126,38 @@ def scroll_by_page(backward):
|
|||||||
window.scrollBy(0, -h if backward else h)
|
window.scrollBy(0, -h if backward else h)
|
||||||
|
|
||||||
|
|
||||||
def flow_onkeydown(evt):
|
def handle_shortcut(sc_name, evt):
|
||||||
handled = False
|
if sc_name is 'down':
|
||||||
key = get_key(evt)
|
smooth_y_scroll(True)
|
||||||
if key is 'up' or key is 'down':
|
return True
|
||||||
handled = True
|
if sc_name is 'up':
|
||||||
if evt.ctrlKey:
|
smooth_y_scroll(True)
|
||||||
goto_boundary(-1 if key is 'up' else 1)
|
return True
|
||||||
else:
|
if sc_name is 'start_of_file':
|
||||||
smooth_y_scroll(key is 'up')
|
goto_boundary(-1)
|
||||||
elif (key is 'left' or key is 'right') and not evt.altKey:
|
return True
|
||||||
handled = True
|
if sc_name is 'end_of_file':
|
||||||
if evt.ctrlKey:
|
goto_boundary(1)
|
||||||
scroll_viewport.scroll_to(0 if key is 'left' else document_width(), window.pageYOffset)
|
return True
|
||||||
else:
|
if sc_name is 'left':
|
||||||
window.scrollBy(-15 if key is 'left' else 15, 0)
|
window.scrollBy(-15)
|
||||||
elif key is 'home' or key is 'end':
|
return True
|
||||||
handled = True
|
if sc_name is 'right':
|
||||||
get_boss().report_human_scroll()
|
window.scrollBy(-15)
|
||||||
clear_small_scrolls()
|
return True
|
||||||
if evt.ctrlKey:
|
if sc_name is 'start_of_book':
|
||||||
get_boss().send_message('goto_doc_boundary', start=key is 'home')
|
get_boss().send_message('goto_doc_boundary', start=True)
|
||||||
else:
|
return True
|
||||||
if key is 'home':
|
if sc_name is 'end_of_book':
|
||||||
scroll_viewport.scroll_to(window.pageXOffset, 0)
|
get_boss().send_message('goto_doc_boundary', start=False)
|
||||||
else:
|
return True
|
||||||
scroll_viewport.scroll_to(window.pageXOffset, document_height())
|
if sc_name is 'pageup':
|
||||||
elif key is 'pageup' or key is 'pagedown' or key is 'space':
|
scroll_by_page(True)
|
||||||
handled = True
|
return True
|
||||||
scroll_by_page(key is 'pageup')
|
if sc_name is 'pagedown':
|
||||||
if handled:
|
scroll_by_page(False)
|
||||||
evt.preventDefault()
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def layout(is_single_page):
|
def layout(is_single_page):
|
||||||
|
@ -8,8 +8,8 @@ from gettext import gettext as _
|
|||||||
from iframe_comm import IframeClient
|
from iframe_comm import IframeClient
|
||||||
from read_book.cfi import at_current, scroll_to as scroll_to_cfi
|
from read_book.cfi import at_current, scroll_to as scroll_to_cfi
|
||||||
from read_book.flow_mode import (
|
from read_book.flow_mode import (
|
||||||
anchor_funcs as flow_anchor_funcs, flow_onkeydown, flow_onwheel,
|
anchor_funcs as flow_anchor_funcs, flow_onwheel, flow_to_scroll_fraction,
|
||||||
flow_to_scroll_fraction, handle_gesture as flow_handle_gesture,
|
handle_gesture as flow_handle_gesture, handle_shortcut as flow_handle_shortcut,
|
||||||
layout as flow_layout, scroll_by_page as flow_scroll_by_page
|
layout as flow_layout, scroll_by_page as flow_scroll_by_page
|
||||||
)
|
)
|
||||||
from read_book.footnotes import is_footnote_link
|
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.mathjax import apply_mathjax
|
||||||
from read_book.paged_mode import (
|
from read_book.paged_mode import (
|
||||||
anchor_funcs as paged_anchor_funcs, calc_columns_per_screen,
|
anchor_funcs as paged_anchor_funcs, calc_columns_per_screen,
|
||||||
handle_gesture as paged_handle_gesture, jump_to_cfi as paged_jump_to_cfi,
|
handle_gesture as paged_handle_gesture, handle_shortcut as paged_handle_shortcut,
|
||||||
layout as paged_layout, onkeydown as paged_onkeydown, onwheel as paged_onwheel,
|
jump_to_cfi as paged_jump_to_cfi, layout as paged_layout,
|
||||||
progress_frac, reset_paged_mode_globals, scroll_by_page as paged_scroll_by_page,
|
onwheel as paged_onwheel, progress_frac, reset_paged_mode_globals,
|
||||||
scroll_to_elem, scroll_to_fraction as paged_scroll_to_fraction,
|
scroll_by_page as paged_scroll_by_page, scroll_to_elem,
|
||||||
snap_to_selection
|
scroll_to_fraction as paged_scroll_to_fraction, snap_to_selection
|
||||||
)
|
)
|
||||||
from read_book.resources import finalize_resources, unserialize_html
|
from read_book.resources import finalize_resources, unserialize_html
|
||||||
from read_book.settings import (
|
from read_book.settings import (
|
||||||
apply_colors, apply_font_size, apply_settings, apply_stylesheet, opts,
|
apply_colors, apply_font_size, apply_settings, apply_stylesheet, opts,
|
||||||
update_settings
|
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.toc import update_visible_toc_anchors
|
||||||
from read_book.touch import create_handlers as create_touch_handlers
|
from read_book.touch import create_handlers as create_touch_handlers
|
||||||
from read_book.viewport import scroll_viewport
|
from read_book.viewport import scroll_viewport
|
||||||
@ -138,7 +139,7 @@ class IframeBoss:
|
|||||||
if current_layout_mode() is 'flow':
|
if current_layout_mode() is 'flow':
|
||||||
self.do_layout = flow_layout
|
self.do_layout = flow_layout
|
||||||
self.handle_wheel = flow_onwheel
|
self.handle_wheel = flow_onwheel
|
||||||
self.handle_keydown = flow_onkeydown
|
self.handle_navigation_shortcut = flow_handle_shortcut
|
||||||
self._handle_gesture = flow_handle_gesture
|
self._handle_gesture = flow_handle_gesture
|
||||||
self.to_scroll_fraction = flow_to_scroll_fraction
|
self.to_scroll_fraction = flow_to_scroll_fraction
|
||||||
self.jump_to_cfi = scroll_to_cfi
|
self.jump_to_cfi = scroll_to_cfi
|
||||||
@ -146,12 +147,13 @@ class IframeBoss:
|
|||||||
else:
|
else:
|
||||||
self.do_layout = paged_layout
|
self.do_layout = paged_layout
|
||||||
self.handle_wheel = paged_onwheel
|
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.to_scroll_fraction = paged_scroll_to_fraction
|
||||||
self.jump_to_cfi = paged_jump_to_cfi
|
self.jump_to_cfi = paged_jump_to_cfi
|
||||||
self._handle_gesture = paged_handle_gesture
|
self._handle_gesture = paged_handle_gesture
|
||||||
self.anchor_funcs = paged_anchor_funcs
|
self.anchor_funcs = paged_anchor_funcs
|
||||||
update_settings(data.settings)
|
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})
|
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
|
self.last_cfi = None
|
||||||
for name in self.blob_url_map:
|
for name in self.blob_url_map:
|
||||||
@ -343,8 +345,14 @@ class IframeBoss:
|
|||||||
self.handle_wheel(evt)
|
self.handle_wheel(evt)
|
||||||
|
|
||||||
def onkeydown(self, 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:
|
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):
|
def oncontextmenu(self, evt):
|
||||||
if self.content_ready:
|
if self.content_ready:
|
||||||
|
@ -7,7 +7,6 @@ from elementmaker import E
|
|||||||
from select import word_at_point
|
from select import word_at_point
|
||||||
|
|
||||||
from dom import set_css
|
from dom import set_css
|
||||||
from keycodes import get_key
|
|
||||||
from read_book.cfi import (
|
from read_book.cfi import (
|
||||||
at_current as cfi_at_current, at_point as cfi_at_point,
|
at_current as cfi_at_current, at_point as cfi_at_point,
|
||||||
scroll_to as cfi_scroll_to
|
scroll_to as cfi_scroll_to
|
||||||
@ -211,15 +210,6 @@ def layout(is_single_page):
|
|||||||
if is_single_page:
|
if is_single_page:
|
||||||
is_full_screen_layout = True
|
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
|
# Some browser engine, WebKit at least, adjust column widths to please
|
||||||
# themselves, unless the container width is an exact multiple, so we check
|
# themselves, unless the container width is an exact multiple, so we check
|
||||||
# for that and manually set the container widths.
|
# for that and manually set the container widths.
|
||||||
@ -485,39 +475,44 @@ def scroll_by_page(backward, by_screen):
|
|||||||
get_boss().report_human_scroll()
|
get_boss().report_human_scroll()
|
||||||
scroll_to_xpos(pos)
|
scroll_to_xpos(pos)
|
||||||
|
|
||||||
def onkeydown(evt):
|
|
||||||
handled = False
|
def handle_shortcut(sc_name, evt):
|
||||||
key = get_key(evt)
|
if sc_name is 'up':
|
||||||
if key is 'up' or key is 'down':
|
scroll_by_page(True, True)
|
||||||
handled = True
|
return True
|
||||||
if evt.ctrlKey:
|
if sc_name is 'down':
|
||||||
get_boss().report_human_scroll()
|
scroll_by_page(False, True)
|
||||||
scroll_to_offset(0 if key is 'left' else document_width())
|
return True
|
||||||
else:
|
if sc_name is 'start_of_file':
|
||||||
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()
|
get_boss().report_human_scroll()
|
||||||
scroll_to_offset(0)
|
scroll_to_offset(0)
|
||||||
else:
|
return True
|
||||||
|
if sc_name is 'end_of_file':
|
||||||
|
get_boss().report_human_scroll()
|
||||||
scroll_to_offset(document_width())
|
scroll_to_offset(document_width())
|
||||||
elif key is 'pageup' or key is 'pagedown' or key is 'space':
|
return True
|
||||||
handled = True
|
if sc_name is 'left':
|
||||||
scroll_by_page(key is 'pageup', True)
|
scroll_by_page(True, False)
|
||||||
if handled:
|
return True
|
||||||
evt.preventDefault()
|
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):
|
def handle_gesture(gesture):
|
||||||
if gesture.type is 'swipe':
|
if gesture.type is 'swipe':
|
||||||
|
139
src/pyj/read_book/shortcuts.pyj
Normal file
139
src/pyj/read_book/shortcuts.pyj
Normal 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]
|
Loading…
x
Reference in New Issue
Block a user