Handle multi-page highlight creation

This commit is contained in:
Kovid Goyal 2020-03-22 09:18:21 +05:30
parent 9323019776
commit 8dc0e875c9
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
6 changed files with 144 additions and 28 deletions

View File

@ -2,7 +2,8 @@
# License: GPL v3 Copyright: 2020, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import bound_methods, hash_literals
from dom import svgicon, ensure_id
from dom import ensure_id, svgicon
from read_book.shortcuts import shortcut_for_key_event
WAITING_FOR_CLICK = 1
WAITING_FOR_DRAG = 2
@ -45,6 +46,7 @@ class CreateAnnotation:
self.view = view
self.state = WAITING_FOR_CLICK
self.left_line_height = self.right_line_height = 8
self.in_flow_mode = False
container = self.container
self.position_in_handle = {'x': 0, 'y': 0}
@ -60,6 +62,20 @@ class CreateAnnotation:
container.addEventListener('click', self.container_clicked, {'passive': False})
container.addEventListener('mouseup', self.mouseup_on_container, {'passive': False})
container.addEventListener('mousemove', self.mousemove_on_container, {'passive': False})
container.addEventListener('keydown', self.on_keydown, {'passive': False})
def on_keydown(self, ev):
ev.stopPropagation(), ev.preventDefault()
sc_name = shortcut_for_key_event(ev, self.view.keyboard_shortcut_map)
if sc_name is 'show_chrome':
self.hide()
elif sc_name in ('up', 'down', 'pageup', 'pagedown'):
self.send_message('scroll', backwards=bool('up' in sc_name))
elif sc_name in ('left', 'right'):
if self.in_flow_mode:
self.send_message('perp-scroll', backwards=bool(sc_name is 'left'))
else:
self.send_message('scroll', backwards=bool(sc_name is 'left'))
def container_clicked(self, ev):
ev.stopPropagation(), ev.preventDefault()
@ -118,17 +134,28 @@ class CreateAnnotation:
@property
def current_handle_position(self):
lh, rh = self.left_handle.getBoundingClientRect(), self.right_handle.getBoundingClientRect()
lh, rh = self.left_handle, self.right_handle
lbr, rbr = self.left_handle.getBoundingClientRect(), self.right_handle.getBoundingClientRect()
return {
'start': {'x': Math.round(lh.right), 'y': Math.round(lh.bottom - self.left_line_height // 2)},
'end': {'x': Math.round(rh.left), 'y': Math.round(rh.bottom - self.right_line_height // 2)}
'start': {
'onscreen': lh.style.display is not 'none',
'x': Math.round(lbr.right), 'y': Math.round(lbr.bottom - self.left_line_height // 2)
},
'end': {
'onscreen': rh.style.display is not 'none',
'x': Math.round(rbr.left), 'y': Math.round(rbr.bottom - self.right_line_height // 2)
}
}
def show(self):
self.container.style.display = 'block'
c = self.container
c.style.display = 'block'
c.focus()
def hide(self):
self.container.style.display = 'none'
if self.is_visible:
self.container.style.display = 'none'
self.view.focus_iframe()
def send_message(self, type, **kw):
self.view.iframe_wrapper.send_message('annotations', type=type, **kw)
@ -142,9 +169,12 @@ class CreateAnnotation:
self.hide_handles()
if msg.extents.start.x is not None:
self.place_handles(msg.extents)
self.in_flow_mode = msg.in_flow_mode
elif msg.type is 'position-handles':
if self.state is WAITING_FOR_CLICK:
self.place_handles(msg.extents)
elif msg.type is 'update-handles':
self.place_handles(msg.extents)
else:
print('Ignoring annotations message with unknown type:', msg.type)
@ -158,12 +188,12 @@ class CreateAnnotation:
def do_it(handle, data):
map_from_iframe_coords(data)
s = handle.style
s.display = 'block'
s.display = 'block' if data.onscreen else 'none'
height = data.height * 3
width = data.height * 2
s.width = f'{width}px'
s.height = f'{height}px'
bottom = min(max(0, data.y + data.height), window.innerHeight)
bottom = data.y + data.height
top = bottom - height
s.top = f'{top}px'
return s, width

View File

@ -140,6 +140,16 @@ def scroll_by_page(direction):
h = scroll_viewport.height() - 10
window.scrollBy(0, h * direction)
def scroll_to_extend_annotation(backward, horizontal):
direction = -1 if backward else 1
if horizontal:
before = window.pageXOffset
window.scrollBy(15 * direction, 0)
return window.pageXOffset is not before
h = scroll_viewport.height() - 10
before = window.pageYOffset
window.scrollBy(0, h * direction)
return window.pageYOffset is not before
def is_auto_scroll_active():
return scroll_animator.is_active()

View File

@ -5,7 +5,8 @@ from __python__ import bound_methods, hash_literals
import traceback
from gettext import gettext as _
from select import (
selection_extents, selection_extents_at_point, set_selections_extents_to
extend_selection_after_scroll, selection_extents, selection_extents_at_point,
set_selections_extents_to
)
from fs_images import fix_fullscreen_svg_images
@ -17,7 +18,8 @@ from read_book.flow_mode import (
anchor_funcs as flow_anchor_funcs, auto_scroll_action as flow_auto_scroll_action,
ensure_selection_visible, 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
layout as flow_layout, scroll_by_page as flow_scroll_by_page,
scroll_to_extend_annotation as flow_annotation_scroll
)
from read_book.footnotes import is_footnote_link
from read_book.globals import (
@ -34,6 +36,7 @@ from read_book.paged_mode import (
prepare_for_resize as paged_prepare_for_resize, progress_frac,
reset_paged_mode_globals, resize_done as paged_resize_done,
scroll_by_page as paged_scroll_by_page, scroll_to_elem,
scroll_to_extend_annotation as paged_annotation_scroll,
scroll_to_fraction as paged_scroll_to_fraction, snap_to_selection,
will_columns_per_screen_change
)
@ -207,6 +210,7 @@ class IframeBoss:
self.jump_to_cfi = scroll_to_cfi
self.anchor_funcs = flow_anchor_funcs
self.auto_scroll_action = flow_auto_scroll_action
self.scroll_to_extend_annotation = flow_annotation_scroll
paged_auto_scroll_action('stop')
else:
self.do_layout = paged_layout
@ -217,6 +221,7 @@ class IframeBoss:
self._handle_gesture = paged_handle_gesture
self.anchor_funcs = paged_anchor_funcs
self.auto_scroll_action = paged_auto_scroll_action
self.scroll_to_extend_annotation = paged_annotation_scroll
flow_auto_scroll_action('stop')
update_settings(data.settings)
self.keyboard_shortcut_map = create_shortcut_map(data.settings.keyboard_shortcuts)
@ -605,21 +610,31 @@ class IframeBoss:
end_reference_mode()
def initiate_creation_of_annotation(self):
self.auto_scroll_action('stop')
in_flow_mode = current_layout_mode() is 'flow'
self.send_message(
'annotations',
type='create-annotation',
in_flow_mode=current_layout_mode() is 'flow',
extents=selection_extents(),
in_flow_mode=in_flow_mode,
extents=selection_extents(in_flow_mode),
)
def annotations_msg_received(self, data):
in_flow_mode = current_layout_mode() is 'flow'
if data.type is 'set-selection':
set_selections_extents_to(data.extents)
elif data.type is 'position-handles-at-point':
self.send_message(
'annotations',
type='position-handles',
extents=selection_extents_at_point(data.x, data.y))
extents=selection_extents_at_point(data.x, data.y, in_flow_mode))
elif data.type is 'scroll':
if self.scroll_to_extend_annotation(data.backwards):
extend_selection_after_scroll(data.backwards, in_flow_mode)
self.send_message('annotations', type='update-handles', extents=selection_extents(in_flow_mode))
elif data.type is 'perp-scroll':
if in_flow_mode and flow_annotation_scroll(data.backwards, True):
self.send_message('annotations', type='update-handles', extents=selection_extents(in_flow_mode))
else:
console.log('Ignoring annotations message to iframe with unknown type: ' + data.type)

View File

@ -562,6 +562,14 @@ def scroll_by_page(backward, by_screen):
scroll_to_xpos(pos)
def scroll_to_extend_annotation(backward):
pos = previous_col_location() if backward else next_col_location()
if pos is -1:
return False
scroll_to_xpos(pos)
return True
def handle_shortcut(sc_name, evt):
if sc_name is 'up':
scroll_by_page(True, True)

View File

@ -219,7 +219,7 @@ class View:
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
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id='controls-help-overlay'), # controls help overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id=CreateAnnotation.container_id), # create annotation overlay
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none', id=CreateAnnotation.container_id, tabindex='0'), # create annotation overlay
)
),
E.div(

View File

@ -44,8 +44,11 @@ def word_at_point(x, y):
return r
def range_extents(start, end):
ans = {'start': {'x': None, 'y': None, 'height': None}, 'end': {'x': None, 'y': None, 'height': None}}
def range_extents(start, end, in_flow_mode):
ans = {
'start': {'x': None, 'y': None, 'height': None, 'onscreen': False},
'end': {'x': None, 'y': None, 'height': None, 'onscreen': False}
}
if not start or not end:
return ans
start = start.cloneRange()
@ -58,9 +61,11 @@ def range_extents(start, end):
if not rects.length:
return
rect = rects[0]
ans.x = rect.left
ans.y = rect.top
ans.x = Math.round(rect.left)
ans.y = Math.round(rect.top)
ans.height = rect.bottom - rect.top
if rect.right <= window.innerWidth and rect.bottom <= window.innerHeight and rect.left >= 0 and rect.top >= 0:
ans.onscreen = True
for_boundary(start, ans.start)
for_boundary(end, ans.end)
@ -68,16 +73,16 @@ def range_extents(start, end):
def selection_extents():
def selection_extents(in_flow_mode):
sel = window.getSelection()
if not sel or not sel.rangeCount:
return range_extents()
start = sel.getRangeAt(0)
end = sel.getRangeAt(sel.rangeCount - 1)
return range_extents(start, end)
return range_extents(start, end, in_flow_mode)
def selection_extents_at_point(x, y):
def selection_extents_at_point(x, y, in_flow_mode):
r = word_at_point(x, y)
if r:
sel = window.getSelection()
@ -89,17 +94,65 @@ def selection_extents_at_point(x, y):
ans.start.height = ans.end.height = parseInt(window.getComputedStyle(document.body).fontSize) + 4
ans.start.x = x
ans.end.x = x + ans.start.height * 3
ans.start.onscreen = ans.end.onscreen = True
return ans
def extend_selection_after_scroll(backwards, in_flow_mode):
sel = window.getSelection()
if not sel.rangeCount:
return
r = sel.getRangeAt(0)
q = r.cloneRange()
q.collapse(backwards)
rects = r.getClientRects()
if not rects.length:
return
rect = rects[0]
if backwards:
if in_flow_mode and rect.bottom <= window.innerHeight:
return
if not in_flow_mode and rect.right <= window.innerWidth:
return
else:
if in_flow_mode and rect.top >= 0:
return
if not in_flow_mode and rect.left >= 0:
return
x = window.innerWidth // 2
y = window.innerHeight // 3
if backwards:
y *= 2
p = range_from_point(x, y)
if p:
if backwards:
r.setStart(p.startContainer, p.startOffset)
else:
r.setEnd(p.startContainer, p.startOffset)
def set_selections_extents_to(extents):
start = range_from_point(extents.start.x, extents.start.y)
if start:
if extents.start.onscreen and extents.end.onscreen:
start = range_from_point(extents.start.x, extents.start.y)
if start:
end = range_from_point(extents.end.x, extents.end.y)
if end:
r = document.createRange()
r.setStart(start.startContainer, start.startOffset)
r.setEnd(end.startContainer, end.startOffset)
sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(r)
return
sel = window.getSelection()
if not sel.rangeCount:
return
r = sel.getRangeAt(0)
if extents.start.onscreen:
start = range_from_point(extents.start.x, extents.start.y)
if start:
r.setStart(start.startContainer, start.startOffset)
if extents.end.onscreen:
end = range_from_point(extents.end.x, extents.end.y)
if end:
r = document.createRange()
r.setStart(start.startContainer, start.startOffset)
r.setEnd(end.startContainer, end.startOffset)
sel = window.getSelection()
sel.removeAllRanges()
sel.addRange(r)