1# vim:fileencoding=utf-8 2# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> 3from __python__ import bound_methods, hash_literals 4 5from elementmaker import E 6from gettext import gettext as _ 7 8from book_list.globals import get_session_data 9from complete import create_search_bar 10from book_list.item_list import create_item, create_item_list 11from dom import clear, svgicon, unique_id 12from read_book.shortcuts import ( 13 key_as_text, keyevent_as_shortcut, shortcut_differs, shortcuts_definition, 14 shortcuts_group_desc 15) 16from widgets import create_button 17 18 19def get_container(): 20 return document.getElementById(get_container.id) 21 22 23def restore_defaults(close_func): 24 get_container().dataset.changed = 'true' 25 for item in get_container().querySelectorAll('[data-user-data]'): 26 q = JSON.parse(item.dataset.userData) 27 q.shortcuts = shortcuts_definition()[q.name].shortcuts 28 item.dataset.userData = JSON.stringify(q) 29 close_func() 30 31 32def as_groups(shortcuts): 33 ans = {} 34 for sc_name in Object.keys(shortcuts): 35 sc = shortcuts[sc_name] 36 if not ans[sc.group]: 37 ans[sc.group] = {} 38 ans[sc.group][sc_name] = sc 39 return ans 40 41 42def sort_group_key(group, sc_name): 43 return group[sc_name].short.toLowerCase() 44 45 46def sc_as_item(sc_name, sc, shortcuts): 47 cuts = shortcuts or sc.shortcuts 48 return create_item(sc.short, action=customize_shortcut.bind(None, sc_name), subtitle=sc.long, data=JSON.stringify({'name': sc_name, 'shortcuts': cuts})) 49 50 51 52def remove_key(evt): 53 key_container = evt.currentTarget.parentNode.parentNode 54 key_container.parentNode.removeChild(key_container) 55 56 57def key_widget(key): 58 return E.tr( 59 data_shortcut=JSON.stringify(keyevent_as_shortcut(key)), 60 E.td(style="padding: 0.5rem; border-right: solid 3px; margin-right: 0.5rem; margin-top: 0.5rem", key_as_text(key)), 61 E.td(E.a(class_='simple-link', '\xa0', svgicon('remove'), ' ', _('Remove'), onclick=remove_key)), 62 ) 63 64 65def close_customize_shortcut(apply_changes): 66 container = get_container() 67 container.firstChild.style.display = 'flex' 68 container.firstChild.nextSibling.style.display = 'block' 69 container.lastChild.style.display = 'none' 70 if close_customize_shortcut.shortcut_being_customized: 71 for item in container.firstChild.querySelectorAll('[data-user-data]'): 72 q = JSON.parse(item.dataset.userData) 73 if q.name is close_customize_shortcut.shortcut_being_customized: 74 item.scrollIntoView() 75 break 76 if apply_changes: 77 shortcuts = v'[]' 78 for x in container.lastChild.querySelectorAll('[data-shortcut]'): 79 sc = JSON.parse(x.dataset.shortcut) 80 shortcuts.push(sc) 81 sc_name = container.lastChild.dataset.scName 82 for item in container.querySelectorAll('[data-user-data]'): 83 q = JSON.parse(item.dataset.userData) 84 if q.name is sc_name: 85 q.shortcuts = shortcuts 86 item.dataset.userData = JSON.stringify(q) 87 break 88 get_container().dataset.changed = 'true' 89 commit_keyboard(create_keyboard_panel.onchange, None) 90 91 92def add_key_widget(): 93 uid = unique_id('add-new-shortcut') 94 return E.div(style='margin-top: 1ex; margin-bottom: 1ex', 95 id=uid, 96 E.div(create_button(_('Add a new shortcut'), icon="plus", action=def (evt): 97 div = document.getElementById(uid) 98 div.firstChild.style.display = 'none' 99 div.lastChild.style.display = 'block' 100 div.lastChild.querySelector('input').focus() 101 )), 102 E.div(style='display:none', 103 E.input(readonly='readonly', 104 value=_('Press the key combination to use as a shortcut'), 105 style='width: 90vw', 106 onkeydown=def (evt): 107 evt.preventDefault() 108 evt.stopPropagation() 109 if evt.key not in v"['Control', 'Meta', 'Alt', 'Shift']": 110 key_con = get_container().querySelector('.key-container') 111 key_con.appendChild(key_widget(evt)) 112 div = document.getElementById(uid) 113 div.firstChild.style.display = 'block' 114 div.lastChild.style.display = 'none' 115 )) 116 ) 117 118 119def customize_shortcut(sc_name): 120 container = get_container() 121 container.firstChild.style.display = 'none' 122 container.firstChild.nextSibling.style.display = 'none' 123 container.lastChild.style.display = 'block' 124 close_customize_shortcut.shortcut_being_customized = sc_name 125 shortcuts = v'[]' 126 for item in container.querySelectorAll('[data-user-data]'): 127 q = JSON.parse(item.dataset.userData) 128 if q.name is sc_name: 129 shortcuts = q.shortcuts 130 break 131 container = container.lastChild 132 clear(container) 133 container.dataset.scName = sc_name 134 sc = shortcuts_definition()[sc_name] 135 container.appendChild(E.h4(sc.short)) 136 if sc.long: 137 container.appendChild(E.div(sc.long, style='font-style: italic; font-size: smaller; margin-top: 1ex')) 138 container.appendChild(E.div(style='margin-top: 1rem', _('Existing shortcuts:'))) 139 key_con = container.appendChild(E.table(class_="key-container")) 140 for key in shortcuts: 141 key_con.appendChild(key_widget(key)) 142 container.appendChild(E.div(style='margin-top:1ex;', add_key_widget())) 143 container.appendChild(E.div(style='margin-top:1ex; display:flex; justify-content: flex-end', 144 create_button(_('OK'), action=close_customize_shortcut.bind(None, True)), 145 E.span('\xa0'), 146 create_button(_('Cancel'), action=close_customize_shortcut.bind(None, False)), 147 )) 148 149 150def run_search(): 151 container = get_container() 152 query = container.querySelector(f'[name=search-for-sc]').value or '' 153 query = query.toLowerCase() 154 for item in get_container().querySelectorAll('[data-user-data]'): 155 q = item.textContent.toLowerCase() 156 matches = not query or q.indexOf(query) > -1 157 item.style.display = 'list-item' if matches else 'none' 158 159 160def create_keyboard_panel(container, apply_func, cancel_func, onchange): 161 create_keyboard_panel.onchange = onchange 162 container.appendChild(E.div(id=unique_id('keyboard-settings'), style='margin: 1rem')) 163 container = container.lastChild 164 container.dataset.changed = 'false' 165 get_container.id = container.id 166 search_button = create_button(_('Search'), icon='search') 167 sb = create_search_bar( 168 run_search, 'search-for-sc', placeholder=_('Search for shortcut'), button=search_button) 169 container.appendChild(E.div( 170 style='margin-bottom: 1ex; display: flex; width: 100%; justify-content: space-between', sb, E.span('\xa0\xa0'), search_button 171 )) 172 container.firstChild.firstChild.style.flexGrow = '100' 173 container.appendChild(E.div()) 174 container.appendChild(E.div(style='display: none')) 175 container = container.firstChild.nextSibling 176 sd = get_session_data() 177 custom_shortcuts = sd.get('keyboard_shortcuts') 178 groups = as_groups(shortcuts_definition()) 179 for group_name in Object.keys(groups): 180 container.appendChild(E.h3(style='margin-top: 1ex', shortcuts_group_desc()[group_name])) 181 group = groups[group_name] 182 items = [] 183 for sc_name in sorted(Object.keys(group), key=sort_group_key.bind(None, group)): 184 sc = group[sc_name] 185 items.push(sc_as_item(sc_name, sc, custom_shortcuts[sc_name])) 186 container.appendChild(E.div()) 187 create_item_list(container.lastChild, items) 188 189 container.appendChild(E.div( 190 style='margin-top: 1rem', create_button(_('Restore defaults'), action=restore_defaults.bind(None, apply_func)) 191 )) 192 193 194develop = create_keyboard_panel 195 196 197def shortcuts_differ(a, b): 198 if a.length is not b.length: 199 return True 200 for x, y in zip(a, b): 201 if shortcut_differs(x, y): 202 return True 203 return False 204 205 206def commit_keyboard(onchange, container): 207 sd = get_session_data() 208 vals = {} 209 for item in get_container().querySelectorAll('[data-user-data]'): 210 q = JSON.parse(item.dataset.userData) 211 if shortcuts_differ(q.shortcuts, shortcuts_definition()[q.name].shortcuts): 212 vals[q.name] = q.shortcuts 213 sd.set('keyboard_shortcuts', vals) 214 if container is not None: 215 create_keyboard_panel.onchange = None 216 if get_container().dataset.changed is 'true': 217 onchange() 218