|
1 # Copyright 2000-2004 Michael Hudson mwh@python.net |
|
2 # |
|
3 # Portions Copyright (c) 2005 Nokia Corporation |
|
4 # |
|
5 # All Rights Reserved |
|
6 # |
|
7 # |
|
8 # Permission to use, copy, modify, and distribute this software and |
|
9 # its documentation for any purpose is hereby granted without fee, |
|
10 # provided that the above copyright notice appear in all copies and |
|
11 # that both that copyright notice and this permission notice appear in |
|
12 # supporting documentation. |
|
13 # |
|
14 # THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO |
|
15 # THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY |
|
16 # AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, |
|
17 # INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER |
|
18 # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF |
|
19 # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN |
|
20 # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
|
21 |
|
22 # keyspec parsing for a pygame console. currently this is simply copy |
|
23 # n' change from the unix (ie. trad terminal) variant; probably some |
|
24 # refactoring will happen when I work out how it will work best. |
|
25 |
|
26 # A key is represented as *either* |
|
27 |
|
28 # a) a (keycode, meta, ctrl) sequence (used for special keys such as |
|
29 # f1, the up arrow key, etc) |
|
30 # b) a (unichar, meta, ctrl) sequence (used for printable chars) |
|
31 |
|
32 # Because we allow keystokes like '\\C-xu', I'll use the same trick as |
|
33 # the unix keymap module uses. |
|
34 |
|
35 # '\\C-a' --> (K_a, 0, 1) |
|
36 |
|
37 # XXX it's actually possible to test this module, so it should have a |
|
38 # XXX test suite. |
|
39 |
|
40 import ascii |
|
41 |
|
42 _escapes = { |
|
43 '\\': K_BACKSLASH, |
|
44 "'" : K_QUOTE, |
|
45 '"' : K_QUOTEDBL, |
|
46 # 'a' : '\a', |
|
47 'b' : K_BACKSLASH, |
|
48 'e' : K_ESCAPE, |
|
49 # 'f' : '\f', |
|
50 'n' : K_RETURN, |
|
51 'r' : K_RETURN, |
|
52 't' : K_TAB, |
|
53 # 'v' : '\v' |
|
54 } |
|
55 |
|
56 _keynames = { |
|
57 'backspace' : K_BACKSPACE, |
|
58 'delete' : K_DELETE, |
|
59 'down' : K_DOWN, |
|
60 'end' : K_END, |
|
61 'enter' : K_KP_ENTER, |
|
62 'escape' : K_ESCAPE, |
|
63 'f1' : K_F1, 'f2' : K_F2, 'f3' : K_F3, 'f4' : K_F4, |
|
64 'f5' : K_F5, 'f6' : K_F6, 'f7' : K_F7, 'f8' : K_F8, |
|
65 'f9' : K_F9, 'f10': K_F10,'f11': K_F11,'f12': K_F12, |
|
66 'f13': K_F13,'f14': K_F14,'f15': K_F15, |
|
67 'home' : K_HOME, |
|
68 'insert' : K_INSERT, |
|
69 'left' : K_LEFT, |
|
70 'pgdown' : K_PAGEDOWN, 'page down' : K_PAGEDOWN, |
|
71 'pgup' : K_PAGEUP, 'page up' : K_PAGEUP, |
|
72 'return' : K_RETURN, |
|
73 'right' : K_RIGHT, |
|
74 'space' : K_SPACE, |
|
75 'tab' : K_TAB, |
|
76 'up' : K_UP, |
|
77 } |
|
78 |
|
79 class KeySpecError(Exception): |
|
80 pass |
|
81 |
|
82 def _parse_key1(key, s): |
|
83 ctrl = 0 |
|
84 meta = 0 |
|
85 ret = '' |
|
86 while not ret and s < len(key): |
|
87 if key[s] == '\\': |
|
88 c = key[s+1].lower() |
|
89 if _escapes.has_key(c): |
|
90 ret = _escapes[c] |
|
91 s += 2 |
|
92 elif c == "c": |
|
93 if key[s + 2] != '-': |
|
94 raise KeySpecError, \ |
|
95 "\\C must be followed by `-' (char %d of %s)"%( |
|
96 s + 2, repr(key)) |
|
97 if ctrl: |
|
98 raise KeySpecError, "doubled \\C- (char %d of %s)"%( |
|
99 s + 1, repr(key)) |
|
100 ctrl = 1 |
|
101 s += 3 |
|
102 elif c == "m": |
|
103 if key[s + 2] != '-': |
|
104 raise KeySpecError, \ |
|
105 "\\M must be followed by `-' (char %d of %s)"%( |
|
106 s + 2, repr(key)) |
|
107 if meta: |
|
108 raise KeySpecError, "doubled \\M- (char %d of %s)"%( |
|
109 s + 1, repr(key)) |
|
110 meta = 1 |
|
111 s += 3 |
|
112 elif c.isdigit(): |
|
113 n = key[s+1:s+4] |
|
114 ret = chr(int(n, 8)) |
|
115 s += 4 |
|
116 elif c == 'x': |
|
117 n = key[s+2:s+4] |
|
118 ret = chr(int(n, 16)) |
|
119 s += 4 |
|
120 elif c == '<': |
|
121 t = key.find('>', s) |
|
122 if t == -1: |
|
123 raise KeySpecError, \ |
|
124 "unterminated \\< starting at char %d of %s"%( |
|
125 s + 1, repr(key)) |
|
126 try: |
|
127 ret = _keynames[key[s+2:t].lower()] |
|
128 s = t + 1 |
|
129 except KeyError: |
|
130 raise KeySpecError, \ |
|
131 "unrecognised keyname `%s' at char %d of %s"%( |
|
132 key[s+2:t], s + 2, repr(key)) |
|
133 if ret is None: |
|
134 return None, s |
|
135 else: |
|
136 raise KeySpecError, \ |
|
137 "unknown backslash escape %s at char %d of %s"%( |
|
138 `c`, s + 2, repr(key)) |
|
139 else: |
|
140 if ctrl: |
|
141 ret = unicode(ascii.ctrl(key[s])) |
|
142 else: |
|
143 ret = unicode(key[s]) |
|
144 s += 1 |
|
145 return (ret, meta, ctrl), s |
|
146 |
|
147 def parse_keys(key): |
|
148 s = 0 |
|
149 r = [] |
|
150 while s < len(key): |
|
151 k, s = _parse_key1(key, s) |
|
152 if k is None: |
|
153 return None |
|
154 r.append(k) |
|
155 return tuple(r) |
|
156 |
|
157 def _compile_keymap(keymap): |
|
158 r = {} |
|
159 for key, value in keymap.items(): |
|
160 r.setdefault(key[0], {})[key[1:]] = value |
|
161 for key, value in r.items(): |
|
162 if value.has_key(()): |
|
163 if len(value) <> 1: |
|
164 raise KeySpecError, \ |
|
165 "key definitions for %s clash"%(value.values(),) |
|
166 else: |
|
167 r[key] = value[()] |
|
168 else: |
|
169 r[key] = _compile_keymap(value) |
|
170 return r |
|
171 |
|
172 def compile_keymap(keymap): |
|
173 r = {} |
|
174 for key, value in keymap: |
|
175 k = parse_keys(key) |
|
176 if value is None and r.has_key(k): |
|
177 del r[k] |
|
178 if k is not None: |
|
179 r[k] = value |
|
180 return _compile_keymap(r) |
|
181 |
|
182 def keyname(key): |
|
183 longest_match = '' |
|
184 longest_match_name = '' |
|
185 for name, keyseq in keyset.items(): |
|
186 if keyseq and key.startswith(keyseq) and \ |
|
187 len(keyseq) > len(longest_match): |
|
188 longest_match = keyseq |
|
189 longest_match_name = name |
|
190 if len(longest_match) > 0: |
|
191 return longest_match_name, len(longest_match) |
|
192 else: |
|
193 return None, 0 |
|
194 |
|
195 _unescapes = {'\r':'\\r', '\n':'\\n', '\177':'^?'} |
|
196 |
|
197 #for k,v in _escapes.items(): |
|
198 # _unescapes[v] = k |
|
199 |
|
200 def unparse_key(keyseq): |
|
201 if not keyseq: |
|
202 return '' |
|
203 name, s = keyname(keyseq) |
|
204 if name: |
|
205 if name <> 'escape' or s == len(keyseq): |
|
206 return '\\<' + name + '>' + unparse_key(keyseq[s:]) |
|
207 else: |
|
208 return '\\M-' + unparse_key(keyseq[1:]) |
|
209 else: |
|
210 c = keyseq[0] |
|
211 r = keyseq[1:] |
|
212 if c == '\\': |
|
213 p = '\\\\' |
|
214 elif _unescapes.has_key(c): |
|
215 p = _unescapes[c] |
|
216 elif ord(c) < ord(' '): |
|
217 p = '\\C-%s'%(chr(ord(c)+96),) |
|
218 elif ord(' ') <= ord(c) <= ord('~'): |
|
219 p = c |
|
220 else: |
|
221 p = '\\%03o'%(ord(c),) |
|
222 return p + unparse_key(r) |
|
223 |
|
224 def _unparse_keyf(keyseq): |
|
225 if not keyseq: |
|
226 return [] |
|
227 name, s = keyname(keyseq) |
|
228 if name: |
|
229 if name <> 'escape' or s == len(keyseq): |
|
230 return [name] + _unparse_keyf(keyseq[s:]) |
|
231 else: |
|
232 rest = _unparse_keyf(keyseq[1:]) |
|
233 return ['M-'+rest[0]] + rest[1:] |
|
234 else: |
|
235 c = keyseq[0] |
|
236 r = keyseq[1:] |
|
237 if c == '\\': |
|
238 p = '\\' |
|
239 elif _unescapes.has_key(c): |
|
240 p = _unescapes[c] |
|
241 elif ord(c) < ord(' '): |
|
242 p = 'C-%s'%(chr(ord(c)+96),) |
|
243 elif ord(' ') <= ord(c) <= ord('~'): |
|
244 p = c |
|
245 else: |
|
246 p = '\\%03o'%(ord(c),) |
|
247 return [p] + _unparse_keyf(r) |
|
248 |
|
249 def unparse_keyf(keyseq): |
|
250 return " ".join(_unparse_keyf(keyseq)) |