Created
May 2, 2019 20:54
-
-
Save kne42/36273dc9319230f10c6ef7fbf3542f48 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 1, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from collections import OrderedDict" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from vispy.util import keys" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"special_keys = [keys.SHIFT,\n", | |
" keys.CONTROL,\n", | |
" keys.ALT,\n", | |
" keys.META,\n", | |
" keys.UP,\n", | |
" keys.DOWN,\n", | |
" keys.LEFT,\n", | |
" keys.RIGHT,\n", | |
" keys.PAGEUP,\n", | |
" keys.PAGEDOWN,\n", | |
" keys.INSERT,\n", | |
" keys.DELETE,\n", | |
" keys.HOME,\n", | |
" keys.END,\n", | |
" keys.ESCAPE,\n", | |
" keys.BACKSPACE,\n", | |
" keys.F1,\n", | |
" keys.F2,\n", | |
" keys.F3,\n", | |
" keys.F4,\n", | |
" keys.F5,\n", | |
" keys.F6,\n", | |
" keys.F7,\n", | |
" keys.F8,\n", | |
" keys.F9,\n", | |
" keys.F10,\n", | |
" keys.F11,\n", | |
" keys.F12,\n", | |
" keys.SPACE,\n", | |
" keys.ENTER,\n", | |
" keys.TAB]\n", | |
"special_keys = [key.name for key in special_keys]" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"modifier_keys = OrderedDict(C=keys.CONTROL.name,\n", | |
" M=keys.ALT.name,\n", | |
" S=keys.SHIFT.name,\n", | |
" s=keys.META.name)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import re" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"shorthand_modifier_patt = re.compile(f\"([{''.join(key for key in modifier_keys)}])(?=-)\")" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def expand_shorthand(shorthand_seq):\n", | |
" return re.sub(shorthand_modifier_patt,\n", | |
" lambda m: modifier_keys[m.group(0)],\n", | |
" shorthand_seq)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"'Control-Alt-x'" | |
] | |
}, | |
"execution_count": 8, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"expand_shorthand('C-M-x')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 14, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def parse_seq(seq):\n", | |
" seq = normalize_key_sequence(seq)\n", | |
" parsed = seq.split('-')\n", | |
" modifiers, key = parsed[:-1], parsed[-1]\n", | |
" \n", | |
" return key, set(modifiers)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"('x', {'Alt', 'Control'})" | |
] | |
}, | |
"execution_count": 15, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"parse_seq('C-M-x')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def seq_match(seq1, seq2):\n", | |
" return parse_seq(seq1) == parse_seq(seq2)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"True" | |
] | |
}, | |
"execution_count": 17, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"seq_match('C-M-x', 'Control-Alt-x')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"True" | |
] | |
}, | |
"execution_count": 18, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"seq_match('M-C-x', 'C-M-x')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"False" | |
] | |
}, | |
"execution_count": 19, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"seq_match('x', 'M-x')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def normalize_key_sequence(seq):\n", | |
" if re.match(shorthand_modifier_patt, seq):\n", | |
" seq = expand_shorthand(seq)\n", | |
" \n", | |
" key, _ = parse_seq(seq)\n", | |
" if len(key) != 1 and key not in special_keys:\n", | |
" raise TypeError(f'invalid key {key}')\n", | |
" \n", | |
" return seq" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"'Control-v'" | |
] | |
}, | |
"execution_count": 10, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"normalize_key_sequence('C-v')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from vispy.app import KeyEvent" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def key_event_to_string(event):\n", | |
" modifiers = tuple(filter(lambda key: key in event.modifiers,\n", | |
" modifier_keys.values()))\n", | |
" return '-'.join(modifiers + (event.text,))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 13, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"'Control-Alt-x'" | |
] | |
}, | |
"execution_count": 13, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"key_event_to_string(KeyEvent(text='x', modifiers=[keys.CONTROL, keys.ALT], type='test'))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 20, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def _unbind_key(seq, keybindings):\n", | |
" return keybindings.pop(seq)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 21, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def _bind_key(seq, func, keybindings):\n", | |
" keybindings[seq] = func" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 22, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def _rebind_key(orig_seq, new_seq, keybindings):\n", | |
" func = _unbind_key(orig_seq, keybindings)\n", | |
" _bind_key(new_seq, func, keybindings)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 23, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def unbind_key(seq, keybindings):\n", | |
" seq = normalize_key_sequence(seq)\n", | |
" return _unbind_key(seq, keybindings)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 24, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def bind_key(seq, func, keybindings):\n", | |
" seq = normalize_key_sequence(seq)\n", | |
" \n", | |
" if func is None:\n", | |
" try:\n", | |
" _unbind_key(seq, keybindings)\n", | |
" except KeyError:\n", | |
" pass\n", | |
" return\n", | |
" elif isinstance(func, str):\n", | |
" new_seq = normalize_key_sequence(func)\n", | |
" _rebind_key(orig_seq, new_seq, keybindings)\n", | |
" \n", | |
" _bind_key(seq, func, keybindings)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 25, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def rebind_key(orig_seq, new_seq, keybindings):\n", | |
" orig_seq = normalize_key_sequence(orig_seq)\n", | |
" new_seq = normalize_key_sequence(new_seq)\n", | |
" \n", | |
" _rebind_key(orig_seq, new_seq, keybindings)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 26, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import itertools" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 27, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from typing import Sequence, Iterable, Mapping, ByteString, Callable" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 31, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"STRINGTYPES = str, ByteString" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 32, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def _bind_keys_validate_pair(pair):\n", | |
" if not (isinstance(pair, Sequence) and not isinstance(pair, STRINGTYPES)):\n", | |
" raise TypeError(f'expected {pair} to be a sequence, got {type(pair)}')\n", | |
" key, op = pair\n", | |
" if not isinstance(key, str):\n", | |
" raise TypeError(f'key {key} must be a string, not {type(key)}')\n", | |
" if op is not None and not isinstance(op, (str, Callable)):\n", | |
" raise TypeError(f'operation must be a string, function or None')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 33, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"_bind_keys_validate_pair(('asdf', None))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 34, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"_bind_keys_validate_pair(('asdf', lambda: 42))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 35, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"_bind_keys_validate_pair(('asdf', 'dsf'))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 36, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def _bind_keys_is_valid_pair(pair):\n", | |
" try:\n", | |
" _bind_keys_validate_pair(pair)\n", | |
" except TypeError:\n", | |
" return False\n", | |
" return True" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 37, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def bind_keys_normalize_input(args, kwargs):\n", | |
" if len(args) == 1:\n", | |
" # bind_keys((('asdf', func), ('sdf', None)))\n", | |
" # ==\n", | |
" # bind_keys(('asdf', func), ('sdf', None))\n", | |
" args = args[0]\n", | |
" \n", | |
" if isinstance(args, Mapping):\n", | |
" args = args.items() # dict -> (N, 2) view\n", | |
" \n", | |
" if isinstance(args, Iterable):\n", | |
" if isinstance(args, STRINGTYPES):\n", | |
" raise TypeError()\n", | |
" args = tuple(args)\n", | |
" \n", | |
" if len(args) == 2 and _bind_keys_is_valid_pair(args):\n", | |
" # bind_keys('asdf', func) == bind_keys(('asdf', func))\n", | |
" args = (args,)\n", | |
" \n", | |
" args += tuple(kwargs.items())\n", | |
" \n", | |
" return args" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 38, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"bind_keys_norm_tester = lambda *args, **kwargs: bind_keys_normalize_input(args, kwargs)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 39, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"(('asdf', 'sdf'),)" | |
] | |
}, | |
"execution_count": 39, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"bind_keys_norm_tester('asdf', 'sdf')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 40, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"(('asdf', None),)" | |
] | |
}, | |
"execution_count": 40, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"bind_keys_norm_tester('asdf', None)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 41, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"(('asdf', 'sdf'),)" | |
] | |
}, | |
"execution_count": 41, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"bind_keys_norm_tester(['asdf', 'sdf'])" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 42, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"(('asdf', 'sdf'), ('zxc', 'wer'))" | |
] | |
}, | |
"execution_count": 42, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"bind_keys_norm_tester(('asdf', 'sdf'),\n", | |
" ('zxc', 'wer'))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 43, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"(('asdf', 'sdf'), ('zxc', 'wer'))" | |
] | |
}, | |
"execution_count": 43, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"bind_keys_norm_tester((('asdf', 'sdf'),\n", | |
" ('zxc', 'wer')))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 48, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def bind_keys(key_pairs, keybindings):\n", | |
" for pair in key_pairs:\n", | |
" _bind_keys_validate_pair(pair)\n", | |
" bind_key(pair[0], pair[1], keybindings)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 44, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import inspect" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 45, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def on_key_press(seq, keybindings, args=(), kwargs={}):\n", | |
" seq = normalize_key_sequence(seq)\n", | |
" try:\n", | |
" func = keybindings[seq]\n", | |
" except KeyError:\n", | |
" return\n", | |
"\n", | |
" gen = func(*args, **kwargs)\n", | |
" if inspect.isgeneratorfunction(func):\n", | |
" try:\n", | |
" next(gen) # execute first statement\n", | |
" except StopIteration: # only one statement\n", | |
" pass # should we error instead? (enforce two statements)\n", | |
" return gen" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 46, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"from warnings import warn" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 47, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def on_key_release(seq, generators):\n", | |
" seq = normalize_key_sequence(seq)\n", | |
" try:\n", | |
" gen = generators.pop(seq)\n", | |
" except KeyError:\n", | |
" return\n", | |
" \n", | |
" try:\n", | |
" next(gen) # execute second statement\n", | |
" except StopIteration: # last statement\n", | |
" pass\n", | |
" else:\n", | |
" # should we error instead? (enforce two statements)\n", | |
" warn('expected 2-statement generator function (`yield` only once)')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python [conda env:napari]", | |
"language": "python", | |
"name": "conda-env-napari-py" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.7.2" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 2 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment