diff --git a/src/pyj/read_book/create_annotation.pyj b/src/pyj/read_book/create_annotation.pyj index ed90855d57..7636166174 100644 --- a/src/pyj/read_book/create_annotation.pyj +++ b/src/pyj/read_book/create_annotation.pyj @@ -4,10 +4,10 @@ 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 +WAITING_FOR_CLICK = 1 +WAITING_FOR_DRAG = 2 +DRAGGING_LEFT = 3 +DRAGGING_RIGHT = 4 def selection_handle(invert): @@ -17,6 +17,7 @@ def selection_handle(invert): s.transform = 'scaleX(-1)' s.position = 'absolute' s.boxSizing = 'border-box' + s.touchAction = 'none' return ans @@ -28,6 +29,14 @@ def map_from_iframe_coords(point): return point +def map_to_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: container_id = 'create-annotation-overlay' @@ -44,6 +53,14 @@ class CreateAnnotation: self.right_handle_id = ensure_id(rh, 'handle') container.appendChild(rh) + container.addEventListener('click', self.container_clicked) + + def container_clicked(self, ev): + ev.stopPropagation(), ev.preventDefault() + if self.state is WAITING_FOR_CLICK: + pt = map_to_iframe_coords({'x': ev.clientX, 'y': ev.clientY}) + self.send_message(type='position-handles-at-point', x=pt.x, y=pt.y) + @property def container(self): return document.getElementById(self.container_id) @@ -78,6 +95,9 @@ class CreateAnnotation: self.hide_handles() if msg.extents.start.x is not None: self.place_handles(msg.extents) + elif msg.type is 'position-handles': + if self.state is WAITING_FOR_CLICK: + self.place_handles(msg.extents) else: print('Ignoring annotations message with unknown type:', msg.type) diff --git a/src/pyj/read_book/iframe.pyj b/src/pyj/read_book/iframe.pyj index 66ff20b341..3541318d9e 100644 --- a/src/pyj/read_book/iframe.pyj +++ b/src/pyj/read_book/iframe.pyj @@ -4,6 +4,7 @@ from __python__ import bound_methods, hash_literals import traceback from gettext import gettext as _ +from select import selection_extents, selection_extents_at_point from fs_images import fix_fullscreen_svg_images from iframe_comm import IframeClient @@ -50,7 +51,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, selection_extents +from utils import debounce, html_escape, is_ios FORCE_FLOW_MODE = False CALIBRE_VERSION = '__CALIBRE_VERSION__' @@ -120,6 +121,7 @@ class IframeBoss: 'overlay_visibility_changed': self.on_overlay_visibility_changed, 'show_search_result': self.show_search_result, 'handle_navigation_shortcut': self.on_handle_navigation_shortcut, + 'annotations': self.annotations_msg_received, } self.comm = IframeClient(handlers) self.last_window_ypos = 0 @@ -608,6 +610,15 @@ class IframeBoss: extents=selection_extents(), ) + def annotations_msg_received(self, data): + if data.type is 'position-handles-at-point': + self.send_message( + 'annotations', + type='position-handles', + extents=selection_extents_at_point(data.x, data.y)) + else: + console.log('Ignoring annotations message to iframe with unknown type: ' + data.type) + def main(): main.boss = IframeBoss() diff --git a/src/pyj/select.pyj b/src/pyj/select.pyj index 02713ec71d..6e11b98c67 100644 --- a/src/pyj/select.pyj +++ b/src/pyj/select.pyj @@ -42,3 +42,51 @@ def word_at_point(x, y): r.setStart(r.startContainer, word_info.start) r.setEnd(r.startContainer, word_info.end) return r + + +def range_extents(start, end): + ans = {'start': {'x': None, 'y': None, 'height': None}, 'end': {'x': None, 'y': None, 'height': None}} + if not start or not end: + return ans + start = start.cloneRange() + end = end.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 + + + +def selection_extents(): + 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) + + +def selection_extents_at_point(x, y): + r = word_at_point(x, y) + if r: + sel = window.getSelection() + sel.removeAllRanges() + sel.addRange(r) + return selection_extents(r, r) + ans = range_extents() + ans.start.y = ans.end.y = 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 + return ans diff --git a/src/pyj/utils.pyj b/src/pyj/utils.pyj index f2af531594..93e1747e10 100644 --- a/src/pyj/utils.pyj +++ b/src/pyj/utils.pyj @@ -252,30 +252,6 @@ 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()