calibre/src/pyj/widgets.pyj

245 lines
9.5 KiB
Plaintext

# vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2015, Kovid Goyal <kovid at kovidgoyal.net>
from __python__ import hash_literals
from dom import build_rule, clear, svgicon, create_keyframes, set_css, change_icon_image, add_extra_css, ensure_id
from elementmaker import E
from book_list.theme import get_color
# Button {{{
def create_button(text, icon=None, action=None, tooltip=None, highlight=False, download_filename=None, class_=''):
ic = ''
if icon:
ic = svgicon(icon)
text = '\xa0' + text
ans = E.a(ic, E.span(text), class_='calibre-push-button ' + class_, href='javascript: void(0)', role='button', title=tooltip or '')
if download_filename and v'"download" in ans':
ans.setAttribute('download', download_filename)
if action is not None:
if jstype(action) is 'string':
ans.setAttribute('href', action)
else:
ans.addEventListener('click', def(event): event.preventDefault(), action(event);)
if highlight:
set_css(ans, font_size='larger', font_weight='bold')
return ans
create_button.style = build_rule('a.calibre-push-button',
border_radius='1em', background_clip='padding-box', background_color=get_color('button-start'),
background_image='linear-gradient(to bottom, {}, {})'.format(get_color('button-start'), get_color('button-end')),
padding='0.5ex 1em', color=get_color('button-text'), cursor='pointer', font_size='inherit', display='inline-flex',
align_items='center', user_select='none',
box_shadow='0px 2px 1px rgba(50, 50, 50, 0.75)', white_space='nowrap'
)
create_button.style += build_rule('a.calibre-push-button:hover', transform='scale(1.05)')
create_button.style += build_rule('a.calibre-push-button:active', transform='scale(1.1)')
# This is needed because of a bug in Firefox: https://bugs.launchpad.net/bugs/1881385
create_button.style += build_rule('a.calibre-push-button:visited', color=get_color('button-text'))
# }}}
# Spinner {{{
def create_spinner(height, width):
ans = svgicon('cog', height, width)
ans.classList.add('spin')
return ans
create_spinner.style = build_rule('.spin', animation='spin 2s infinite linear')
create_spinner.style += create_keyframes('spin', 'from { transform: rotate(0deg); } to { transform: rotate(359deg);}')
# }}}
# Breadcrumbs {{{
class Breadcrumbs:
STYLE_RULES = build_rule(
'.calibre-breadcrumbs',
user_select='none', white_space='nowrap', background_color=get_color('window-background2'),
z_index='-1', border_radius='10px', margin='1ex 1em', margin_bottom='0'
)
STYLE_RULES += build_rule(
'.calibre-breadcrumbs > li',
cursor='pointer', display='inline-block', line_height='26px', margin='0 9px 0 -10px',
padding='0.5ex 1rem', position='relative'
)
STYLE_RULES += build_rule('.calibre-breadcrumbs > li:hover', color=get_color('window-hover-foreground'))
STYLE_RULES += build_rule('.calibre-breadcrumbs > li:active', color=get_color('window-hover-foreground'), transform='scale(1.5)')
STYLE_RULES += build_rule(
'.calibre-breadcrumbs > li:before, .calibre-breadcrumbs > li:after',
border_right='2px solid currentColor', content='""', display='block', height='50%',
position='absolute', left='0', top='0', right='0', transform='skewX(45deg)'
)
STYLE_RULES += build_rule(
'.calibre-breadcrumbs > li:after',
bottom='0', top='auto', transform='skewX(-45deg)'
)
STYLE_RULES += build_rule(
'.calibre-breadcrumbs > li:last-of-type:before, .calibre-breadcrumbs > li:last-of-type:after',
display='none')
def __init__(self, container):
self.container_id = ensure_id(container, 'calibre-breadcrumbs-')
container.classList.add('calibre-breadcrumbs')
clear(container)
@property
def container(self):
return document.getElementById(self.container_id)
def reset(self):
clear(self.container)
def add_crumb(self, callback):
li = E.li()
if callback:
li.addEventListener('click', callback)
self.container.appendChild(li)
return li
# }}}
# Simple Tree {{{
def create_tree(root, populate_data, onclick):
container = E.div(class_='simple-tree')
set_css(container, overflow='auto')
def toggle_node(li):
if li.dataset.treeState is 'closed':
li.dataset.treeState = 'open'
li.lastChild.style.display = 'block'
change_icon_image(li.firstChild.firstChild, 'caret-down')
else:
li.dataset.treeState = 'closed'
li.lastChild.style.display = 'none'
change_icon_image(li.firstChild.firstChild, 'caret-right')
def bullet(icon):
ans = svgicon(icon, '2ex', '2ex')
ans.style.minWidth = '2ex'
ans.style.minHeight = '2ex'
return ans
def process_node(node, parent_container, level):
if node.children?.length:
ul = E.div()
parent_container.appendChild(ul)
for child in node.children:
icon = 'caret-right' if child.children?.length else None
li = E.div(style='display:flex; flex-direction:column; margin: 1ex 1em; margin-left: {}em'.format(level+1),
E.div(style='display:flex; align-items: center',
bullet(icon),
E.span('\xa0'),
E.a(
href='javascript: void(0)',
class_='simple-link tree-item-title',
onclick=def (event):
if onclick:
if event.button is 0:
event.preventDefault(), event.stopPropagation()
onclick(event, event.currentTarget.parentNode.parentNode)
),
),
E.div(style='display:none', data_tree_subtree_container='1'),
data_tree_state='closed',
)
ul.appendChild(li)
populate_data(child, li, li.firstChild.lastChild)
if icon:
set_css(li.firstChild.firstChild, cursor='pointer')
li.firstChild.firstChild.addEventListener('click', def(event):
toggle_node(event.currentTarget.parentNode.parentNode)
)
process_node(child, li.lastChild, level + 1)
if root:
process_node(root, container, 0)
return container
def find_text_in_tree(container, q):
q = q.lower()
last_match = container.querySelector('a[data-tree-last-match]')
if last_match:
last_match.parentNode.style.backgroundColor = 'transparent'
last_match.parentNode.style.borderRadius = '0'
lm = last_match.getAttribute('data-tree-last-match')
last_match.removeAttribute('data-tree-last-match')
if lm is not q:
last_match = None
if not q:
return
before = []
seen = False
ans = None
for a in container.querySelectorAll('a.tree-item-title'):
if a is last_match:
seen = True
else:
if a.textContent.lower().indexOf(q) != -1:
if seen:
ans = a
break
if last_match is None:
ans = a
break
before.push(a)
if not ans and before.length:
ans = before[0]
ans = ans or last_match
if ans:
ans.dataset.treeLastMatch = q
if ans:
ans.parentNode.style.backgroundColor = get_color('tree-highlight-item')
ans.parentNode.style.borderRadius = '5px'
ans = ans.parentNode.parentNode
return ans
def scroll_tree_item_into_view(item):
p = item.parentNode?.parentNode
while p and p.getAttribute('data-tree-subtree-container'):
p.style.display = 'block'
p = p.parentNode?.parentNode?.parentNode
item.scrollIntoView()
# }}}
add_extra_css(def():
ans = 'a, button:focus { outline: none }; a, button::-moz-focus-inner { border: 0 }\n'
ans += '.simple-link { cursor: pointer } .simple-link:hover { color: HC } .simple-link:active { transform: scale(1.5) }\n'.replace('HC', get_color('window-hover-foreground'))
ans += '.blue-link { cursor: pointer; color: COL } .blue-link:visited { color: COL} .blue-link:hover { color: HC } .blue-link:active { transform: scale(1.5) }\n'.replace('HC', get_color('window-hover-foreground')).replace(/COL/g, get_color('link-foreground'))
ans += create_button.style + '\n'
ans += create_button.style + '\n'
ans += create_spinner.style + '\n'
ans += Breadcrumbs.STYLE_RULES + '\n'
# Chrome hardcodes the size of these to 13.3px. In safari on some devices
# the font size is smaller than the body font size and small enough to
# trigger page zooms. So set it to 1em so they scale with body font size.
ans += '''
select, textarea, input[type="text"], input[type="password"],
input[type="datetime"], input[type="datetime-local"],
input[type="date"], input[type="month"], input[type="time"],
input[type="week"], input[type="number"], input[type="email"],
input[type="url"], input[type="search"] { font-size: 1em }
'''
return ans
)
def enable_escape_key(container, action):
container.setAttribute('tabindex', '0')
container.addEventListener('keydown', def(ev):
if ev.key is 'Escape':
ev.stopPropagation(), ev.preventDefault()
action()
, {'passive': False})
container.focus()