E-book viewer: Allow configuring the actions triggered by touch gestures. Fixes #2023367 [Feature Request: Customizable Touchscreen Behaviors in Content Server's Web Viewer](https://bugs.launchpad.net/calibre/+bug/2023367)

This commit is contained in:
Kovid Goyal 2023-06-12 08:00:19 +05:30
parent 97ad986ace
commit 748ffc7fb3
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 220 additions and 24 deletions

View File

@ -32,7 +32,7 @@ def get_action_descriptions():
}, },
'highlight_or_inspect': { 'highlight_or_inspect': {
'short': _('Highlight or inspect'), 'short': _('Highlight or inspect'),
'long': _('Highlight the word under the tap point or') 'long': _('Highlight the word under the tap point or view the image under the tap or select the highlight under the tap')
}, },
'decrease_font_size': { 'decrease_font_size': {
'short': _('Make text smaller'), 'short': _('Make text smaller'),
@ -67,14 +67,14 @@ def get_action_descriptions():
only_flow_swipe_mode_actions = {'pan': True, 'animated_scroll': True} only_flow_swipe_mode_actions = {'pan': True, 'animated_scroll': True}
only_tap_actions = {'highlight_or_inspect': True} only_tap_actions = {'highlight_or_inspect': True}
default_actions_for_gesture = { default_actions_for_gesture = {
'common': { 'common': {
GESTURE.back_zone_tap: 'prev_page', GESTURE.back_zone_tap: 'prev_page',
GESTURE.forward_zone_tap: 'next_page', GESTURE.forward_zone_tap: 'next_page',
GESTURE.control_zone_tap: 'show_chrome', GESTURE.control_zone_tap: 'show_chrome',
GESTURE.two_finger_tap: 'show_chrome',
GESTURE.long_tap: 'highlight_or_inspect', GESTURE.long_tap: 'highlight_or_inspect',
GESTURE.two_finger_tap: 'show_chrome',
GESTURE.pinch_in: 'decrease_font_size', GESTURE.pinch_in: 'decrease_font_size',
GESTURE.pinch_out: 'increase_font_size', GESTURE.pinch_out: 'increase_font_size',
}, },
@ -98,15 +98,30 @@ default_actions_for_gesture = {
}, },
} }
def action_for_gesture(gesture, in_flow_mode): def current_action_for_gesture_type(overrides, gesture_type, in_flow_mode):
overrides = opts.gesture_overrides
mode = 'flow_mode' if in_flow_mode else 'paged_mode' mode = 'flow_mode' if in_flow_mode else 'paged_mode'
mode_overrides = overrides[mode] or {} mode_overrides = overrides[mode] or {}
if mode_overrides[gesture.type]: if mode_overrides[gesture_type]:
return mode_overrides[gesture.type] return mode_overrides[gesture_type]
common_overrides = overrides.common or {} common_overrides = overrides.common or {}
if common_overrides[gesture.type]: if common_overrides[gesture_type]:
return common_overrides[gesture.type] return common_overrides[gesture_type]
if default_actions_for_gesture[mode][gesture.type]: if default_actions_for_gesture[mode][gesture_type]:
return default_actions_for_gesture[mode][gesture.type] return default_actions_for_gesture[mode][gesture_type]
return default_actions_for_gesture.common[gesture.type] or 'none' return default_actions_for_gesture.common[gesture_type] or 'none'
def action_for_gesture(gesture, in_flow_mode):
return current_action_for_gesture_type(opts.gesture_overrides, gesture.type, in_flow_mode)
def allowed_actions_for_tap():
return [ac for ac in Object.keys(get_action_descriptions()) if not only_flow_swipe_mode_actions[ac]]
def allowed_actions_for_paged_mode_swipe():
return [ac for ac in Object.keys(get_action_descriptions()) if not only_flow_swipe_mode_actions[ac] and not only_tap_actions[ac]]
allowed_actions_for_two_fingers = allowed_actions_for_paged_mode_swipe
def allowed_actions_for_flow_mode_flick():
return [ac for ac in Object.keys(get_action_descriptions()) if not only_tap_actions[ac]]
def allowed_actions_for_flow_mode_drag():
return ['pan', 'none']

View File

@ -17,6 +17,7 @@ from read_book.prefs.layout import commit_layout, create_layout_panel
from read_book.prefs.misc import commit_misc, create_misc_panel from read_book.prefs.misc import commit_misc, create_misc_panel
from read_book.prefs.scrolling import commit_scrolling, create_scrolling_panel from read_book.prefs.scrolling import commit_scrolling, create_scrolling_panel
from read_book.prefs.selection import commit_selection, create_selection_panel from read_book.prefs.selection import commit_selection, create_selection_panel
from read_book.prefs.touch import commit_touch, create_touch_panel
from read_book.prefs.user_stylesheet import ( from read_book.prefs.user_stylesheet import (
commit_user_stylesheet, create_user_stylesheet_panel commit_user_stylesheet, create_user_stylesheet_panel
) )
@ -106,6 +107,7 @@ class Prefs:
ci(_('Headers and footers'), 'head_foot', _('Customize the headers and footers')) ci(_('Headers and footers'), 'head_foot', _('Customize the headers and footers'))
ci(_('Scrolling behavior'), 'scrolling', _('Control how the viewer scrolls')) ci(_('Scrolling behavior'), 'scrolling', _('Control how the viewer scrolls'))
ci(_('Selection behavior'), 'selection', _('Control how the viewer selects text')) ci(_('Selection behavior'), 'selection', _('Control how the viewer selects text'))
ci(_('Touch behavior'), 'touch', _('Customize what various touchscreen gestures do'))
ci(_('Keyboard shortcuts'), 'keyboard', _('Customize the keyboard shortcuts')) ci(_('Keyboard shortcuts'), 'keyboard', _('Customize the keyboard shortcuts'))
if runtime.is_standalone_viewer: if runtime.is_standalone_viewer:
ci(_('Fonts'), 'fonts', _('Font choices')) ci(_('Fonts'), 'fonts', _('Font choices'))
@ -166,6 +168,13 @@ class Prefs:
def close_selection(self): def close_selection(self):
commit_selection(self.onchange, self.container) commit_selection(self.onchange, self.container)
def display_touch(self, container):
self.create_panel(container, 'touch', create_touch_panel)
def close_touch(self):
commit_touch(self.onchange, self.container)
def create_prefs_panel(container, close_func, on_change): def create_prefs_panel(container, close_func, on_change):
return Prefs(container, close_func, on_change) return Prefs(container, close_func, on_change)

View File

@ -0,0 +1,170 @@
# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals
from elementmaker import E
from book_list.globals import get_session_data
from dom import unique_id
from gettext import gettext as _
from read_book.gestures import (
allowed_actions_for_flow_mode_drag, allowed_actions_for_flow_mode_flick,
allowed_actions_for_paged_mode_swipe, allowed_actions_for_tap,
allowed_actions_for_two_fingers, current_action_for_gesture_type,
get_action_descriptions
)
from read_book.prefs.utils import create_button_box
from read_book.touch import GESTURE, GESTURE_NAMES
CONTAINER = unique_id('touch-settings')
def restore_defaults():
apply_settings_to_ui({})
def get_container():
return document.getElementById(CONTAINER)
def apply_settings_to_ui(overrides):
overrides = overrides or {}
for group in get_container().querySelectorAll('[data-group]'):
group_name = group.dataset.group
in_flow_mode = group_name.indexOf('flow') >= 0
for select in group.querySelectorAll('select'):
allowed_actions = v'[]'
for option in select.querySelectorAll('option'):
allowed_actions.push(option.value)
gesture_type = select.name
current_action = current_action_for_gesture_type(overrides, gesture_type, in_flow_mode)
if allowed_actions.indexOf(current_action) < 0:
current_action = current_action_for_gesture_type({}, gesture_type, in_flow_mode)
if not current_action or allowed_actions.indexOf(current_action) < 0:
current_action = 'none'
select.value = current_action
def get_overrides_from_ui():
ans = {}
for group in get_container().querySelectorAll('[data-group]'):
group_name = group.dataset.group
in_flow_mode = group_name.indexOf('flow') >= 0
if group_name is 'paged_swipe':
attr = 'paged_mode'
elif group_name is 'flow_swipe':
attr = 'flow_mode'
else:
attr = 'common'
if not ans[attr]:
ans[attr] = {}
for select in group.querySelectorAll('select'):
val = select.value
defval = current_action_for_gesture_type({}, select.name, in_flow_mode)
if val is not defval:
ans[attr][select.name] = val
for which in Object.keys(ans):
if Object.keys(ans[which]).length is 0:
v'delete ans[which]'
return ans
def create_touch_panel(container, apply_func, cancel_func):
container.appendChild(E.div(id=CONTAINER, style='margin: 1rem'))
container = container.lastChild
sd = get_session_data()
overrides = sd.get('gesture_overrides')
action_descriptions = get_action_descriptions()
in_flow_mode = False
def on_select_change(ev):
select = ev.target
ad = action_descriptions[select.value]
span = select.parentNode.querySelector('span')
span.textContent = ad.long
def make_setting(gesture_type, allowed_actions):
ans = E.div(style='margin-top: 1ex')
title = GESTURE_NAMES[gesture_type]
sid = unique_id(gesture_type)
ans.appendChild(E.h4(E.label(title, 'for'=sid)))
select = E.select(name=gesture_type, id=sid)
for action in allowed_actions:
ad = action_descriptions[action]
select.appendChild(E.option(ad.short, value=action))
select.addEventListener('change', on_select_change)
ans.appendChild(E.div(select, '\xa0', E.span(style='font-size: smaller; font-style: italic')))
on_select_change({'target': select})
return ans
container.appendChild(E.h2(_('Tapping')))
container.appendChild(E.div(_(
'There are three tap zones, depending on where on the screen you tap. When the tap is'
' on a link, the link is followed, otherwise a configurable action based on the zone is performed.')))
c = E.div('data-group'='tap')
container.appendChild(c)
aat = allowed_actions_for_tap()
c.appendChild(make_setting(GESTURE.control_zone_tap, aat))
c.appendChild(make_setting(GESTURE.forward_zone_tap, aat))
c.appendChild(make_setting(GESTURE.back_zone_tap, aat))
c.appendChild(make_setting(GESTURE.long_tap, aat))
container.appendChild(E.hr())
container.appendChild(E.h2(_('Two finger gestures')))
c = E.div('data-group'='two_finger')
container.appendChild(c)
aat = allowed_actions_for_two_fingers()
c.appendChild(make_setting(GESTURE.two_finger_tap, aat))
c.appendChild(make_setting(GESTURE.pinch_in, aat))
c.appendChild(make_setting(GESTURE.pinch_out, aat))
container.appendChild(E.hr())
container.appendChild(E.h2(_('Swiping')))
container.appendChild(E.div(_(
'Swiping works differently in paged and flow mode, with different actions. For an English like language, swiping in'
' the writing direction means swiping horizontally. For languages written vertically, it means swiping vertically.'
' For languages written left-to-right "going forward" means swiping right-to-left, like turning a page with your finger.'
)))
container.appendChild(E.h3(_('Swiping in paged mode'), style='padding-top: 1ex'))
c = E.div('data-group'='paged_swipe')
container.appendChild(c)
aap = allowed_actions_for_paged_mode_swipe()
c.appendChild(make_setting(GESTURE.flick_block_forward, aap))
c.appendChild(make_setting(GESTURE.flick_block_backward, aap))
c.appendChild(make_setting(GESTURE.flick_inline_forward, aap))
c.appendChild(make_setting(GESTURE.flick_inline_backward, aap))
c.appendChild(make_setting(GESTURE.swipe_inline_forward_hold, aap))
c.appendChild(make_setting(GESTURE.swipe_inline_backward_hold, aap))
c.appendChild(make_setting(GESTURE.swipe_block_forward_hold, aap))
c.appendChild(make_setting(GESTURE.swipe_block_backward_hold, aap))
container.appendChild(E.hr())
container.appendChild(E.h3(_('Swiping in flow mode'), style='padding-top: 1ex'))
c = E.div('data-group'='flow_swipe')
container.appendChild(c)
in_flow_mode = True
in_flow_mode
aaf = allowed_actions_for_flow_mode_flick()
c.appendChild(make_setting(GESTURE.flick_block_forward, aaf))
c.appendChild(make_setting(GESTURE.flick_block_backward, aaf))
c.appendChild(make_setting(GESTURE.flick_inline_forward, aaf))
c.appendChild(make_setting(GESTURE.flick_inline_backward, aaf))
aaf = allowed_actions_for_flow_mode_drag()
c.appendChild(make_setting(GESTURE.swipe_inline_backward_in_progress, aaf))
c.appendChild(make_setting(GESTURE.swipe_inline_forward_in_progress, aaf))
c.appendChild(make_setting(GESTURE.swipe_block_backward_in_progress, aaf))
c.appendChild(make_setting(GESTURE.swipe_block_forward_in_progress, aaf))
container.appendChild(E.hr())
container.appendChild(create_button_box(restore_defaults, apply_func, cancel_func))
apply_settings_to_ui(overrides)
develop = create_touch_panel
def commit_touch(onchange):
sd = get_session_data()
current_overrides = sd.get('gesture_overrides')
overrides = get_overrides_from_ui()
changed = overrides != current_overrides
if changed:
sd.set('gesture_overrides', overrides)
onchange()

View File

@ -21,18 +21,20 @@ GESTURE_NAMES = {
'pinch_in': _('Pinch in'), 'pinch_in': _('Pinch in'),
'pinch_out': _('Pinch out'), 'pinch_out': _('Pinch out'),
'flick_inline_backward': _('Flick in writing direction, backwards'), 'flick_inline_backward': _('Flick in writing direction, to go back'),
'flick_inline_forward': _('Flick in writing direction, forwards'), 'flick_inline_forward': _('Flick in writing direction, to go forward'),
'flick_block_backward': _('Flick perpendicular to writing direction, backwards'), 'flick_block_backward': _('Flick perpendicular to writing direction, to go forward'),
'flick_block_forward': _('Flick perpendicular to writing direction, forwards'), 'flick_block_forward': _('Flick perpendicular to writing direction, to go back'),
'swipe_inline_backward_in_progress': _('Swipe in writing direction, backwards, in-progress'),
'swipe_inline_forward_in_progress': _('Swipe in writing direction, forwards, in-progress'), 'swipe_inline_backward_in_progress': _('Drag finger in writing direction, to go back'),
'swipe_block_backward_in_progress': _('Swipe perpendicular to writing direction, backwards, in-progress'), 'swipe_inline_forward_in_progress': _('Drag finger in writing direction, to go forward'),
'swipe_block_forward_in_progress': _('Swipe perpendicular to writing direction, forwards, in-progress'), 'swipe_block_backward_in_progress': _('Drag finger perpendicular to writing direction, to go back'),
'swipe_inline_backward_hold': _('Swipe in writing direction, backwards and hold'), 'swipe_block_forward_in_progress': _('Drag finger perpendicular to writing direction, to go forward'),
'swipe_inline_forward_hold': _('Swipe in writing direction, forwards and hold'),
'swipe_block_backward_hold': _('Swipe perpendicular to writing direction, backwards and hold'), 'swipe_inline_backward_hold': _('Drag and hold finger in writing direction, to go back'),
'swipe_block_forward_hold': _('Swipe perpendicular to writing direction, forwards and hold'), 'swipe_inline_forward_hold': _('Drag and hold finger in writing direction, to go forward'),
'swipe_block_backward_hold': _('Drag and hold finger perpendicular to writing direction, to go back'),
'swipe_block_forward_hold': _('Drag and hold finger perpendicular to writing direction, to go forward'),
} }
GESTURE = {k:k for k in Object.keys(GESTURE_NAMES)} GESTURE = {k:k for k in Object.keys(GESTURE_NAMES)}
GESTURE.tap = 'tap' GESTURE.tap = 'tap'