mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Allow dragging of selection handles
This commit is contained in:
parent
a0b6979fbf
commit
4f68fb39fc
@ -7,11 +7,36 @@ from gettext import gettext as _
|
|||||||
|
|
||||||
from book_list.globals import get_session_data
|
from book_list.globals import get_session_data
|
||||||
from book_list.theme import get_color
|
from book_list.theme import get_color
|
||||||
from dom import clear, svgicon
|
from dom import clear, svgicon, unique_id
|
||||||
from read_book.globals import runtime, ui_operations
|
from read_book.globals import runtime, ui_operations
|
||||||
from read_book.highlights import HighlightStyle
|
from read_book.highlights import HighlightStyle
|
||||||
|
|
||||||
ICON_SIZE = '3ex'
|
ICON_SIZE = '3ex'
|
||||||
|
DRAG_SCROLL_ZONE_MIN_HEIGHT = 10
|
||||||
|
|
||||||
|
# Utils {{{
|
||||||
|
|
||||||
|
def get_margins():
|
||||||
|
return {
|
||||||
|
'top': document.getElementById('book-top-margin').offsetHeight,
|
||||||
|
'left': document.getElementById('book-left-margin').offsetWidth,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def map_to_iframe_coords(point, margins):
|
||||||
|
point.x -= margins.left
|
||||||
|
point.y -= margins.top
|
||||||
|
return point
|
||||||
|
|
||||||
|
|
||||||
|
def near_element(elem, x, y):
|
||||||
|
r = elem.getBoundingClientRect()
|
||||||
|
extend_by = 15
|
||||||
|
left = r.left - extend_by
|
||||||
|
top = r.top - extend_by
|
||||||
|
right = r.right + extend_by
|
||||||
|
bottom = r.bottom + extend_by
|
||||||
|
return left <= x <= right and top <= y <= bottom
|
||||||
|
|
||||||
|
|
||||||
def position_bar_avoiding_handles(lh, rh, left, top, bar_width, bar_height, available_width, available_height, buffer):
|
def position_bar_avoiding_handles(lh, rh, left, top, bar_width, bar_height, available_width, available_height, buffer):
|
||||||
@ -142,40 +167,91 @@ def all_actions():
|
|||||||
return all_actions.ans
|
return all_actions.ans
|
||||||
|
|
||||||
|
|
||||||
def selection_handle(is_left, bg, fg):
|
def selection_handle(is_left):
|
||||||
ans = svgicon('selection-handle')
|
ans = svgicon('selection-handle')
|
||||||
use = ans.querySelector('use')
|
|
||||||
use.style.stroke = fg
|
|
||||||
use.style.fill = bg
|
|
||||||
s = ans.style
|
s = ans.style
|
||||||
if not is_left:
|
if not is_left:
|
||||||
s.transform = 'scaleX(-1)'
|
s.transform = 'scaleX(-1)'
|
||||||
s.position = 'absolute'
|
s.position = 'absolute'
|
||||||
s.boxSizing = 'border-box'
|
s.boxSizing = 'border-box'
|
||||||
s.touchAction = 'none'
|
s.touchAction = 'none'
|
||||||
s.pointerEvents = 'auto'
|
|
||||||
return ans
|
return ans
|
||||||
|
|
||||||
|
|
||||||
|
def set_handle_color(handle, bg, fg):
|
||||||
|
use = handle.querySelector('use')
|
||||||
|
use.style.stroke = fg
|
||||||
|
use.style.fill = bg
|
||||||
|
|
||||||
|
|
||||||
def elements_overlap(a, b):
|
def elements_overlap(a, b):
|
||||||
return a.left < b.right and b.left < a.right and a.top < b.bottom and b.top < a.bottom
|
return a.left < b.right and b.left < a.right and a.top < b.bottom and b.top < a.bottom
|
||||||
|
|
||||||
|
|
||||||
|
HIDDEN = 0
|
||||||
|
WAITING = 1
|
||||||
|
DRAGGING = 2
|
||||||
|
EDITING = 3
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
|
||||||
class SelectionBar:
|
class SelectionBar:
|
||||||
|
|
||||||
def __init__(self, view):
|
def __init__(self, view):
|
||||||
self.view = view
|
self.view = view
|
||||||
self.current_highlight_style = HighlightStyle(get_session_data().get('highlight_style'))
|
self.current_highlight_style = HighlightStyle(get_session_data().get('highlight_style'))
|
||||||
|
self.state = HIDDEN
|
||||||
|
self.left_handle_id = unique_id('handle')
|
||||||
|
self.right_handle_id = unique_id('handle')
|
||||||
|
self.bar_id = unique_id('bar')
|
||||||
|
self.editor_id = unique_id('editor')
|
||||||
|
container = self.container
|
||||||
|
container.style.overflow = 'hidden'
|
||||||
|
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('touchmove', self.touchmove_on_container, {'passive': False})
|
||||||
|
container.addEventListener('touchend', self.touchend_on_container, {'passive': False})
|
||||||
|
container.addEventListener('touchcancel', self.touchend_on_container, {'passive': False})
|
||||||
|
container.addEventListener('keydown', self.on_keydown, {'passive': False}) # TODO: Implement this
|
||||||
|
|
||||||
|
self.dragging_handle = None
|
||||||
|
self.position_in_handle = {'x': 0, 'y': 0}
|
||||||
|
self.active_touch = None
|
||||||
|
self.drag_scroll_timer = None
|
||||||
|
self.last_drag_scroll_at = -100000
|
||||||
|
self.left_line_height = self.right_line_height = 0
|
||||||
|
|
||||||
|
left_handle = selection_handle(True)
|
||||||
|
left_handle.id = self.left_handle_id
|
||||||
|
right_handle = selection_handle(False)
|
||||||
|
right_handle.id = self.right_handle_id
|
||||||
|
for h in (left_handle, right_handle):
|
||||||
|
h.addEventListener('mousedown', self.mousedown_on_handle, {'passive': False})
|
||||||
|
h.addEventListener('touchstart', self.touchstart_on_handle, {'passive': False})
|
||||||
|
container.appendChild(h)
|
||||||
|
container.appendChild(E.div(
|
||||||
|
id=self.bar_id,
|
||||||
|
style='position: absolute; border: solid 1px currentColor; border-radius: 5px;'
|
||||||
|
'left: 0; top: 0; display: flex; flex-direction: column;'
|
||||||
|
))
|
||||||
|
container.appendChild(E.div(id=self.editor_id))
|
||||||
|
|
||||||
|
# bar and handles markup {{{
|
||||||
|
|
||||||
|
def set_handle_colors(self):
|
||||||
|
handle_fill = get_color('window-background')
|
||||||
|
fg = self.view.current_color_scheme.foreground
|
||||||
|
for h in (self.left_handle, self.right_handle):
|
||||||
|
set_handle_color(h, handle_fill, fg)
|
||||||
|
|
||||||
def build_bar(self, annot_id):
|
def build_bar(self, annot_id):
|
||||||
notes = self.view.annotations_manager.notes_for_highlight(annot_id)
|
notes = self.view.annotations_manager.notes_for_highlight(annot_id)
|
||||||
c = self.container
|
bar_container = self.bar
|
||||||
max_width = 'min(50rem, 90vw)' if self.supports_css_min_max else '50rem'
|
clear(bar_container)
|
||||||
bar_container = E.div(
|
bar_container.style.maxWidth = 'min(50rem, 90vw)' if self.supports_css_min_max else '50rem'
|
||||||
style='position: absolute; border: solid 1px currentColor; border-radius: 5px;'
|
bar_container.style.backgroundColor = get_color("window-background")
|
||||||
'left: 0; top: 0; pointer-events: auto; display: flex; flex-direction: column;'
|
for x in [
|
||||||
'background-color: {}; max-width: {}'.format(get_color("window-background"), max_width),
|
|
||||||
|
|
||||||
E.div(style='height: 4ex; display: flex; align-items: center; padding: 5px; justify-content: center'),
|
E.div(style='height: 4ex; display: flex; align-items: center; padding: 5px; justify-content: center'),
|
||||||
|
|
||||||
E.hr(style='border-top: solid 1px; margin: 0; padding: 0; display: none'),
|
E.hr(style='border-top: solid 1px; margin: 0; padding: 0; display: none'),
|
||||||
@ -183,20 +259,16 @@ class SelectionBar:
|
|||||||
E.div(
|
E.div(
|
||||||
style='display: none; padding: 5px;',
|
style='display: none; padding: 5px;',
|
||||||
E.div(),
|
E.div(),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
]:
|
||||||
|
bar_container.appendChild(x)
|
||||||
bar = bar_container.firstChild
|
bar = bar_container.firstChild
|
||||||
handle_fill = get_color('window-background')
|
|
||||||
left_handle = selection_handle(True, handle_fill, self.view.current_color_scheme.foreground)
|
|
||||||
right_handle = selection_handle(False, handle_fill, self.view.current_color_scheme.foreground)
|
|
||||||
c.appendChild(left_handle)
|
|
||||||
c.appendChild(right_handle)
|
|
||||||
c.appendChild(bar_container)
|
|
||||||
hs = self.current_highlight_style.highlight_shade
|
hs = self.current_highlight_style.highlight_shade
|
||||||
|
|
||||||
def cb(ac, callback):
|
def cb(ac, callback):
|
||||||
ans = ac.icon_function(hs)
|
ans = ac.icon_function(hs)
|
||||||
ans.addEventListener('click', def(ev):
|
ans.addEventListener('click', def(ev):
|
||||||
|
ev.stopPropagation(), ev.preventDefault()
|
||||||
callback(ev)
|
callback(ev)
|
||||||
self.view.focus_iframe()
|
self.view.focus_iframe()
|
||||||
)
|
)
|
||||||
@ -211,70 +283,7 @@ class SelectionBar:
|
|||||||
if ac and (not ac.needs_highlight or v'!!annot_id'):
|
if ac and (not ac.needs_highlight or v'!!annot_id'):
|
||||||
bar.appendChild(cb(ac, self[ac.function_name]))
|
bar.appendChild(cb(ac, self[ac.function_name]))
|
||||||
self.show_notes(bar_container, notes)
|
self.show_notes(bar_container, notes)
|
||||||
return bar_container, left_handle, right_handle
|
return bar_container
|
||||||
|
|
||||||
@property
|
|
||||||
def supports_css_min_max(self):
|
|
||||||
return not runtime.is_standalone_viewer or runtime.QT_VERSION >= 0x050f00
|
|
||||||
|
|
||||||
@property
|
|
||||||
def container(self):
|
|
||||||
return document.getElementById('book-selection-bar-overlay')
|
|
||||||
|
|
||||||
@property
|
|
||||||
def bar(self):
|
|
||||||
return self.container.firstChild
|
|
||||||
|
|
||||||
def hide(self):
|
|
||||||
self.container.style.display = 'none'
|
|
||||||
|
|
||||||
def show(self):
|
|
||||||
sd = get_session_data()
|
|
||||||
if not self.view.create_annotation.is_visible and sd.get('show_selection_bar'):
|
|
||||||
self.container.style.display = 'block'
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_visible(self):
|
|
||||||
return self.container.style.display is not 'none'
|
|
||||||
|
|
||||||
def copy_to_clipboard(self):
|
|
||||||
if self.view.currently_showing.selection.text:
|
|
||||||
ui_operations.copy_selection(self.view.currently_showing.selection.text)
|
|
||||||
|
|
||||||
def lookup(self):
|
|
||||||
if ui_operations.toggle_lookup:
|
|
||||||
ui_operations.toggle_lookup(True)
|
|
||||||
else:
|
|
||||||
self.view.overlay.show_word_actions(self.view.currently_showing.selection.text)
|
|
||||||
|
|
||||||
def internet_search(self):
|
|
||||||
text = self.view.currently_showing.selection.text
|
|
||||||
if text:
|
|
||||||
q = encodeURIComponent(text)
|
|
||||||
url = get_session_data().get('net_search_url').format(q=q)
|
|
||||||
ui_operations.open_url(url)
|
|
||||||
|
|
||||||
def clear_selection(self):
|
|
||||||
self.view.on_handle_shortcut({'name': 'clear_selection'})
|
|
||||||
|
|
||||||
def create_highlight(self):
|
|
||||||
self.view.initiate_create_annotation(True)
|
|
||||||
|
|
||||||
def adjust_selection(self):
|
|
||||||
self.view.initiate_create_annotation(False)
|
|
||||||
|
|
||||||
def quick_highlight(self):
|
|
||||||
cs = self.view.currently_showing.selection
|
|
||||||
if cs.text:
|
|
||||||
if cs.annot_id:
|
|
||||||
self.view.initiate_create_annotation(True)
|
|
||||||
else:
|
|
||||||
self.view.create_annotation.quick_create()
|
|
||||||
|
|
||||||
def remove_highlight(self):
|
|
||||||
annot_id = self.view.currently_showing.selection.annot_id
|
|
||||||
if annot_id:
|
|
||||||
self.view.create_annotation.remove_highlight(annot_id)
|
|
||||||
|
|
||||||
def show_notes(self, bar, notes):
|
def show_notes(self, bar, notes):
|
||||||
notes = (notes or "").strip()
|
notes = (notes or "").strip()
|
||||||
@ -305,11 +314,193 @@ class SelectionBar:
|
|||||||
current_block += line + '\n'
|
current_block += line + '\n'
|
||||||
if current_block:
|
if current_block:
|
||||||
add_para()
|
add_para()
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# accessors {{{
|
||||||
|
@property
|
||||||
|
def supports_css_min_max(self):
|
||||||
|
return not runtime.is_standalone_viewer or runtime.QT_VERSION >= 0x050f00
|
||||||
|
|
||||||
|
@property
|
||||||
|
def container(self):
|
||||||
|
return document.getElementById('book-selection-bar-overlay')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bar(self):
|
||||||
|
return document.getElementById(self.bar_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 editor(self):
|
||||||
|
return document.getElementById(self.editor_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def current_handle_position(self):
|
||||||
|
lh, rh = self.left_handle, self.right_handle
|
||||||
|
lbr, rbr = lh.getBoundingClientRect(), rh.getBoundingClientRect()
|
||||||
|
return {
|
||||||
|
'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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# event handlers {{{
|
||||||
|
def mousedown_on_handle(self, ev):
|
||||||
|
ev.stopPropagation(), ev.preventDefault()
|
||||||
|
if self.state is WAITING:
|
||||||
|
self.start_handle_drag(ev, ev.currentTarget)
|
||||||
|
|
||||||
|
def touchstart_on_handle(self, ev):
|
||||||
|
ev.stopPropagation(), ev.preventDefault()
|
||||||
|
if self.state is WAITING:
|
||||||
|
for touch in ev.changedTouches:
|
||||||
|
self.active_touch = touch.identifier
|
||||||
|
self.start_handle_drag(touch, ev.currentTarget)
|
||||||
|
break
|
||||||
|
|
||||||
|
def start_handle_drag(self, ev, handle):
|
||||||
|
self.state = DRAGGING
|
||||||
|
self.dragging_handle = handle.id
|
||||||
|
r = handle.getBoundingClientRect()
|
||||||
|
self.position_in_handle.x = Math.round(ev.clientX - r.left)
|
||||||
|
self.position_in_handle.y = Math.round(ev.clientY - r.top)
|
||||||
|
|
||||||
|
def container_clicked(self, ev):
|
||||||
|
ev.stopPropagation(), ev.preventDefault()
|
||||||
|
if self.state is EDITING:
|
||||||
|
return # TODO: accept edit and apply highlight
|
||||||
|
if self.state is WAITING:
|
||||||
|
for x in (self.bar, self.left_handle, self.right_handle):
|
||||||
|
if near_element(x, ev.clientX, ev.clientY):
|
||||||
|
return
|
||||||
|
self.clear_selection()
|
||||||
|
|
||||||
|
def mousemove_on_container(self, ev):
|
||||||
|
if self.state is not DRAGGING:
|
||||||
|
return
|
||||||
|
ev.stopPropagation(), ev.preventDefault()
|
||||||
|
self.handle_moved(ev)
|
||||||
|
|
||||||
|
def touchmove_on_container(self, ev):
|
||||||
|
if self.state is not DRAGGING:
|
||||||
|
return
|
||||||
|
ev.stopPropagation(), ev.preventDefault()
|
||||||
|
for touch in ev.changedTouches:
|
||||||
|
if touch.identifier is self.active_touch:
|
||||||
|
self.handle_moved(touch)
|
||||||
|
return
|
||||||
|
|
||||||
|
def handle_moved(self, ev):
|
||||||
|
handle = document.getElementById(self.dragging_handle)
|
||||||
|
s = handle.style
|
||||||
|
s.left = (ev.clientX - self.position_in_handle.x) + 'px'
|
||||||
|
s.top = (ev.clientY - self.position_in_handle.y) + 'px'
|
||||||
|
margins = get_margins()
|
||||||
|
pos = self.current_handle_position
|
||||||
|
pos.start = map_to_iframe_coords(pos.start, margins)
|
||||||
|
pos.end = map_to_iframe_coords(pos.end, margins)
|
||||||
|
self.send_message('set-selection', extents=pos)
|
||||||
|
c = self.container
|
||||||
|
rect = c.getBoundingClientRect()
|
||||||
|
t = document.getElementById('book-top-margin').offsetHeight
|
||||||
|
top = rect.top + max(t, DRAG_SCROLL_ZONE_MIN_HEIGHT)
|
||||||
|
t = document.getElementById('book-bottom-margin').offsetHeight
|
||||||
|
bottom = rect.bottom - max(t, DRAG_SCROLL_ZONE_MIN_HEIGHT)
|
||||||
|
if ev.clientY < top or ev.clientY > bottom:
|
||||||
|
self.run_drag_scroll(ev.clientY, top, bottom)
|
||||||
|
else:
|
||||||
|
self.end_drag_scroll()
|
||||||
|
|
||||||
|
def end_handle_drag(self):
|
||||||
|
self.end_drag_scroll()
|
||||||
|
self.dragging_handle = None
|
||||||
|
self.state = WAITING
|
||||||
|
self.update_position()
|
||||||
|
|
||||||
|
def mouseup_on_container(self, ev):
|
||||||
|
if self.state is DRAGGING:
|
||||||
|
ev.preventDefault(), ev.stopPropagation()
|
||||||
|
self.end_handle_drag()
|
||||||
|
|
||||||
|
def touchend_on_container(self, ev):
|
||||||
|
if self.state is DRAGGING:
|
||||||
|
ev.preventDefault(), ev.stopPropagation()
|
||||||
|
for touch in ev.changedTouches:
|
||||||
|
if touch.identifier is self.active_touch:
|
||||||
|
self.active_touch = None
|
||||||
|
self.end_handle_drag()
|
||||||
|
return
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# drag scroll {{{
|
||||||
|
def run_drag_scroll(self, mouse_y, top, bottom):
|
||||||
|
backwards = mouse_y <= top
|
||||||
|
self.do_one_drag_scroll(backwards, top - mouse_y if backwards else mouse_y - bottom)
|
||||||
|
|
||||||
|
def do_one_drag_scroll(self, backwards, distance_from_boundary):
|
||||||
|
window.clearTimeout(self.drag_scroll_timer)
|
||||||
|
self.drag_scroll_timer = None
|
||||||
|
if self.state is not DRAGGING:
|
||||||
|
return
|
||||||
|
sd = get_session_data()
|
||||||
|
interval = 1000/sd.get('lines_per_sec_smooth') if self.in_flow_mode else 1200
|
||||||
|
self.drag_scroll_timer = window.setTimeout(self.do_one_drag_scroll.bind(None, backwards, distance_from_boundary), interval)
|
||||||
|
now = window.performance.now()
|
||||||
|
if now - self.last_drag_scroll_at > interval:
|
||||||
|
self.send_drag_scroll_message(backwards, 'left' if self.dragging_handle is self.left_handle_id else 'right', True)
|
||||||
|
self.last_drag_scroll_at = now
|
||||||
|
|
||||||
|
def send_drag_scroll_message(self, backwards, handle, extend_selection):
|
||||||
|
self.send_message(
|
||||||
|
'drag-scroll', backwards=backwards, handle=handle, extents=self.current_handle_position,
|
||||||
|
extend_selection=extend_selection)
|
||||||
|
|
||||||
|
def end_drag_scroll(self):
|
||||||
|
if self.drag_scroll_timer is not None:
|
||||||
|
window.clearTimeout(self.drag_scroll_timer)
|
||||||
|
self.drag_scroll_timer = None
|
||||||
|
self.last_drag_scroll_at = -10000
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# show and hide {{{
|
||||||
|
def hide(self):
|
||||||
|
self.state = HIDDEN
|
||||||
|
self.container.style.display = 'none'
|
||||||
|
|
||||||
|
def show(self):
|
||||||
|
sd = get_session_data()
|
||||||
|
if self.state is HIDDEN:
|
||||||
|
if sd.get('show_selection_bar'):
|
||||||
|
self.container.style.display = 'block'
|
||||||
|
self.state = WAITING
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_visible(self):
|
||||||
|
return self.container.style.display is not 'none'
|
||||||
|
|
||||||
def update_position(self):
|
def update_position(self):
|
||||||
container = self.container
|
container = self.container
|
||||||
clear(container)
|
self.bar.style.display = 'none'
|
||||||
container.style.overflow = 'hidden'
|
self.set_handle_colors()
|
||||||
|
if self.state is DRAGGING:
|
||||||
|
return
|
||||||
|
self.left_handle.style.display = 'none'
|
||||||
|
self.right_handle.style.display = 'none'
|
||||||
|
|
||||||
cs = self.view.currently_showing.selection
|
cs = self.view.currently_showing.selection
|
||||||
if not cs or cs.empty or jstype(cs.drag_mouse_position.x) is 'number':
|
if not cs or cs.empty or jstype(cs.drag_mouse_position.x) is 'number':
|
||||||
@ -318,20 +509,18 @@ class SelectionBar:
|
|||||||
if not cs.start.onscreen and not cs.end.onscreen:
|
if not cs.start.onscreen and not cs.end.onscreen:
|
||||||
return self.hide()
|
return self.hide()
|
||||||
|
|
||||||
margins = {
|
margins = get_margins()
|
||||||
'top': document.getElementById('book-top-margin').offsetHeight,
|
|
||||||
'bottom': document.getElementById('book-bottom-margin').offsetHeight,
|
|
||||||
'left': document.getElementById('book-left-margin').offsetWidth,
|
|
||||||
'right': document.getElementById('book-right-margin').offsetWidth,
|
|
||||||
}
|
|
||||||
|
|
||||||
def map_boundary(x):
|
def map_boundary(x):
|
||||||
return {'x': (x.x or 0) + margins.left, 'y': (x.y or 0) + margins.top, 'height': x.height or 0, 'onscreen': x.onscreen}
|
return {'x': (x.x or 0) + margins.left, 'y': (x.y or 0) + margins.top, 'height': x.height or 0, 'onscreen': x.onscreen}
|
||||||
|
|
||||||
bar, left_handle, right_handle = self.build_bar(cs.annot_id)
|
self.show()
|
||||||
|
if self.state is EDITING:
|
||||||
|
return self.show_editor() # TODO: Implement this
|
||||||
|
self.bar.style.display = self.left_handle.style.display = self.right_handle.style.display = 'block'
|
||||||
start = map_boundary(cs.start)
|
start = map_boundary(cs.start)
|
||||||
end = map_boundary(cs.end)
|
end = map_boundary(cs.end)
|
||||||
self.show()
|
bar = self.build_bar(cs.annot_id)
|
||||||
end_after_start = start.y < end.y or (start.y is end.y and start.x < end.x)
|
end_after_start = start.y < end.y or (start.y is end.y and start.x < end.x)
|
||||||
bar_height = bar.offsetHeight
|
bar_height = bar.offsetHeight
|
||||||
bar_width = bar.offsetWidth
|
bar_width = bar.offsetWidth
|
||||||
@ -341,6 +530,7 @@ class SelectionBar:
|
|||||||
# - 10 ensures we dont cover scroll bar
|
# - 10 ensures we dont cover scroll bar
|
||||||
'left': buffer, 'right': container.offsetWidth - bar_width - buffer - 10
|
'left': buffer, 'right': container.offsetWidth - bar_width - buffer - 10
|
||||||
}
|
}
|
||||||
|
left_handle, right_handle = self.left_handle, self.right_handle
|
||||||
self.position_handles(left_handle, right_handle, start, end, end_after_start)
|
self.position_handles(left_handle, right_handle, start, end, end_after_start)
|
||||||
|
|
||||||
def place_vertically(pos, put_below):
|
def place_vertically(pos, put_below):
|
||||||
@ -392,10 +582,63 @@ class SelectionBar:
|
|||||||
s.top = f'{top}px'
|
s.top = f'{top}px'
|
||||||
if is_left:
|
if is_left:
|
||||||
s.left = (boundary.x - width) + 'px'
|
s.left = (boundary.x - width) + 'px'
|
||||||
|
self.left_line_height = boundary.height
|
||||||
else:
|
else:
|
||||||
s.left = boundary.x + 'px'
|
s.left = boundary.x + 'px'
|
||||||
|
self.right_line_height = boundary.height
|
||||||
|
|
||||||
if not end_after_start:
|
if not end_after_start:
|
||||||
start, end = end, start
|
start, end = end, start
|
||||||
place_single_handle(left_handle, start, True)
|
place_single_handle(left_handle, start, True)
|
||||||
place_single_handle(right_handle, end, False)
|
place_single_handle(right_handle, end, False)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Actions {{{
|
||||||
|
def copy_to_clipboard(self):
|
||||||
|
if self.view.currently_showing.selection.text:
|
||||||
|
ui_operations.copy_selection(self.view.currently_showing.selection.text)
|
||||||
|
|
||||||
|
def lookup(self):
|
||||||
|
if ui_operations.toggle_lookup:
|
||||||
|
ui_operations.toggle_lookup(True)
|
||||||
|
else:
|
||||||
|
self.view.overlay.show_word_actions(self.view.currently_showing.selection.text)
|
||||||
|
|
||||||
|
def internet_search(self):
|
||||||
|
text = self.view.currently_showing.selection.text
|
||||||
|
if text:
|
||||||
|
q = encodeURIComponent(text)
|
||||||
|
url = get_session_data().get('net_search_url').format(q=q)
|
||||||
|
ui_operations.open_url(url)
|
||||||
|
|
||||||
|
def clear_selection(self):
|
||||||
|
self.view.on_handle_shortcut({'name': 'clear_selection'})
|
||||||
|
self.hide()
|
||||||
|
|
||||||
|
def create_highlight(self):
|
||||||
|
self.view.initiate_create_annotation(True)
|
||||||
|
|
||||||
|
def adjust_selection(self):
|
||||||
|
self.view.initiate_create_annotation(False)
|
||||||
|
|
||||||
|
def quick_highlight(self):
|
||||||
|
cs = self.view.currently_showing.selection
|
||||||
|
if cs.text:
|
||||||
|
if cs.annot_id:
|
||||||
|
self.view.initiate_create_annotation(True)
|
||||||
|
else:
|
||||||
|
self.view.create_annotation.quick_create()
|
||||||
|
|
||||||
|
def remove_highlight(self):
|
||||||
|
annot_id = self.view.currently_showing.selection.annot_id
|
||||||
|
if annot_id:
|
||||||
|
self.view.create_annotation.remove_highlight(annot_id)
|
||||||
|
# }}}
|
||||||
|
|
||||||
|
# Interact with iframe {{{
|
||||||
|
|
||||||
|
def send_message(self, type, **kw):
|
||||||
|
self.view.iframe_wrapper.send_message('annotations', type=type, **kw)
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
@ -237,7 +237,7 @@ class View:
|
|||||||
),
|
),
|
||||||
right_margin,
|
right_margin,
|
||||||
self.book_scrollbar.create(),
|
self.book_scrollbar.create(),
|
||||||
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none; pointer-events: none', id='book-selection-bar-overlay'), # selection bar overlay
|
E.div(style='position: absolute; top:0; left:0; width: 100%; height: 100%; display:none;', id='book-selection-bar-overlay'), # selection bar overlay
|
||||||
E.div(style='position: absolute; top:0; left:0; width: 100%; pointer-events:none; display:none', id='book-search-overlay'), # search overlay
|
E.div(style='position: absolute; top:0; left:0; width: 100%; pointer-events:none; display:none', id='book-search-overlay'), # search overlay
|
||||||
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%; 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%; overflow: auto; display:none', id='book-overlay'), # main overlay
|
||||||
|
Loading…
x
Reference in New Issue
Block a user