Commits

Henning Schröder  committed b7885b4

first version

  • Participants

Comments (0)

Files changed (6)

+# -*_ coding: utf-8 -*-
+# http://gnosis.cx/publish/programming/charming_python_6.html
+# TODO: implement ConsoleLib-API with ncurses: http://effbot.org/zone/console-handbook.htm
+import sys
+import curses
+
+from keymap import keymap
+
+
+def debug(*args):
+    s = " ".join(str(a) for a in args)
+    open("/tmp/debug.log", "a").write("%s\n" % s)
+
+    
+
+class Console(object):
+    instance = None
+    
+    def __init__(self):
+        if Console.instance:
+            raise RuntimeError("Only one console instance allowed")
+        Console.instance = self
+        sys.excepthook = self.excepthook
+        self.window = curses.initscr()
+        if curses.has_colors():
+            curses.start_color()
+        curses.noecho()
+        curses.cbreak()    
+        #curses.raw() # intercept everything
+        # In keypad mode, escape sequences for special keys
+        # (like the cursor keys) will be interpreted and
+        # a special value like curses.KEY_LEFT will be returned
+        #self.window.keypad(1)
+
+        
+        # Frame the interface area at fixed VT100 size
+        self.screen = self.window.subwin(25, 80, 0, 0)
+        #self.screen.box()
+        #self.screen.hline(2, 1, curses.ACS_HLINE, 77)
+        self.screen.refresh()
+    
+
+        
+    def excepthook(self, *args):
+        self.close()
+        sys.__excepthook__(*args)
+        sys.exit(1)
+
+
+    def __del__(self):
+        self.close()
+        
+
+    def close(self):
+        if self.window:
+            self.window.keypad(0)
+        curses.echo()
+        #curses.nocbreak()
+        curses.endwin()
+
+
+    def getchar(self, wait=False):
+        if not wait:
+            self.screen.nodelay(1)
+            while 1:
+                try:
+                    curses.cbreak()
+                    break
+                except _curses.error:
+                    pass
+        return self.screen.getch()
+    
+    
+    def readkeys(self):
+        l = []
+        while True:
+            key = self.getchar()
+            if key == -1:
+                break
+            l.append(chr(key))
+        return "".join(l)
+
+        
+    
+    def refresh(self):
+        self.screen.refresh()
+    
+    
+    def text(self, column, line, string, style=curses.A_NORMAL):
+        self.screen.addstr(line, column, string, style)
+
+
+    def page(self):
+        self.window.erase()
+        
+
+    def gotoxy(self, x, y):
+        self.window.move(x, y)
+
+        
+    def show_cursor(self):
+        curses.curs_set(2)
+
+
+    def hide_cursor(self):
+        curses.curs_set(0)
+
+
+
+if __name__=='__main__':
+    c = Console()
+    c.text(1,1, "hello, world!")
+    i = 0
+    while True:
+        keys = c.readkeys()
+        if keys:
+            if keys in keymap:
+                keys = keymap[keys]
+            i += 1
+            c.text(i%23, 1, "%r  " % keys)
+    c.close()
+

File generate_keymap.py

