Skip to content

Instantly share code, notes, and snippets.

@kne42
Created May 2, 2019 20:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kne42/36273dc9319230f10c6ef7fbf3542f48 to your computer and use it in GitHub Desktop.
Save kne42/36273dc9319230f10c6ef7fbf3542f48 to your computer and use it in GitHub Desktop.
Display the source blob
Display the rendered blob
Raw
{
"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