1# Copyright 2000-2004 Michael Hudson-Doyle <micahel@gmail.com> 2# 3# All Rights Reserved 4# 5# 6# Permission to use, copy, modify, and distribute this software and 7# its documentation for any purpose is hereby granted without fee, 8# provided that the above copyright notice appear in all copies and 9# that both that copyright notice and this permission notice appear in 10# supporting documentation. 11# 12# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO 13# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 14# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, 15# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER 16# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF 17# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 18# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 19 20# (naming modules after builtin functions is not such a hot idea...) 21 22# an KeyTrans instance translates Event objects into Command objects 23 24# hmm, at what level do we want [C-i] and [tab] to be equivalent? 25# [meta-a] and [esc a]? obviously, these are going to be equivalent 26# for the UnixConsole, but should they be for PygameConsole? 27 28# it would in any situation seem to be a bad idea to bind, say, [tab] 29# and [C-i] to *different* things... but should binding one bind the 30# other? 31 32# executive, temporary decision: [tab] and [C-i] are distinct, but 33# [meta-key] is identified with [esc key]. We demand that any console 34# class does quite a lot towards emulating a unix terminal. 35 36from __future__ import annotations 37 38from abc import ABC, abstractmethod 39import unicodedata 40from collections import deque 41 42 43# types 44if False: 45 from .types import EventTuple 46 47 48class InputTranslator(ABC): 49 @abstractmethod 50 def push(self, evt: EventTuple) -> None: 51 pass 52 53 @abstractmethod 54 def get(self) -> EventTuple | None: 55 return None 56 57 @abstractmethod 58 def empty(self) -> bool: 59 return True 60 61 62class KeymapTranslator(InputTranslator): 63 def __init__(self, keymap, verbose=False, invalid_cls=None, character_cls=None): 64 self.verbose = verbose 65 from .keymap import compile_keymap, parse_keys 66 67 self.keymap = keymap 68 self.invalid_cls = invalid_cls 69 self.character_cls = character_cls 70 d = {} 71 for keyspec, command in keymap: 72 keyseq = tuple(parse_keys(keyspec)) 73 d[keyseq] = command 74 if self.verbose: 75 print(d) 76 self.k = self.ck = compile_keymap(d, ()) 77 self.results = deque() 78 self.stack = [] 79 80 def push(self, evt): 81 if self.verbose: 82 print("pushed", evt.data, end="") 83 key = evt.data 84 d = self.k.get(key) 85 if isinstance(d, dict): 86 if self.verbose: 87 print("transition") 88 self.stack.append(key) 89 self.k = d 90 else: 91 if d is None: 92 if self.verbose: 93 print("invalid") 94 if self.stack or len(key) > 1 or unicodedata.category(key) == "C": 95 self.results.append((self.invalid_cls, self.stack + [key])) 96 else: 97 # small optimization: 98 self.k[key] = self.character_cls 99 self.results.append((self.character_cls, [key])) 100 else: 101 if self.verbose: 102 print("matched", d) 103 self.results.append((d, self.stack + [key])) 104 self.stack = [] 105 self.k = self.ck 106 107 def get(self): 108 if self.results: 109 return self.results.popleft() 110 else: 111 return None 112 113 def empty(self) -> bool: 114 return not self.results 115