mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Handle multi-page highlight creation
This commit is contained in:
parent
9323019776
commit
8dc0e875c9
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user