# Copyright 2000-2004 Michael Hudson mwh@python.net
#
# Portions Copyright (c) 2005 Nokia Corporation
#
# All Rights Reserved
#
#
# Permission to use, copy, modify, and distribute this software and
# its documentation for any purpose is hereby granted without fee,
# provided that the above copyright notice appear in all copies and
# that both that copyright notice and this permission notice appear in
# supporting documentation.
#
# THE AUTHOR MICHAEL HUDSON DISCLAIMS ALL WARRANTIES WITH REGARD TO
# THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
# INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
# RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
# CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
# keyspec parsing for a pygame console. currently this is simply copy
# n' change from the unix (ie. trad terminal) variant; probably some
# refactoring will happen when I work out how it will work best.
# A key is represented as *either*
# a) a (keycode, meta, ctrl) sequence (used for special keys such as
# f1, the up arrow key, etc)
# b) a (unichar, meta, ctrl) sequence (used for printable chars)
# Because we allow keystokes like '\\C-xu', I'll use the same trick as
# the unix keymap module uses.
# '\\C-a' --> (K_a, 0, 1)
# XXX it's actually possible to test this module, so it should have a
# XXX test suite.
import ascii
_escapes = {
'\\': K_BACKSLASH,
"'" : K_QUOTE,
'"' : K_QUOTEDBL,
# 'a' : '\a',
'b' : K_BACKSLASH,
'e' : K_ESCAPE,
# 'f' : '\f',
'n' : K_RETURN,
'r' : K_RETURN,
't' : K_TAB,
# 'v' : '\v'
}
_keynames = {
'backspace' : K_BACKSPACE,
'delete' : K_DELETE,
'down' : K_DOWN,
'end' : K_END,
'enter' : K_KP_ENTER,
'escape' : K_ESCAPE,
'f1' : K_F1, 'f2' : K_F2, 'f3' : K_F3, 'f4' : K_F4,
'f5' : K_F5, 'f6' : K_F6, 'f7' : K_F7, 'f8' : K_F8,
'f9' : K_F9, 'f10': K_F10,'f11': K_F11,'f12': K_F12,
'f13': K_F13,'f14': K_F14,'f15': K_F15,
'home' : K_HOME,
'insert' : K_INSERT,
'left' : K_LEFT,
'pgdown' : K_PAGEDOWN, 'page down' : K_PAGEDOWN,
'pgup' : K_PAGEUP, 'page up' : K_PAGEUP,
'return' : K_RETURN,
'right' : K_RIGHT,
'space' : K_SPACE,
'tab' : K_TAB,
'up' : K_UP,
}
class KeySpecError(Exception):
pass
def _parse_key1(key, s):
ctrl = 0
meta = 0
ret = ''
while not ret and s < len(key):
if key[s] == '\\':
c = key[s+1].lower()
if _escapes.has_key(c):
ret = _escapes[c]
s += 2
elif c == "c":
if key[s + 2] != '-':
raise KeySpecError, \
"\\C must be followed by `-' (char %d of %s)"%(
s + 2, repr(key))
if ctrl:
raise KeySpecError, "doubled \\C- (char %d of %s)"%(
s + 1, repr(key))
ctrl = 1
s += 3
elif c == "m":
if key[s + 2] != '-':
raise KeySpecError, \
"\\M must be followed by `-' (char %d of %s)"%(
s + 2, repr(key))
if meta:
raise KeySpecError, "doubled \\M- (char %d of %s)"%(
s + 1, repr(key))
meta = 1
s += 3
elif c.isdigit():
n = key[s+1:s+4]
ret = chr(int(n, 8))
s += 4
elif c == 'x':
n = key[s+2:s+4]
ret = chr(int(n, 16))
s += 4
elif c == '<':
t = key.find('>', s)
if t == -1:
raise KeySpecError, \
"unterminated \\< starting at char %d of %s"%(
s + 1, repr(key))
try:
ret = _keynames[key[s+2:t].lower()]
s = t + 1
except KeyError:
raise KeySpecError, \
"unrecognised keyname `%s' at char %d of %s"%(
key[s+2:t], s + 2, repr(key))
if ret is None:
return None, s
else:
raise KeySpecError, \
"unknown backslash escape %s at char %d of %s"%(
`c`, s + 2, repr(key))
else:
if ctrl:
ret = unicode(ascii.ctrl(key[s]))
else:
ret = unicode(key[s])
s += 1
return (ret, meta, ctrl), s
def parse_keys(key):
s = 0
r = []
while s < len(key):
k, s = _parse_key1(key, s)
if k is None:
return None
r.append(k)
return tuple(r)
def _compile_keymap(keymap):
r = {}
for key, value in keymap.items():
r.setdefault(key[0], {})[key[1:]] = value
for key, value in r.items():
if value.has_key(()):
if len(value) <> 1:
raise KeySpecError, \
"key definitions for %s clash"%(value.values(),)
else:
r[key] = value[()]
else:
r[key] = _compile_keymap(value)
return r
def compile_keymap(keymap):
r = {}
for key, value in keymap:
k = parse_keys(key)
if value is None and r.has_key(k):
del r[k]
if k is not None:
r[k] = value
return _compile_keymap(r)
def keyname(key):
longest_match = ''
longest_match_name = ''
for name, keyseq in keyset.items():
if keyseq and key.startswith(keyseq) and \
len(keyseq) > len(longest_match):
longest_match = keyseq
longest_match_name = name
if len(longest_match) > 0:
return longest_match_name, len(longest_match)
else:
return None, 0
_unescapes = {'\r':'\\r', '\n':'\\n', '\177':'^?'}
#for k,v in _escapes.items():
# _unescapes[v] = k
def unparse_key(keyseq):
if not keyseq:
return ''
name, s = keyname(keyseq)
if name:
if name <> 'escape' or s == len(keyseq):
return '\\<' + name + '>' + unparse_key(keyseq[s:])
else:
return '\\M-' + unparse_key(keyseq[1:])
else:
c = keyseq[0]
r = keyseq[1:]
if c == '\\':
p = '\\\\'
elif _unescapes.has_key(c):
p = _unescapes[c]
elif ord(c) < ord(' '):
p = '\\C-%s'%(chr(ord(c)+96),)
elif ord(' ') <= ord(c) <= ord('~'):
p = c
else:
p = '\\%03o'%(ord(c),)
return p + unparse_key(r)
def _unparse_keyf(keyseq):
if not keyseq:
return []
name, s = keyname(keyseq)
if name:
if name <> 'escape' or s == len(keyseq):
return [name] + _unparse_keyf(keyseq[s:])
else:
rest = _unparse_keyf(keyseq[1:])
return ['M-'+rest[0]] + rest[1:]
else:
c = keyseq[0]
r = keyseq[1:]
if c == '\\':
p = '\\'
elif _unescapes.has_key(c):
p = _unescapes[c]
elif ord(c) < ord(' '):
p = 'C-%s'%(chr(ord(c)+96),)
elif ord(' ') <= ord(c) <= ord('~'):
p = c
else:
p = '\\%03o'%(ord(c),)
return [p] + _unparse_keyf(r)
def unparse_keyf(keyseq):
return " ".join(_unparse_keyf(keyseq))