0
|
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))
|