diff options
author | Wade Brainerd <wadetb@gmail.com> | 2008-11-21 20:07:50 (GMT) |
---|---|---|
committer | Wade Brainerd <wadetb@gmail.com> | 2008-11-21 20:07:50 (GMT) |
commit | cf326419e00e8a6ca24e2c8e8af9f912ee31002d (patch) | |
tree | ec38ce811b7877cdb852f25ff49fe4384a0dd10b | |
parent | de8987bf9a2477563796269933485506fceccd84 (diff) |
Keyboard internationalized.
-rw-r--r-- | TODO | 2 | ||||
-rw-r--r-- | keyboard.py | 207 | ||||
-rw-r--r-- | lessonscreen.py | 7 |
3 files changed, 122 insertions, 94 deletions
@@ -25,6 +25,8 @@ First Release - Support for displaying modifier keys in Keyboard. - Change key shown when modified is held. - Indicate next key to press on keyboard. +- Translate keyboard to native key layout. ++ Use keyboard geometry file from system instead of hardcoding. + Make medal WPM adjustable somehow? Perhaps a settable Goal WPM? + Highlight regions of keyboard, color by finger. + Allow lessons to choose between forcing correct keypresses and allowing incorrect ones (with support for Backspace). diff --git a/keyboard.py b/keyboard.py index 6960a08..1243f77 100644 --- a/keyboard.py +++ b/keyboard.py @@ -64,14 +64,8 @@ KEY_PROPS = [ # Keyboard scan code for this key. { 'name': 'key-scan', 'default': 0 }, - # Text label to be displayed on keys which do not generate ASCII keys. + # Text label to be displayed on keys which do not generate keys. { 'name': 'key-label', 'default': '' }, - - # Character generated by the key, when no modifier keys are pressed. - { 'name': 'key-normal', 'default': '' }, - - # Character generated by the key with shift pressed. - { 'name': 'key-shift', 'default': '' }, ] # This is an example keyboard layout. @@ -105,13 +99,13 @@ DEFAULT_LAYOUT = { 'key-height': 35, 'keys': [ - {'key-normal':"",'key-shift':""}, # Escape - {'key-normal':"",'key-shift':""}, # Show Source - {'key-normal':"",'key-shift':"",'key-width':182}, # Zoom - {'key-normal':"",'key-shift':"",'key-width':182}, # Size - {'key-normal':"",'key-shift':"",'key-width':181}, # Volume - {'key-normal':"",'key-shift':""}, # Window - {'key-normal':"",'key-shift':""}, # Frame + {}, # Escape + {}, # Show Source + {'key-width':182}, # Zoom + {'key-width':182}, # Size + {'key-width':181}, # Volume + {}, # Window + {}, # Frame ] }, { @@ -120,19 +114,19 @@ DEFAULT_LAYOUT = { 'group-y': 50, 'keys': [ - {'key-scan':0x31,'key-normal':"`",'key-shift':"~",'key-width':35}, - {'key-scan':0x0a,'key-normal':"1",'key-shift':"!"}, - {'key-scan':0x0b,'key-normal':"2",'key-shift':"@"}, - {'key-scan':0x0c,'key-normal':"3",'key-shift':"#"}, - {'key-scan':0x0d,'key-normal':"4",'key-shift':"$"}, - {'key-scan':0x0e,'key-normal':"5",'key-shift':"%"}, - {'key-scan':0x0f,'key-normal':"6",'key-shift':"^"}, - {'key-scan':0x10,'key-normal':"7",'key-shift':"&"}, - {'key-scan':0x11,'key-normal':"8",'key-shift':"*"}, - {'key-scan':0x12,'key-normal':"9",'key-shift':"("}, - {'key-scan':0x13,'key-normal':"0",'key-shift':")"}, - {'key-scan':0x14,'key-normal':"-",'key-shift':"_"}, - {'key-scan':0x15,'key-normal':"=",'key-shift':"+",'key-width':65}, + {'key-scan':0x31,'key-width':35}, + {'key-scan':0x0a}, + {'key-scan':0x0b}, + {'key-scan':0x0c}, + {'key-scan':0x0d}, + {'key-scan':0x0e}, + {'key-scan':0x0f}, + {'key-scan':0x10}, + {'key-scan':0x11}, + {'key-scan':0x12}, + {'key-scan':0x13}, + {'key-scan':0x14}, + {'key-scan':0x15,'key-width':65}, {'key-scan':0x16,'key-label':"erase",'key-width':95} ] }, @@ -143,18 +137,18 @@ DEFAULT_LAYOUT = { 'keys': [ {'key-scan':0x17,'key-label':"tab"}, - {'key-scan':0x18,'key-normal':"q",'key-shift':"Q"}, - {'key-scan':0x19,'key-normal':"w",'key-shift':"W"}, - {'key-scan':0x1a,'key-normal':"e",'key-shift':"E"}, - {'key-scan':0x1b,'key-normal':"r",'key-shift':"R"}, - {'key-scan':0x1c,'key-normal':"t",'key-shift':"T"}, - {'key-scan':0x1d,'key-normal':"y",'key-shift':"Y"}, - {'key-scan':0x1e,'key-normal':"u",'key-shift':"U"}, - {'key-scan':0x1f,'key-normal':"i",'key-shift':"I"}, - {'key-scan':0x20,'key-normal':"o",'key-shift':"O"}, - {'key-scan':0x21,'key-normal':"p",'key-shift':"P"}, - {'key-scan':0x22,'key-normal':"[",'key-shift':"{"}, - {'key-scan':0x23,'key-normal':"]",'key-shift':"}",'key-width':55}, + {'key-scan':0x18}, + {'key-scan':0x19}, + {'key-scan':0x1a}, + {'key-scan':0x1b}, + {'key-scan':0x1c}, + {'key-scan':0x1d}, + {'key-scan':0x1e}, + {'key-scan':0x1f}, + {'key-scan':0x20}, + {'key-scan':0x21}, + {'key-scan':0x22}, + {'key-scan':0x23,'key-width':55}, {'key-scan':0x24,'key-label':"enter",'key-width':95,'key-height':95} ] }, @@ -165,18 +159,18 @@ DEFAULT_LAYOUT = { 'keys': [ {'key-scan':0x25,'key-label':"ctrl",'key-width':55}, - {'key-scan':0x26,'key-normal':"a",'key-shift':"A"}, - {'key-scan':0x27,'key-normal':"s",'key-shift':"S"}, - {'key-scan':0x28,'key-normal':"d",'key-shift':"D"}, - {'key-scan':0x29,'key-normal':"f",'key-shift':"F"}, - {'key-scan':0x2a,'key-normal':"g",'key-shift':"G"}, - {'key-scan':0x2b,'key-normal':"h",'key-shift':"H"}, - {'key-scan':0x2c,'key-normal':"j",'key-shift':"J"}, - {'key-scan':0x2d,'key-normal':"k",'key-shift':"K"}, - {'key-scan':0x2e,'key-normal':"l",'key-shift':"L"}, - {'key-scan':0x2f,'key-normal':";",'key-shift':":"}, - {'key-scan':0x30,'key-normal':"'",'key-shift':")"}, - {'key-scan':0x33,'key-normal':"\\",'key-shift':"|"} + {'key-scan':0x26}, + {'key-scan':0x27}, + {'key-scan':0x28}, + {'key-scan':0x29}, + {'key-scan':0x2a}, + {'key-scan':0x2b}, + {'key-scan':0x2c}, + {'key-scan':0x2d}, + {'key-scan':0x2e}, + {'key-scan':0x2f}, + {'key-scan':0x30}, + {'key-scan':0x33} ] }, { @@ -186,19 +180,19 @@ DEFAULT_LAYOUT = { 'keys': [ {'key-scan':0x32,'key-label':"shift",'key-width':75}, - {'key-scan':0x34,'key-normal':"z",'key-shift':"Z"}, - {'key-scan':0x35,'key-normal':"x",'key-shift':"X"}, - {'key-scan':0x36,'key-normal':"c",'key-shift':"C"}, - {'key-scan':0x37,'key-normal':"v",'key-shift':"V"}, - {'key-scan':0x38,'key-normal':"b",'key-shift':"B"}, - {'key-scan':0x39,'key-normal':"n",'key-shift':"N"}, - {'key-scan':0x3a,'key-normal':"m",'key-shift':"M"}, - {'key-scan':0x3b,'key-normal':",",'key-shift':"<"}, - {'key-scan':0x3c,'key-normal':".",'key-shift':">"}, - {'key-scan':0x3d,'key-normal':"/",'key-shift':"?"}, + {'key-scan':0x34}, + {'key-scan':0x35}, + {'key-scan':0x36}, + {'key-scan':0x37}, + {'key-scan':0x38}, + {'key-scan':0x39}, + {'key-scan':0x3a}, + {'key-scan':0x3b}, + {'key-scan':0x3c}, + {'key-scan':0x3d}, {'key-scan':0x3e,'key-label':"shift",'key-width':75}, - {'key-scan':0x6f,'key-label':"",'key-shift':""}, # Up - {'key-label':"",'key-shift':""}, # Multiply + {'key-scan':0x6f,'key-label':""}, # Up + {'key-label':""}, # Language key ] }, { @@ -210,7 +204,7 @@ DEFAULT_LAYOUT = { {'key-label':"fn",'key-width':35}, {'key-label':"",'key-width':55}, # LHand {'key-scan':0x40,'key-label':"alt",'key-width':55}, # LAlt - {'key-scan':0x41,'key-normal':" ",'key-shift':" ", 'key-width':325}, + {'key-scan':0x41,'key-width':325}, # Spacebar {'key-scan':0x6c,'key-label':"alt",'key-width':55}, # RAlt {'key-label':"",'key-width':55}, # RHand {'key-scan':0x71,'key-label':""}, # Left @@ -247,19 +241,29 @@ class Keyboard(gtk.EventBox): def __init__(self, root_window): gtk.EventBox.__init__(self) - + self.root_window = root_window - + + # Create the drawing area. self.area = gtk.DrawingArea() self.area.connect("expose-event", self._expose_cb) self.add(self.area) - + + # Access the current GTK keymap. + self.keymap = gtk.gdk.keymap_get_default() + + # Active language group and modifier state. + # See http://www.pygtk.org/docs/pygtk/class-gdkkeymap.html for more + # information about key group and state. + self.active_group = 0 + self.active_state = 0 + # This array contains the current keyboard layout. self.keys = None - self.key_map = None - + self.key_scan_map = None + self.shift_down = False - + # Connect keyboard grabbing and releasing callbacks. self.connect('realize', self._realize_cb) self.connect('unrealize', self._unrealize_cb) @@ -279,7 +283,7 @@ class Keyboard(gtk.EventBox): Also fills in derived and inherited key properties. The layout description can be discarded afterwards.""" self.keys = [] - self.key_map = {} + self.key_scan_map = {} group_count = 0 for g in layout['groups']: @@ -305,7 +309,7 @@ class Keyboard(gtk.EventBox): props[pname] = layout[pname] else: props[pname] = p['default'] - + # Add to internal list. key = Key(props) self.keys.append(key) @@ -313,7 +317,7 @@ class Keyboard(gtk.EventBox): # Add to scan code mapping table. if props['key-scan']: - self.key_map[props['key-scan']] = key + self.key_scan_map[props['key-scan']] = key group_count += 1 @@ -429,12 +433,18 @@ class Keyboard(gtk.EventBox): cr.clip() # Inner text. + text = '' if k.props['key-label']: text = k.props['key-label'] - elif self.shift_down: - text = k.props['key-shift'] else: - text = k.props['key-normal'] + info = self.keymap.translate_keyboard_state( + k.props['key-scan'], self.active_state, self.active_group) + if info: + key = gtk.gdk.keyval_to_unicode(info[0]) + try: + text = unichr(key).encode('utf-8') + except: + pass cr.set_font_size(16) x_bearing, y_bearing, width, height = cr.text_extents(text)[:4] @@ -447,22 +457,32 @@ class Keyboard(gtk.EventBox): return True def _key_press_cb(self, widget, event): - if self.key_map.has_key(event.hardware_keycode): - key = self.key_map[event.hardware_keycode] + key = self.key_scan_map.get(event.hardware_keycode) + if key: key.pressed = True - if key.props['key-label'] == 'shift': - self.shift_down = True - + + self.active_group = event.group + self.active_state = event.state + + # Hack to get the current modifier state - which will not be represented by the event. + self.active_state = gtk.gdk.device_get_core_pointer().get_state(self.window)[1] + #print "press %d state=%x group=%d" % (event.hardware_keycode, self.active_state, event.group) + self.queue_draw() return False def _key_release_cb(self, widget, event): - if self.key_map.has_key(event.hardware_keycode): - key = self.key_map[event.hardware_keycode] + key = self.key_scan_map.get(event.hardware_keycode) + if key: key.pressed = False - if key.props['key-label'] == 'shift': - self.shift_down = False - + + self.active_group = event.group + self.active_state = event.state + + # Hack to get the current modifier state - which will not be represented by the event. + self.active_state = gtk.gdk.device_get_core_pointer().get_state(self.window)[1] + #print "release %d state=%x group=%d" % (event.hardware_keycode, self.active_state, event.group) + self.queue_draw() return False @@ -471,10 +491,17 @@ class Keyboard(gtk.EventBox): k.hilite = False def find_key_by_letter(self, letter): - for k in self.keys: - if k.props['key-normal'] == letter or k.props['key-shift'] == letter: - return k - return None + # Convert unicode to GDK keyval. + keyval = gtk.gdk.unicode_to_keyval(ord(letter)) + + # Find list of key combinations that can generate this keyval. + # If found, return the key whose scan code matches the first combo. + entries = self.keymap.get_entries_for_keyval(keyval) + if entries: + code = entries[0][0] + return self.key_scan_map.get(code) + else: + return None if __name__ == "__main__": window = gtk.Window(gtk.WINDOW_TOPLEVEL) diff --git a/lessonscreen.py b/lessonscreen.py index b09f925..25328dc 100644 --- a/lessonscreen.py +++ b/lessonscreen.py @@ -201,7 +201,6 @@ class LessonScreen(gtk.VBox): # End lesson if this is the last step. if self.next_step_idx >= len(self.lesson['steps']): - print "Lesson finished." self.lesson_finished = True self.show_lesson_report() return @@ -262,11 +261,11 @@ class LessonScreen(gtk.VBox): def key_press_cb(self, widget, event): # Ignore hotkeys. if event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.MOD1_MASK): - return + return False # Extract information about the key pressed. key = gtk.gdk.keyval_to_unicode(event.keyval) - if key != 0: key = chr(key) + if key != 0: key = unichr(key) key_name = gtk.gdk.keyval_name(event.keyval) # Simply wait for a return keypress on the lesson finished screen. @@ -274,7 +273,7 @@ class LessonScreen(gtk.VBox): # TODO: Wait a second first. if key_name == 'Return': self.end_lesson() - return + return False # Convert Return keys to paragraph symbols. if key_name == 'Return': |