diff --git a/imgsrc/srv/selection-handle.svg b/imgsrc/srv/selection-handle.svg
new file mode 100644
index 0000000000..ab4611e539
--- /dev/null
+++ b/imgsrc/srv/selection-handle.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/pyj/read_book/create_annotation.pyj b/src/pyj/read_book/create_annotation.pyj
index 69a7cc8f84..ed90855d57 100644
--- a/src/pyj/read_book/create_annotation.pyj
+++ b/src/pyj/read_book/create_annotation.pyj
@@ -2,6 +2,31 @@
# License: GPL v3 Copyright: 2020, Kovid Goyal
from __python__ import bound_methods, hash_literals
+from dom import svgicon, ensure_id
+
+WAITING_FOR_CLICK = 0
+WAITING_FOR_DRAG = 1
+DRAGGING_LEFT = 2
+DRAGGING_RIGHT = 3
+
+
+def selection_handle(invert):
+ ans = svgicon('selection-handle')
+ s = ans.style
+ if invert:
+ s.transform = 'scaleX(-1)'
+ s.position = 'absolute'
+ s.boxSizing = 'border-box'
+ return ans
+
+
+def map_from_iframe_coords(point):
+ l = document.getElementById('book-left-margin')
+ point.x += l.offsetWidth
+ t = document.getElementById('book-top-margin')
+ point.y += t.offsetHeight
+ return point
+
class CreateAnnotation:
@@ -9,11 +34,28 @@ class CreateAnnotation:
def __init__(self, view):
self.view = view
+ self.state = WAITING_FOR_CLICK
+ container = self.container
+
+ lh = selection_handle()
+ self.left_handle_id = ensure_id(lh, 'handle')
+ container.appendChild(lh)
+ rh = selection_handle(True)
+ self.right_handle_id = ensure_id(rh, 'handle')
+ container.appendChild(rh)
@property
def container(self):
return document.getElementById(self.container_id)
+ @property
+ def left_handle(self):
+ return document.getElementById(self.left_handle_id)
+
+ @property
+ def right_handle(self):
+ return document.getElementById(self.right_handle_id)
+
@property
def is_visible(self):
return self.container.style.display is not 'none'
@@ -29,6 +71,38 @@ class CreateAnnotation:
def handle_message(self, msg):
if msg.type is 'create-annotation':
+ if not self.is_visible:
+ self.view.hide_overlays()
+ self.state = WAITING_FOR_CLICK
self.show()
+ self.hide_handles()
+ if msg.extents.start.x is not None:
+ self.place_handles(msg.extents)
else:
print('Ignoring annotations message with unknown type:', msg.type)
+
+ def hide_handles(self):
+ self.left_handle.style.display = 'none'
+ self.right_handle.style.display = 'none'
+
+ def place_handles(self, extents):
+ lh, rh = self.left_handle, self.right_handle
+
+ def do_it(handle, data):
+ map_from_iframe_coords(data)
+ s = handle.style
+ s.display = 'block'
+ 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)
+ top = bottom - height
+ s.top = f'{top}px'
+ return s, width
+
+ style, width = do_it(lh, extents.start)
+ style.left = min(max(0, extents.start.x), window.innerWidth - width // 2) + 'px'
+ style, width = do_it(rh, extents.end)
+ style.left = (min(max(width // 2, extents.end.x), window.innerWidth) - width) + 'px'
+ self.state = WAITING_FOR_DRAG
diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj
index 3adcace5d3..66ff20b341 100644
--- a/src/pyj/read_book/iframe.pyj
+++ b/src/pyj/read_book/iframe.pyj
@@ -12,9 +12,9 @@ from read_book.extract import get_elements
from read_book.find import reset_find_caches, select_search_result
from read_book.flow_mode import (
anchor_funcs as flow_anchor_funcs, auto_scroll_action as flow_auto_scroll_action,
- 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, ensure_selection_visible
+ 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
)
from read_book.footnotes import is_footnote_link
from read_book.globals import (
@@ -50,7 +50,7 @@ from read_book.touch import (
create_handlers as create_touch_handlers, reset_handlers as reset_touch_handlers
)
from read_book.viewport import scroll_viewport
-from utils import debounce, html_escape, is_ios
+from utils import debounce, html_escape, is_ios, selection_extents
FORCE_FLOW_MODE = False
CALIBRE_VERSION = '__CALIBRE_VERSION__'
@@ -495,7 +495,7 @@ class IframeBoss:
if self.handle_navigation_shortcut(sc_name, evt):
evt.preventDefault()
elif sc_name is 'create_annotation':
- self.send_message('annotations', type='create-annotation')
+ self.initiate_creation_of_annotation()
else:
self.send_message('handle_shortcut', name=sc_name)
@@ -600,6 +600,14 @@ class IframeBoss:
else:
end_reference_mode()
+ def initiate_creation_of_annotation(self):
+ self.send_message(
+ 'annotations',
+ type='create-annotation',
+ in_flow_mode=current_layout_mode() is 'flow',
+ extents=selection_extents(),
+ )
+
def main():
main.boss = IframeBoss()
diff --git a/src/pyj/read_book/view.pyj b/src/pyj/read_book/view.pyj
index d234ba87f3..30cfd45abd 100644
--- a/src/pyj/read_book/view.pyj
+++ b/src/pyj/read_book/view.pyj
@@ -560,6 +560,7 @@ class View:
self.search_overlay.hide()
self.content_popup_overlay.hide()
self.reference_mode_overlay.style.display = 'none'
+ self.create_annotation.hide()
self.focus_iframe()
def focus_iframe(self):
diff --git a/src/pyj/utils.pyj b/src/pyj/utils.pyj
index 93e1747e10..f2af531594 100644
--- a/src/pyj/utils.pyj
+++ b/src/pyj/utils.pyj
@@ -252,6 +252,30 @@ def sandboxed_html(html, style, sandbox):
return ans
+def selection_extents():
+ ans = {'start': {'x': None, 'y': None, 'height': None}, 'end': {'x': None, 'y': None, 'height': None}}
+ sel = window.getSelection()
+ if not sel or not sel.rangeCount:
+ return ans
+ start = sel.getRangeAt(0).cloneRange()
+ end = sel.getRangeAt(sel.rangeCount - 1).cloneRange()
+ start.collapse(True)
+ end.collapse(False)
+
+ def for_boundary(r, ans):
+ rects = r.getClientRects()
+ if not rects.length:
+ return
+ rect = rects[0]
+ ans.x = rect.left
+ ans.y = rect.top
+ ans.height = rect.bottom - rect.top
+
+ for_boundary(start, ans.start)
+ for_boundary(end, ans.end)
+ return ans
+
+
if __name__ is '__main__':
from pythonize import strings
strings()