+# -*- coding: utf-8 -*-
+
+def escape_modifier( digit ):
+    mode = ord(digit) - ord("1")
+    return "shift "*(mode&1) + "meta "*((mode&2)/2) + "ctrl "*((mode&4)/4)
+
+
+keys = [('[A','up'),('[B','down'),('[C','right'),('[D','left'),
+    ('[E','5'),('[F','end'),('[G','5'),('[H','home'),
+    ('[1~','home'),('[2~','insert'),('[3~','delete'),('[4~','end'),
+    ('[5~','page up'),('[6~','page down'),
+    ('[7~','home'),('[8~','end'),
+    
+    ('[[A','f1'),('[[B','f2'),('[[C','f3'),('[[D','f4'),('[[E','f5'),
+    
+    ('[11~','f1'),('[12~','f2'),('[13~','f3'),('[14~','f4'),
+    ('[15~','f5'),('[17~','f6'),('[18~','f7'),('[19~','f8'),
+    ('[20~','f9'),('[21~','f10'),('[23~','f11'),('[24~','f12'),
+    ('[25~','f13'),('[26~','f14'),('[28~','f15'),('[29~','f16'),
+    ('[31~','f17'),('[32~','f18'),('[33~','f19'),('[34~','f20'),
+    
+    ('OA','up'),('OB','down'),('OC','right'),('OD','left'),
+    ('OH','home'),('OF','end'),
+    ('OP','f1'),('OQ','f2'),('OR','f3'),('OS','f4'),
+    ('Oo','/'),('Oj','*'),('Om','-'),('Ok','+'),
+    
+    ('[Z','shift tab')
+    ] + [
+    # modified cursor keys + home, end, 5 -- [#X and [1;#X forms
+    (prefix+digit+letter, escape_modifier(digit) + key)
+       for prefix in "[","[1;"
+         for digit in "12345678"
+           for letter,key in zip("ABCDEFGH",
+              ('up','down','right','left','5','end','5','home'))
+    ] + [ 
+    # modified F1-F4 keys -- O#X form
+    ("O"+digit+letter, escape_modifier(digit) + key)
+      for digit in "12345678"
+        for letter,key in zip("PQRS",('f1','f2','f3','f4'))
+    ] + [ 
+    # modified F1-F13 keys -- [XX;#~ form
+    ("["+str(num)+";"+digit+"~", escape_modifier(digit) + key)
+    for digit in "12345678"
+    for num,key in zip(
+    (11,12,13,14,15,17,18,19,20,21,23,24,25,26,28,29,31,32,33,34),
+    ('f1','f2','f3','f4','f5','f6','f7','f8','f9','f10','f11',
+    'f12','f13','f14','f15','f16','f17','f18','f19','f20'))
+    ]
+
+	
+if __name__ == "__main__":
+    f = open("keymap.py", "w")
+    print >>f, "# -*- coding: utf-8 -*-"
+    print >>f, "keymap = {"
+    for chars, name in keys:
+        print >>f, "    %r: %r," % (chars, name)
+    print >>f, "}"
+    f.close()
+# -*- coding: utf-8 -*-
+keymap = {
+    '[A': 'up',
+    '[B': 'down',
+    '[C': 'right',
+    '[D': 'left',
+    '[E': '5',
+    '[F': 'end',
+    '[G': '5',
+    '[H': 'home',
+    '[1~': 'home',
+    '[2~': 'insert',
+    '[3~': 'delete',
+    '[4~': 'end',
+    '[5~': 'page up',
+    '[6~': 'page down',
+    '[7~': 'home',
+    '[8~': 'end',
+    '[[A': 'f1',
+    '[[B': 'f2',
+    '[[C': 'f3',
+    '[[D': 'f4',
+    '[[E': 'f5',
+    '[11~': 'f1',
+    '[12~': 'f2',
+    '[13~': 'f3',
+    '[14~': 'f4',
+    '[15~': 'f5',
+    '[17~': 'f6',
+    '[18~': 'f7',
+    '[19~': 'f8',
+    '[20~': 'f9',
+    '[21~': 'f10',
+    '[23~': 'f11',
+    '[24~': 'f12',
+    '[25~': 'f13',
+    '[26~': 'f14',
+    '[28~': 'f15',
+    '[29~': 'f16',
+    '[31~': 'f17',
+    '[32~': 'f18',
+    '[33~': 'f19',
+    '[34~': 'f20',
+    'OA': 'up',
+    'OB': 'down',
+    'OC': 'right',
+    'OD': 'left',
+    'OH': 'home',
+    'OF': 'end',
+    'OP': 'f1',
+    'OQ': 'f2',
+    'OR': 'f3',
+    'OS': 'f4',
+    'Oo': '/',
+    'Oj': '*',
+    'Om': '-',
+    'Ok': '+',
+    '[Z': 'shift tab',
+    '[1A': 'up',
+    '[1B': 'down',
+    '[1C': 'right',
+    '[1D': 'left',
+    '[1E': '5',
+    '[1F': 'end',
+    '[1G': '5',
+    '[1H': 'home',
+    '[2A': 'shift up',
+    '[2B': 'shift down',
+    '[2C': 'shift right',
+    '[2D': 'shift left',
+    '[2E': 'shift 5',
+    '[2F': 'shift end',
+    '[2G': 'shift 5',
+    '[2H': 'shift home',
+    '[3A': 'meta up',
+    '[3B': 'meta down',
+    '[3C': 'meta right',
+    '[3D': 'meta left',
+    '[3E': 'meta 5',
+    '[3F': 'meta end',
+    '[3G': 'meta 5',
+    '[3H': 'meta home',
+    '[4A': 'shift meta up',
+    '[4B': 'shift meta down',
+    '[4C': 'shift meta right',
+    '[4D': 'shift meta left',
+    '[4E': 'shift meta 5',
+    '[4F': 'shift meta end',
+    '[4G': 'shift meta 5',
+    '[4H': 'shift meta home',
+    '[5A': 'ctrl up',
+    '[5B': 'ctrl down',
+    '[5C': 'ctrl right',
+    '[5D': 'ctrl left',
+    '[5E': 'ctrl 5',
+    '[5F': 'ctrl end',
+    '[5G': 'ctrl 5',
+    '[5H': 'ctrl home',
+    '[6A': 'shift ctrl up',
+    '[6B': 'shift ctrl down',
+    '[6C': 'shift ctrl right',
+    '[6D': 'shift ctrl left',
+    '[6E': 'shift ctrl 5',
+    '[6F': 'shift ctrl end',
+    '[6G': 'shift ctrl 5',
+    '[6H': 'shift ctrl home',
+    '[7A': 'meta ctrl up',
+    '[7B': 'meta ctrl down',
+    '[7C': 'meta ctrl right',
+    '[7D': 'meta ctrl left',
+    '[7E': 'meta ctrl 5',
+    '[7F': 'meta ctrl end',
+    '[7G': 'meta ctrl 5',
+    '[7H': 'meta ctrl home',
+    '[8A': 'shift meta ctrl up',
+    '[8B': 'shift meta ctrl down',
+    '[8C': 'shift meta ctrl right',
+    '[8D': 'shift meta ctrl left',
+    '[8E': 'shift meta ctrl 5',
+    '[8F': 'shift meta ctrl end',
+    '[8G': 'shift meta ctrl 5',
+    '[8H': 'shift meta ctrl home',
+    '[1;1A': 'up',
+    '[1;1B': 'down',
+    '[1;1C': 'right',
+    '[1;1D': 'left',
+    '[1;1E': '5',
+    '[1;1F': 'end',
+    '[1;1G': '5',
+    '[1;1H': 'home',
+    '[1;2A': 'shift up',
+    '[1;2B': 'shift down',
+    '[1;2C': 'shift right',
+    '[1;2D': 'shift left',
+    '[1;2E': 'shift 5',
+    '[1;2F': 'shift end',
+    '[1;2G': 'shift 5',
+    '[1;2H': 'shift home',
+    '[1;3A': 'meta up',
+    '[1;3B': 'meta down',
+    '[1;3C': 'meta right',
+    '[1;3D': 'meta left',
+    '[1;3E': 'meta 5',
+    '[1;3F': 'meta end',
+    '[1;3G': 'meta 5',
+    '[1;3H': 'meta home',
+    '[1;4A': 'shift meta up',
+    '[1;4B': 'shift meta down',
+    '[1;4C': 'shift meta right',
+    '[1;4D': 'shift meta left',
+    '[1;4E': 'shift meta 5',
+    '[1;4F': 'shift meta end',
+    '[1;4G': 'shift meta 5',
+    '[1;4H': 'shift meta home',
+    '[1;5A': 'ctrl up',
+    '[1;5B': 'ctrl down',
+    '[1;5C': 'ctrl right',
+    '[1;5D': 'ctrl left',
+    '[1;5E': 'ctrl 5',
+    '[1;5F': 'ctrl end',
+    '[1;5G': 'ctrl 5',
+    '[1;5H': 'ctrl home',
+    '[1;6A': 'shift ctrl up',
+    '[1;6B': 'shift ctrl down',
+    '[1;6C': 'shift ctrl right',
+    '[1;6D': 'shift ctrl left',
+    '[1;6E': 'shift ctrl 5',
+    '[1;6F': 'shift ctrl end',
+    '[1;6G': 'shift ctrl 5',
+    '[1;6H': 'shift ctrl home',
+    '[1;7A': 'meta ctrl up',
+    '[1;7B': 'meta ctrl down',
+    '[1;7C': 'meta ctrl right',
+    '[1;7D': 'meta ctrl left',
+    '[1;7E': 'meta ctrl 5',
+    '[1;7F': 'meta ctrl end',
+    '[1;7G': 'meta ctrl 5',
+    '[1;7H': 'meta ctrl home',
+    '[1;8A': 'shift meta ctrl up',
+    '[1;8B': 'shift meta ctrl down',
+    '[1;8C': 'shift meta ctrl right',
+    '[1;8D': 'shift meta ctrl left',
+    '[1;8E': 'shift meta ctrl 5',
+    '[1;8F': 'shift meta ctrl end',
+    '[1;8G': 'shift meta ctrl 5',
+    '[1;8H': 'shift meta ctrl home',
+    'O1P': 'f1',
+    'O1Q': 'f2',
+    'O1R': 'f3',
+    'O1S': 'f4',
+    'O2P': 'shift f1',
+    'O2Q': 'shift f2',
+    'O2R': 'shift f3',
+    'O2S': 'shift f4',
+    'O3P': 'meta f1',
+    'O3Q': 'meta f2',
+    'O3R': 'meta f3',
+    'O3S': 'meta f4',
+    'O4P': 'shift meta f1',
+    'O4Q': 'shift meta f2',
+    'O4R': 'shift meta f3',
+    'O4S': 'shift meta f4',
+    'O5P': 'ctrl f1',
+    'O5Q': 'ctrl f2',
+    'O5R': 'ctrl f3',
+    'O5S': 'ctrl f4',
+    'O6P': 'shift ctrl f1',
+    'O6Q': 'shift ctrl f2',
+    'O6R': 'shift ctrl f3',
+    'O6S': 'shift ctrl f4',
+    'O7P': 'meta ctrl f1',
+    'O7Q': 'meta ctrl f2',
+    'O7R': 'meta ctrl f3',
+    'O7S': 'meta ctrl f4',
+    'O8P': 'shift meta ctrl f1',
+    'O8Q': 'shift meta ctrl f2',
+    'O8R': 'shift meta ctrl f3',
+    'O8S': 'shift meta ctrl f4',
+    '[11;1~': 'f1',
+    '[12;1~': 'f2',
+    '[13;1~': 'f3',
+    '[14;1~': 'f4',
+    '[15;1~': 'f5',
+    '[17;1~': 'f6',
+    '[18;1~': 'f7',
+    '[19;1~': 'f8',
+    '[20;1~': 'f9',
+    '[21;1~': 'f10',
+    '[23;1~': 'f11',
+    '[24;1~': 'f12',
+    '[25;1~': 'f13',
+    '[26;1~': 'f14',
+    '[28;1~': 'f15',
+    '[29;1~': 'f16',
+    '[31;1~': 'f17',
+    '[32;1~': 'f18',
+    '[33;1~': 'f19',
+    '[34;1~': 'f20',
+    '[11;2~': 'shift f1',
+    '[12;2~': 'shift f2',
+    '[13;2~': 'shift f3',
+    '[14;2~': 'shift f4',
+    '[15;2~': 'shift f5',
+    '[17;2~': 'shift f6',
+    '[18;2~': 'shift f7',
+    '[19;2~': 'shift f8',
+    '[20;2~': 'shift f9',
+    '[21;2~': 'shift f10',
+    '[23;2~': 'shift f11',
+    '[24;2~': 'shift f12',
+    '[25;2~': 'shift f13',
+    '[26;2~': 'shift f14',
+    '[28;2~': 'shift f15',
+    '[29;2~': 'shift f16',
+    '[31;2~': 'shift f17',
+    '[32;2~': 'shift f18',
+    '[33;2~': 'shift f19',
+    '[34;2~': 'shift f20',
+    '[11;3~': 'meta f1',
+    '[12;3~': 'meta f2',
+    '[13;3~': 'meta f3',
+    '[14;3~': 'meta f4',
+    '[15;3~': 'meta f5',
+    '[17;3~': 'meta f6',
+    '[18;3~': 'meta f7',
+    '[19;3~': 'meta f8',
+    '[20;3~': 'meta f9',
+    '[21;3~': 'meta f10',
+    '[23;3~': 'meta f11',
+    '[24;3~': 'meta f12',
+    '[25;3~': 'meta f13',
+    '[26;3~': 'meta f14',
+    '[28;3~': 'meta f15',
+    '[29;3~': 'meta f16',
+    '[31;3~': 'meta f17',
+    '[32;3~': 'meta f18',
+    '[33;3~': 'meta f19',
+    '[34;3~': 'meta f20',
+    '[11;4~': 'shift meta f1',
+    '[12;4~': 'shift meta f2',
+    '[13;4~': 'shift meta f3',
+    '[14;4~': 'shift meta f4',
+    '[15;4~': 'shift meta f5',
+    '[17;4~': 'shift meta f6',
+    '[18;4~': 'shift meta f7',
+    '[19;4~': 'shift meta f8',
+    '[20;4~': 'shift meta f9',
+    '[21;4~': 'shift meta f10',
+    '[23;4~': 'shift meta f11',
+    '[24;4~': 'shift meta f12',
+    '[25;4~': 'shift meta f13',
+    '[26;4~': 'shift meta f14',
+    '[28;4~': 'shift meta f15',
+    '[29;4~': 'shift meta f16',
+    '[31;4~': 'shift meta f17',
+    '[32;4~': 'shift meta f18',
+    '[33;4~': 'shift meta f19',
+    '[34;4~': 'shift meta f20',
+    '[11;5~': 'ctrl f1',
+    '[12;5~': 'ctrl f2',
+    '[13;5~': 'ctrl f3',
+    '[14;5~': 'ctrl f4',
+    '[15;5~': 'ctrl f5',
+    '[17;5~': 'ctrl f6',
+    '[18;5~': 'ctrl f7',
+    '[19;5~': 'ctrl f8',
+    '[20;5~': 'ctrl f9',
+    '[21;5~': 'ctrl f10',
+    '[23;5~': 'ctrl f11',
+    '[24;5~': 'ctrl f12',
+    '[25;5~': 'ctrl f13',
+    '[26;5~': 'ctrl f14',
+    '[28;5~': 'ctrl f15',
+    '[29;5~': 'ctrl f16',
+    '[31;5~': 'ctrl f17',
+    '[32;5~': 'ctrl f18',
+    '[33;5~': 'ctrl f19',
+    '[34;5~': 'ctrl f20',
+    '[11;6~': 'shift ctrl f1',
+    '[12;6~': 'shift ctrl f2',
+    '[13;6~': 'shift ctrl f3',
+    '[14;6~': 'shift ctrl f4',
+    '[15;6~': 'shift ctrl f5',
+    '[17;6~': 'shift ctrl f6',
+    '[18;6~': 'shift ctrl f7',
+    '[19;6~': 'shift ctrl f8',
+    '[20;6~': 'shift ctrl f9',
+    '[21;6~': 'shift ctrl f10',
+    '[23;6~': 'shift ctrl f11',
+    '[24;6~': 'shift ctrl f12',
+    '[25;6~': 'shift ctrl f13',
+    '[26;6~': 'shift ctrl f14',
+    '[28;6~': 'shift ctrl f15',
+    '[29;6~': 'shift ctrl f16',
+    '[31;6~': 'shift ctrl f17',
+    '[32;6~': 'shift ctrl f18',
+    '[33;6~': 'shift ctrl f19',
+    '[34;6~': 'shift ctrl f20',
+    '[11;7~': 'meta ctrl f1',
+    '[12;7~': 'meta ctrl f2',
+    '[13;7~': 'meta ctrl f3',
+    '[14;7~': 'meta ctrl f4',
+    '[15;7~': 'meta ctrl f5',
+    '[17;7~': 'meta ctrl f6',
+    '[18;7~': 'meta ctrl f7',
+    '[19;7~': 'meta ctrl f8',
+    '[20;7~': 'meta ctrl f9',
+    '[21;7~': 'meta ctrl f10',
+    '[23;7~': 'meta ctrl f11',
+    '[24;7~': 'meta ctrl f12',
+    '[25;7~': 'meta ctrl f13',
+    '[26;7~': 'meta ctrl f14',
+    '[28;7~': 'meta ctrl f15',
+    '[29;7~': 'meta ctrl f16',
+    '[31;7~': 'meta ctrl f17',
+    '[32;7~': 'meta ctrl f18',
+    '[33;7~': 'meta ctrl f19',
+    '[34;7~': 'meta ctrl f20',
+    '[11;8~': 'shift meta ctrl f1',
+    '[12;8~': 'shift meta ctrl f2',
+    '[13;8~': 'shift meta ctrl f3',
+    '[14;8~': 'shift meta ctrl f4',
+    '[15;8~': 'shift meta ctrl f5',
+    '[17;8~': 'shift meta ctrl f6',
+    '[18;8~': 'shift meta ctrl f7',
+    '[19;8~': 'shift meta ctrl f8',
+    '[20;8~': 'shift meta ctrl f9',
+    '[21;8~': 'shift meta ctrl f10',
+    '[23;8~': 'shift meta ctrl f11',
+    '[24;8~': 'shift meta ctrl f12',
+    '[25;8~': 'shift meta ctrl f13',
+    '[26;8~': 'shift meta ctrl f14',
+    '[28;8~': 'shift meta ctrl f15',
+    '[29;8~': 'shift meta ctrl f16',
+    '[31;8~': 'shift meta ctrl f17',
+    '[32;8~': 'shift meta ctrl f18',
+    '[33;8~': 'shift meta ctrl f19',
+    '[34;8~': 'shift meta ctrl f20',
+}
+# -*- coding: utf-8 -*-
+import curses
+
+colours = {
+    'default':        (-1,                    0),
+    'black':          (curses.COLOR_BLACK,    0),
+    'dark red':       (curses.COLOR_RED,      0),
+    'dark green':     (curses.COLOR_GREEN,    0),
+    'brown':          (curses.COLOR_YELLOW,   0),
+    'dark blue':      (curses.COLOR_BLUE,     0),
+    'dark magenta':   (curses.COLOR_MAGENTA,  0),
+    'dark cyan':      (curses.COLOR_CYAN,     0),
+    'light gray':     (curses.COLOR_WHITE,    0),
+    'dark gray':      (curses.COLOR_BLACK,    1),
+    'light red':      (curses.COLOR_RED,      1),
+    'light green':    (curses.COLOR_GREEN,    1),
+    'yellow':         (curses.COLOR_YELLOW,   1),
+    'light blue':     (curses.COLOR_BLUE,     1),
+    'light magenta':  (curses.COLOR_MAGENTA,  1),
+    'light cyan':     (curses.COLOR_CYAN,     1),
+    'white':          (curses.COLOR_WHITE,    1),
+    }
+                                                                    
+
+class ColorScheme(object):
+    color_names = [
+	    "black", "red", "green", "yellow", 
+		"blue", "magenta", "cyan", "white"]
+	# index == curses color value
+    colors = dict([(c, i) for i, c in enumerate(color_names)])
+    registered = {}
+
+
+    def __init__(self, foreground, background):
+        key = (foreground, background)
+        found = self.registered.get(key, None)
+        if not found:
+            self.num = len(self.registered)
+            curses.init_pair(1, self.colors[foreground], self.colors[background])
+            self.registered[key] = selfnum
+        
+        
+    def color_pair(self):
+        return curses.color_pair(self.num)
+
+
+from tobject import TApplication, TWidget, TLabel, TPoint, TSize
+
+
+app = TApplication()
+app.desktop().show()
+win = TWidget()
+win.move(TPoint(10, 10))
+win.resize(TSize(60, 15))
+l = TLabel(win)
+l.setText("Hello, world")
+win.show()
+
+while app.event_loop.processEvent():
+    pass
+app.desktop().console.getchar(wait=True)
+app.desktop().console.close()
+# -*- coding: utf-8 -*-
+from console import Console, debug
+
+
+class TEvent(object):
+    pass
+
+
+class TKeyEvent(TEvent):
+    pass
+
+
+class TPaintEvent(TEvent):
+
+    def __init__(self, rect=None):
+        super(TPaintEvent, self).__init__()
+        self.rect = rect
+
+
+class TCloseEvent(TEvent):
+    pass
+
+
+class THideEvent(TEvent):
+    pass
+
+
+class TShowEvent(TEvent):
+    pass
+
+
+class TResizeEvent(TEvent):
+    
+    def __init__(self, size):
+        super(TResizeEvent, self).__init__()
+        self.size = size
+
+
+class TEventLoop(object):
+    _instance = None
+
+    def __init__(self):
+        self.queue = []
+
+
+    @classmethod
+    def instance(cls):
+        if not cls._instance:
+            cls._instance = cls()
+        return cls._instance
+
+
+    def postEvent(self, obj, event):
+        self.queue.insert(0, (obj, event))
+
+
+    def processEvent(self):
+        if self.queue:
+            obj, event = self.queue.pop()
+            debug("processing event", event, "for", obj)
+            obj.event(event)
+            return True
+        return False
+
+
+
+class TObject(object):
+    
+    def __init__(self, parent=None):
+        self._children = set()
+        self._parent = parent
+        self._event_filters = set()
+        if parent:
+            parent._children.add(self)
+            
+            
+    def children(self):
+        return list(self._children)
+    
+    
+    def parent(self):
+        return self._parent
+    
+    
+    def installEventFilter(self, receiver_object):
+        self._event_filters.add(receiver_object)
+
+
+    def postEvent(self, event):
+        TEventLoop.instance().postEvent(self, event)
+
+
+    def event(self, event):
+        for receiver_object in self._event_filters:
+            if receiver_object.eventFilter(self, event):
+                return True
+        return False
+            
+    
+    def eventFilter(self, obj, event):
+        pass
+    
+
+    
+class TSize(object):
+    
+    def __init__(self, width, height):
+        self.width = width
+        self.height = height
+
+
+
+class TPoint(object):
+    
+    def __init__(self, x, y):
+        self.x = x
+        self.y = y
+
+
+        
+class TRect(object):
+    
+
+    def __init__(self, point, size):
+        self.topLeft = point
+        self.size = size
+
+
+
+
+
+class TApplication(TObject):
+
+    instance = None
+
+    
+    def __init__(self):
+        super(TApplication, self).__init__()
+        self.event_loop = TEventLoop.instance()
+        self._desktop = TDesktopWidget()
+        TApplication.instance = self
+
+        
+    def desktop(self):
+        return self._desktop
+
+
+    def postEvent(self, receiver_object, event):
+        self.event_loop.postEvent(receiver_object, event)
+    
+    
+    def sendEvent(self, receiver_object, event):
+        self.event_loop.sendEvent(receiver_object, event)
+
+    
+    def notify(self, receiver_object, event):
+        self.event_loop.notify(receiver_object, event)
+        
+        
+    def processEvent(self):
+        return self.event_loop.processEvent()
+    
+        
+    def run(self):
+        self._desktop.show()
+        while True:
+            self.processEvent()
+
+            
+            
+
+
+class TWidget(TObject):
+
+    
+    def __init__(self, parent=None):
+        super(TWidget, self).__init__(parent)
+        self._update_geom(TPoint(0,0), TSize(1,1))
+        self._visible = True
+        
+
+    def window(self):
+        parent = self
+        parent_parent = parent.parent()
+        while parent_parent:
+            parent = parent_parent
+            parent_parent = parent.parent()
+        return parent
+
+    
+    def close(self):
+        pass
+    
+        
+    def show(self):
+        self.setVisible(True)
+        
+
+    def hide(self):
+        self.setVisible(False)
+
+        
+    def setVisible(self, flag):
+        self._visible = flag
+        self.postEvent(TPaintEvent())
+        if flag:
+            self.postEvent(TShowEvent())
+        else:    
+            self.postEvent(THideEvent())
+        for child in self.children():
+            child.setVisible(flag)
+
+
+    def _update_geom(self, pos, size):
+        x1 = pos.x
+        y1 = pos.y
+        x2 = x1 + size.width
+        y2 = y1 + size.height
+        self._geom = (x1, y1, x2, y2)
+        self.postEvent(TResizeEvent(size))
+
+        
+    def size(self):
+        (x1, y1, x2, y2) = self._geom
+        width, height = x2 - y1, y2 - y1
+        return TSize(width, height)
+    
+
+    def resize(self, size):
+        (x1, y1, x2, y2) = self._geom
+        width, height = size.width, size.height
+        self._update_geom(TPoint(x1, y1), TSize(width, height))
+
+        
+    def pos(self):
+        (x1, y1, x2, y2) = self._geom
+        return TPoint(x1, y1)
+
+
+    def move(self, pos):
+        self._update_geom(pos, self.size())
+
+
+    def geometry(self):
+        return TRect(self.pos(), self.size())
+
+    
+    def setGeometry(self, pos, size):
+        self._update_geom(pos, size)
+
+    
+    def mapToParent(self, pos):
+        parent = self.parent()
+        if not parent:
+            return TPoint(pos.x, pos.y)
+        else:
+            ppos = parent.pos()
+            return TPoint(ppos.x + pos.x, ppos.y + pos.y)
+
+
+    def mapToGlobal(self, pos):
+        obj = self
+        while obj:
+            pos = obj.mapToParent(pos)
+            obj = obj.parent()
+        return pos
+
+    
+    def event(self, event):
+        if super(TWidget, self).event(event):
+            return True
+        if isinstance(event, TPaintEvent):
+            return self.paintEvent(event)
+        elif isinstance(event, TKeyEvent):
+            return self.keyPressevent(event)
+
+            
+    def keyPressEvent(self, event):
+        pass
+
+
+    def paintEvent(self, event):
+        painter = TPainter(self)
+        if self.parent():
+            painter.fillRect(self.geometry(), " ")
+        else:
+            rect = self.geometry()
+            painter.drawRect(rect)
+            pos = rect.topLeft
+            width, height = rect.size.width, rect.size.height
+            inner = TRect(TPoint(pos.x, pos.y), TSize(width-1, height-1))
+            painter.fillRect(inner, " ")
+
+    def resizeEvent(self, event):
+        pass
+
+    
+    def update(self, rect=None):
+        self.postEvent(TPaintEvent(rect))
+    
+        
+    def repaint(self, rect=None):
+        event = TPaintEvent(rect)
+        self.paintEvent(event)
+
+
+        
+class TPainter(object):
+
+
+    def __init__(self, obj=None):
+        self.stack = []
+        if obj:
+            self.stack.append(obj)
+        self.obj = obj
+        self.con = Console.instance
+
+
+    def begin(self, obj):
+        self.stack.append(obj)
+        self.obj = obj
+        
+
+    def end(self):
+        self.stack.pop()
+        if self.stack:
+            self.obj = self.stack[-1]
+        else:
+            self.obj = None
+            
+            
+    def drawRect(self, rect):
+        gpos = self.obj.mapToGlobal(TPoint(0, 0))
+        x1 = gpos.x + rect.topLeft.x
+        y1 = gpos.y + rect.topLeft.y
+        width = rect.size.width
+        height = rect.size.height
+        x2 = x1 + width - 1
+        y2 = y1 + height - 1
+        # topleft
+        self.con.text(x1, y1, "+")
+        # topright
+        self.con.text(x2, y1, "+")
+        # top line
+        self.con.text(x1 + 1, y1, "-" * (width-2))
+        # bottomleft
+        self.con.text(x1, y2, "+")
+        # bottomright
+        self.con.text(x2, y2, "+")
+        # bottom line
+        self.con.text(x1+1, y2, "-" * (width-2))
+        # side lines
+        for y in range(y1+1, y2):
+            self.con.text(x1, y, "|") # left
+            self.con.text(x2, y, "|") # right
+
+        
+    def drawText(self, pos, text):
+        gpos = self.obj.mapToGlobal(pos)
+        self.con.text(gpos.x, gpos.y, text)
+        
+
+
+    def fillRect(self, rect, char):
+        gpos = self.obj.mapToGlobal(TPoint(0, 0))
+        x1 = gpos.x + rect.topLeft.x
+        y1 = gpos.y + rect.topLeft.y
+        width = rect.size.width
+        height = rect.size.height
+        x2 = x1 + width - 1
+        y2 = y1 + height - 1
+        for y in range(y1, y2):
+            self.con.text(x1, y, char*width)
+    
+
+
+class TDesktopWidget(TWidget):
+
+
+    def __init__(self):
+        super(TDesktopWidget, self).__init__()
+        self._update_geom(TPoint(1, 1), TSize(78, 23))
+        self.console = Console()
+
+        
+    def paintEvent(self, event):
+        painter = TPainter(self)
+        painter.fillRect(self.geometry(), ".")
+
+        
+class TLabel(TWidget):
+
+
+    def __init__(self, parent=None, text=None):
+        super(TLabel, self).__init__(parent)
+        self.setText(text)
+        if text:
+            self.resize(TSize(min(70, len(text)), 1))
+
+        
+    def setText(self, text):
+        self._text = text
+        self.update()
+
+        
+    def text(self):
+        return self._text
+    
+
+    def paintEvent(self, event):
+        painter = TPainter(self)
+        painter.drawText(self.pos(), self.text())