Update Emitter: All event handlers now have exactly one argument: object inherited from Event class, which carries any data.
authorRadek Brich <radek.brich@devl.cz>
Sat, 29 Dec 2012 12:16:06 +0100
changeset 41 37b7dfc3eae6
parent 40 5faa38c10b67
child 42 0224ce40792f
Update Emitter: All event handlers now have exactly one argument: object inherited from Event class, which carries any data.
demo_checkbox.py
demo_editor.py
demo_input.py
demo_layout.py
demo_menu.py
demo_tableview.py
demo_treeview.py
tuikit/button.py
tuikit/checkbox.py
tuikit/combobox.py
tuikit/common.py
tuikit/container.py
tuikit/driver.py
tuikit/driver_curses.py
tuikit/driver_pygame.py
tuikit/editbox.py
tuikit/editfield.py
tuikit/emitter.py
tuikit/label.py
tuikit/layout.py
tuikit/menu.py
tuikit/menubar.py
tuikit/scrollbar.py
tuikit/scrollview.py
tuikit/tableview.py
tuikit/textedit.py
tuikit/treeview.py
tuikit/widget.py
tuikit/window.py
--- a/demo_checkbox.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/demo_checkbox.py	Sat Dec 29 12:16:06 2012 +0100
@@ -10,20 +10,20 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.on_keypress)
+        self.top.connect('keypress', self.on_top_keypress)
 
         vert = VerticalLayout(homogeneous=False)
         self.top.layout(vert)
-        
+
         combo = ComboBox(items=['abc', 'xyz'])
         self.top.add(combo)
-        
+
         for i in range(10):
             cbox = Checkbox('checkbox ' + str(i))
             self.top.add(cbox)
-        
-    def on_keypress(self, keyname, char):
-        if keyname == 'escape' or char == 'q':
+
+    def on_top_keypress(self, ev):
+        if ev.keyname == 'escape' or ev.char == 'q':
             self.terminate()
 
 
--- a/demo_editor.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/demo_editor.py	Sat Dec 29 12:16:06 2012 +0100
@@ -17,7 +17,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.on_keypress)
+        self.top.connect('keypress', self.on_top_keypress)
 
         #edit = EditField(50, 'DlouhyTest12')
         #self.top.add(edit)
@@ -48,13 +48,9 @@
         self.button.label = 'YES'
 
 
-    def on_keypress(self, keyname, char):
-        if keyname == 'escape':
+    def on_top_keypress(self, ev):
+        if ev.keyname == 'escape':
             self.terminate()
-        if keyname == 'f1':
-            self.textedit.settext('%s' % self.top.focuschild)
-            self.textedit.redraw()
-            
 
 
 if __name__ == '__main__':
--- a/demo_input.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/demo_input.py	Sat Dec 29 12:16:06 2012 +0100
@@ -10,7 +10,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.globalkeypress)
+        self.top.connect('keypress', self.on_top_keypress)
 
         self.text = ''
         textedit = TextEdit(100, 40, self.text)
@@ -19,10 +19,10 @@
         self.textedit = textedit
 
 
-    def globalkeypress(self, keyname, char):
-        if char == 'q':
+    def on_top_keypress(self, ev):
+        if ev.char == 'q':
             self.terminate()
-        self.text += 'keyname: %s  char: %s\n' % (keyname, char)
+        self.text += 'keyname: %(keyname)s  char: %(char)s\n' % ev
         self.textedit.settext(self.text)
         self.textedit.scrolltoend()
 
--- a/demo_layout.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/demo_layout.py	Sat Dec 29 12:16:06 2012 +0100
@@ -10,13 +10,13 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.globalkeypress)
+        self.top.connect('keypress', self.on_top_keypress)
 
         #self.top.borders = (1,1,1,1)
 
         vert = VerticalLayout(homogeneous=False)
         self.top.layout(vert)
-        
+
         self.buildrow()
         self.buildrow(expand=True)
         self.buildrow(expand=True, fill=True)
@@ -24,7 +24,7 @@
         self.buildrow(homogeneous=True, fill=True)
         self.buildrow(homogeneous=True, fill=True, spacing=1)
         self.buildrow(homogeneous=True, fill=True, spacing=2)
-        
+
     def buildrow(self, homogeneous=False, spacing=0, expand=False, fill=False):
         horz = HorizontalLayout(homogeneous=homogeneous, spacing=spacing)
         hbox1 = Container()
@@ -34,9 +34,9 @@
         for i in range(5):
             btn = Button('Btn' + str(i) * i * i)
             hbox1.add(btn, expand=expand, fill=fill)
-        
-    def globalkeypress(self, keyname, char):
-        if keyname == 'escape' or char == 'q':
+
+    def on_top_keypress(self, ev):
+        if ev.keyname == 'escape' or ev.char == 'q':
             self.terminate()
 
 
--- a/demo_menu.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/demo_menu.py	Sat Dec 29 12:16:06 2012 +0100
@@ -10,7 +10,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.globalkeypress)
+        self.top.connect('keypress', self.on_top_keypress)
 
         menubar = MenuBar()
         self.top.add(menubar)
@@ -64,8 +64,8 @@
         #win.add(subwin)
 
 
-    def globalkeypress(self, keyname, char):
-        if keyname == 'escape':
+    def on_top_keypress(self, ev):
+        if ev.keyname == 'escape':
             self.terminate()
 
 
--- a/demo_tableview.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/demo_tableview.py	Sat Dec 29 12:16:06 2012 +0100
@@ -12,7 +12,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.globalkeypress)
+        self.top.connect('keypress', self.on_top_keypress)
 
         data = []
         for y in range(100):
@@ -21,19 +21,19 @@
                 row.append('r{}:c{}'.format(y, x))
             data.append(row)
         model = TableModel(data)
-        
+
         view = TableView(model)
         view.addcolumn(header=True, expand=False, sizereq=5)
         for x in range(10):
             view.addcolumn(title='head'+str(x))
-        
+
         self.top.add(view, expand=True, fill=True)
 
         vert = VerticalLayout()
         self.top.layout(vert)
 
-    def globalkeypress(self, keyname, char):
-        if keyname == 'escape':
+    def on_top_keypress(self, ev):
+        if ev.keyname == 'escape':
             self.terminate()
 
 
--- a/demo_treeview.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/demo_treeview.py	Sat Dec 29 12:16:06 2012 +0100
@@ -10,7 +10,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.globalkeypress)
+        self.top.connect('keypress', self.on_top_keypress)
 
         model = TreeModel()
         model.add('/',  ['a', 'b'])
@@ -32,8 +32,8 @@
         vert = VerticalLayout()
         self.top.layout(vert)
 
-    def globalkeypress(self, eo, keyname, char):
-        if keyname == 'escape':
+    def on_top_keypress(self, ev):
+        if ev.keyname == 'escape':
             self.terminate()
 
 
--- a/tuikit/button.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/button.py	Sat Dec 29 12:16:06 2012 +0100
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from tuikit.widget import Widget
+from tuikit.emitter import Event
 
 
 class Button(Widget):
@@ -32,29 +33,29 @@
         self.sizereq.w = w
         self.sizereq.h = h
 
-        self.add_events('click')
+        self.add_events('click', Event)
 
 
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
         pad = self.width - len(self.label) - len(self.prefix) - len(self.suffix)
         lpad, rpad = self._divide_padding(pad)
-        screen.pushcolor(self.getcolor())
+        ev.driver.pushcolor(self.getcolor())
         # prefix
-        screen.puts(x, y, self.prefix)
+        ev.driver.puts(ev.x, ev.y, self.prefix)
         pos = len(self.prefix)
         # left pad
-        screen.puts(x + pos, y, ' ' * lpad)
+        ev.driver.puts(ev.x + pos, ev.y, ' ' * lpad)
         pos += lpad
         # label
-        screen.puts(x + pos, y, self.label)
+        ev.driver.puts(ev.x + pos, ev.y, self.label)
         pos += len(self.label)
         # right pad
-        screen.puts(x + pos, y, ' ' * rpad)
+        ev.driver.puts(ev.x + pos, ev.y, ' ' * rpad)
         pos += rpad
         # suffix
-        screen.puts(x + pos, y, self.suffix)
-        screen.popcolor()
+        ev.driver.puts(ev.x + pos, ev.y, self.suffix)
+        ev.driver.popcolor()
 
     def _handle_mousedown(self, ev):
         super()._handle_mousedown(ev)
@@ -69,9 +70,9 @@
         if self.enclose(ev.px, ev.py):
             self.emit('click')
 
-    def _handle_keypress(self, keyname, char):
-        super()._handle_keypress(keyname, char)
-        if keyname == 'enter':
+    def _handle_keypress(self, ev):
+        super()._handle_keypress(ev)
+        if ev.keyname == 'enter':
             self.emit('click')
 
     def getcolor(self):
--- a/tuikit/checkbox.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/checkbox.py	Sat Dec 29 12:16:06 2012 +0100
@@ -18,8 +18,8 @@
         self.bg = 'normal'
         self.bghi = 'active'
 
-    def _handle_click(self):
-        super()._handle_click()
+    def _handle_click(self, ev):
+        super()._handle_click(ev)
         if self.checked:
             self.checked = False
             self.prefix = '[ ] '
--- a/tuikit/combobox.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/combobox.py	Sat Dec 29 12:16:06 2012 +0100
@@ -28,8 +28,11 @@
         self._menu = Menu(items)
         self._menu.hide()
         self._menu.allow_layout = False
-        #self.top.add(self._menu)
 
-    def _on_btn_click(self):
-        pass
+    def _set_top(self, value):
+        super()._set_top(value)
+        self.top.add(self._menu)
 
+    def _on_btn_click(self, ev):
+        self._menu.show()
+
--- a/tuikit/common.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/common.py	Sat Dec 29 12:16:06 2012 +0100
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-from tuikit.emitter import Emitter
+from tuikit.emitter import Event, Emitter
 
 
 class Coords:
@@ -41,7 +41,7 @@
     def __init__(self, w=None, h=None):
         self._w = w
         self._h = h
-        self.add_events('change')
+        self.add_events('change', Event)
 
     @property
     def w(self):
@@ -197,28 +197,3 @@
     LTEE = '├'
     RTEE = '┤'
 
-
-class MouseEvent:
-    def __init__(self, x=0, y=0, button=0):
-        self.x = x   # global coordinates
-        self.y = y
-        self.wx = x  # local widget coordinates
-        self.wy = y
-        self.px = 0  # parent coordinates
-        self.py = 0
-        self.button = button
-
-
-    def childevent(self, child):
-        ev = MouseEvent(self.x, self.y, self.button)
-        # original local coordinates are new parent coordinates
-        ev.px = self.wx
-        ev.py = self.wy
-        # update local coordinates
-        ev.wx = self.wx - child.x
-        ev.wy = self.wy - child.y
-        return ev
-
-    def __repr__(self):
-        return 'MouseEvent(x={0.x},y={0.y},button={0.button})'.format(self)
-
--- a/tuikit/container.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/container.py	Sat Dec 29 12:16:06 2012 +0100
@@ -43,7 +43,7 @@
         '''Add widget into this container.'''
         self.children.append(widget)
         widget.parent = self
-        widget.settop(self.top)
+        widget.top = self.top
         widget.hints.update(kwargs)
         if self.focuschild is None and widget.can_focus():
             self.focuschild = widget
@@ -55,11 +55,10 @@
         layout.container = self
 
 
-    def settop(self, top):
-        '''Set top widget.'''
-        self.top = top
+    def _set_top(self, value):
+        self._top = value
         for child in self.children:
-            child.settop(top)
+            child.top = value
 
 
     def focus_next(self):
@@ -92,17 +91,17 @@
                 return self.trap_focus or not cycled
             idx_new += 1
 
-    def _handle_keypress(self, keyname, char):
-        super()._handle_keypress(keyname, char)
+    def _handle_keypress(self, ev):
+        super()._handle_keypress(ev)
         if self.focuschild is not None:
-            handled = self.focuschild.emit('keypress', keyname, char)
+            handled = self.focuschild.emit('keypress', ev)
             if handled:
                 return True
-        if keyname == 'tab':
+        if ev.keyname == 'tab':
             return self.focus_next()
 
-    def _handle_resize(self):
-        super()._handle_resize()
+    def _handle_resize(self, ev):
+        super()._handle_resize(ev)
         for child in self.children:
             child.emit('resize')
 
--- a/tuikit/driver.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/driver.py	Sat Dec 29 12:16:06 2012 +0100
@@ -5,23 +5,23 @@
 
 
 class Driver:
-    
+
     '''Abstract driver interface. Use as base for drivers.'''
-    
+
     def __init__(self):
         '''Initialize instance attributes.'''
+        #: Screen size.
         self.size = Size()
-        '''Screen size.'''
+        #: Clipping region stack.
         self.clipstack = ClipStack()
-        '''Clipping region stack.'''
+        #: Unicode graphics characters.
         self.unigraph = UnicodeGraphics()
-        '''Unicode graphics characters.'''
+        #: Stack of color prefixes.
         self.colorprefix = []
-        '''Stack of color prefixes.'''
 
 
     ## drawing ##
-    
+
     def puts(self, x, y, s):
         '''Output string of characters.'''
         for c in s:
@@ -59,7 +59,7 @@
 
 
     ## colors ##
-    
+
     def pushcolorprefix(self, name):
         self.colorprefix.append(name)
 
--- a/tuikit/driver_curses.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/driver_curses.py	Sat Dec 29 12:16:06 2012 +0100
@@ -6,7 +6,6 @@
 import logging
 
 from tuikit.driver import Driver
-from tuikit.common import MouseEvent
 
 
 class DriverCurses(Driver):
@@ -282,28 +281,22 @@
         except curses.error:
             return []
 
-        ev = MouseEvent(x, y)
-
         out = []
 
         if bstate & curses.REPORT_MOUSE_POSITION:
-            out += [('mousemove', ev)]
+            out += [('mousemove', x, y)]
 
         if bstate & curses.BUTTON1_PRESSED:
-            ev.button = 1
-            out += [('mousedown', ev)]
+            out += [('mousedown', x, y, 1)]
 
         if bstate & curses.BUTTON3_PRESSED:
-            ev.button = 3
-            out += [('mousedown', ev)]
+            out += [('mousedown', x, y, 3)]
 
         if bstate & curses.BUTTON1_RELEASED:
-            ev.button = 1
-            out += [('mouseup', ev)]
+            out += [('mouseup', x, y, 1)]
 
         if bstate & curses.BUTTON3_RELEASED:
-            ev.button = 3
-            out += [('mouseup', ev)]
+            out += [('mouseup', x, y, 3)]
 
         return out
 
@@ -333,7 +326,7 @@
         consumed.append(c)
 
         while True:
-         #   self.log.debug('c=%s len=%s', c, len(codes))
+            #self.log.debug('c=%s len=%s', c, len(codes))
             for code in codes:
                 if c == code[len(consumed)-1]:
                     if len(code) - 1 == len(consumed):
@@ -341,7 +334,7 @@
                     else:
                         matchingcodes += [code]
 
-         #   self.log.debug('matching=%s', len(matchingcodes))
+            #self.log.debug('matching=%s', len(matchingcodes))
 
             # match found, or no matching code found -> stop
             if len(matchingcodes) == 0:
@@ -385,25 +378,23 @@
         x = self.inputqueue_get_wait() - 0x21
         y = self.inputqueue_get_wait() - 0x21
 
-        ev = MouseEvent(x, y)
         out = []
 
         if t in (0x20, 0x21, 0x22): # button press
             btn = t - 0x1f
-            ev.button = btn
             if not btn in self.mbtnstack:
                 self.mbtnstack.append(btn)
-                out += [('mousedown', ev)]
+                out += [('mousedown', x, y, btn)]
             else:
-                out += [('mousemove', ev)]
+                out += [('mousemove', x, y, btn)]
 
         elif t == 0x23: # button release
-            ev.button = self.mbtnstack.pop()
-            out += [('mouseup', ev)]
+            btn = self.mbtnstack.pop()
+            out += [('mouseup', x, y, btn)]
 
         elif t in (0x60, 0x61): # wheel up, down
-            ev.button = 4 + t - 0x60
-            out += [('mousewheel', ev)]
+            btn = 4 + t - 0x60
+            out += [('mousewheel', x, y, btn)]
 
         else:
             raise Exception('Unknown mouse event: %x' % t)
--- a/tuikit/driver_pygame.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/driver_pygame.py	Sat Dec 29 12:16:06 2012 +0100
@@ -5,17 +5,17 @@
 import logging
 
 from tuikit.driver import Driver
-from tuikit.common import Coords, Size, MouseEvent
+from tuikit.common import Coords, Size
 
 
 class TerminalScreen:
-    
+
     '''Provide character-level output to screen SDL surface.
-    
+
     This is performance bottleneck and should be optimized as much as possible.
-    
+
     '''
-    
+
     def __init__(self):
         fontselect = 'dejavusansmono,liberationmono,freemono'
         self.font = pygame.font.SysFont(fontselect, 14)
@@ -25,13 +25,13 @@
         height = self.font.get_height()
         self.charsize = Size(advance, height)
         self.ascent = self.font.get_ascent()
-        
+
         # choose self.render() implementation
         if hasattr(self.font, 'render_glyph'):
             self.render_char = self.render_glyph
         else:
             self.render_char = self.render_noglyph
-            
+
         self.chars = None
         self.attrs = None
         self.default_attr = None
@@ -39,10 +39,10 @@
 
     def set_default_attr(self, fg, bg, flags):
         self.default_attr = (fg, bg, flags)
-    
+
     def set_attr(self, fg, bg, flags):
         self.current_attr = (fg, bg, flags)
-    
+
     def reset_attr(self):
         self.current_attr = self.default_attr
 
@@ -51,18 +51,18 @@
         numchars = w * h
         self.chars = [' '] * numchars
         self.attrs = [self.default_attr] * numchars
-    
+
     def clear(self):
         numchars = self.w * self.h
         for pos in range(numchars):
             self.chars[pos] = ' '
             self.attrs[pos] = self.default_attr
-    
+
     def putch(self, x, y, c):
         pos = y * self.w + x
         self.chars[pos] = c
         self.attrs[pos] = self.current_attr
-    
+
     def update(self, surface):
         pos = 0
         for y in range(self.h):
@@ -77,18 +77,18 @@
 
     def render_glyph(self, screen, x, y, c, fgcolor, bgcolor, flags):
         '''Render using render_glyph and metrics.
-        
+
         This is the correct way, but the output seems same as of render_noglyph
         and this implementation requires patching PyGame to work.
-        
+
         This implements render() method. See render_noglyph for other implementation.
-        
-        '''                
+
+        '''
         # draw background
         dest = Coords(x * self.charsize.w, y * self.charsize.h)
         if bgcolor != self.default_attr[1]:
             screen.fill(bgcolor, pygame.Rect(dest.x, dest.y, self.charsize.w, self.charsize.h))
-        
+
         if not c:
             return
 
@@ -103,7 +103,7 @@
         metrics = font.metrics(c)[0]
         minx, maxx, miny, maxy, advance = metrics
         height, ascent = self.charsize.h, self.ascent
-        
+
         # clips origin and area of rendered character according to metrics
         startx, starty = 0, 0
         if minx < 0:
@@ -116,24 +116,24 @@
             miny = ascent - height
         if maxx > advance:
             maxx = advance
-        
+
         # draw character
         dest.x += minx
         dest.y += ascent - maxy
         area = pygame.Rect(startx, starty, maxx - minx, maxy - miny)
         screen.blit(surface, tuple(dest), area)
-    
+
     def render_noglyph(self, screen, x, y, c, fgcolor, bgcolor, attr):
         '''Render character using normal text rendering.
-        
+
         This implements render() method. See render_glyph for other implementation.
-        
+
         '''
         if attr == 'bold':
             font = self.font_bold
         else:
             font = self.font
-               
+
         # render character, get metrics
         surface = font.render(c, True, fgcolor, bgcolor)
         metrics = font.metrics(c)[0]
@@ -141,20 +141,20 @@
         startx = 0
         if minx < 0:
             startx = abs(minx)
-        
+
         # draw background
         dest = Coords(x * self.charsize.w, y * self.charsize.h)
         screen.fill(bgcolor, pygame.Rect(dest.x, dest.y, self.charsize.w, self.charsize.h))
-        
+
         # draw character
         area = pygame.Rect(startx, 0, self.charsize.w, self.charsize.h)
         screen.blit(surface, tuple(dest), area)
 
 
 class DriverPygame(Driver):
-    
+
     '''PyGame driver class.'''
-    
+
     keymap = {
         pygame.K_ESCAPE     : 'escape',
         pygame.K_TAB        : 'tab',
@@ -186,7 +186,7 @@
         pygame.K_SCROLLOCK  : 'scrollock',
         pygame.K_PAUSE      : 'pause',
         }
-    
+
     colormap = {
         'black'         : (0,0,0),
         'blue'          : (23,23,178),
@@ -205,7 +205,7 @@
         'intenseyellow' : (255,255,84),
         'intensewhite'  : (255,255,255),
         }
-    
+
     def __init__(self):
         '''Initialize instance attributes'''
         Driver.__init__(self)
@@ -234,21 +234,22 @@
         self.term.reset(self.size.w, self.size.h)
 
     ## input ##
-    
+
     def getevents(self, timeout=None):
         '''Process input, return list of events.'''
         events = []
         for ev in pygame.event.get():
             # mouse
             if ev.type == pygame.MOUSEMOTION:
-                evdata = MouseEvent(ev.pos[0] // self.charsize.w, ev.pos[1] // self.charsize.h)
-                events.append(('mousemove', evdata))
+                mx = ev.pos[0] // self.charsize.w
+                my = ev.pos[1] // self.charsize.h
+                events.append(('mousemove', mx, my))
             elif ev.type in (pygame.MOUSEBUTTONDOWN, pygame.MOUSEBUTTONUP):
-                evdata = MouseEvent(ev.pos[0] // self.charsize.w, ev.pos[1] // self.charsize.h)
-                evdata.button = ev.button
+                mx = ev.pos[0] // self.charsize.w
+                my = ev.pos[1] // self.charsize.h
                 evname = {pygame.MOUSEBUTTONDOWN: 'mousedown', pygame.MOUSEBUTTONUP: 'mouseup'}
-                events.append((evname[ev.type], evdata))
-            
+                events.append((evname[ev.type], mx, my, ev.button))
+
             # keyboard
             elif ev.type == pygame.KEYDOWN:
                 keypress = self.keypress_from_pygame_event(ev)
@@ -263,7 +264,7 @@
             elif ev.type == pygame.KEYUP:
                 if ev.key == self.last_key:
                     pygame.time.set_timer(pygame.USEREVENT, 0)
-            
+
             # window
             elif ev.type == pygame.VIDEORESIZE:
                 neww, newh = ev.w // self.charsize.w, ev.h // self.charsize.h
@@ -273,7 +274,7 @@
                     events.append(('resize',))
             elif ev.type == pygame.QUIT:
                 events.append(('quit',))
-            
+
             else:
                 self.log.warning('Unknown PyGame event: %r', ev.type)
         return events
@@ -289,12 +290,12 @@
         return keypress
 
     ## drawing ##
-    
+
     def erase(self):
         '''Clear screen.'''
         self.term.clear()
         self.screen.fill(self.term.default_attr[1])
-    
+
     def putch(self, x, y, c):
         if not self.clipstack.test(x, y):
             return
@@ -307,13 +308,13 @@
 
 
     ## colors ##
-    
+
     def _parsecolor(self, name, attr=None):
         name = name.lower().strip()
         if attr == 'bold':
             name = 'intense' + name
         return self.colormap[name]
-    
+
     def _parseattrs(self, attrs):
         res = ''
         for a in attrs:
@@ -324,10 +325,10 @@
 
     def setcolor(self, name, desc):
         '''Define color name.
-        
+
         name - name of color (e.g. 'normal', 'active')
         desc - color description - foreground, background, attributes (e.g. 'black on white, bold')
-        
+
         '''
         parts = desc.split(',')
         fg, bg = parts[0].split(' on ')
@@ -347,7 +348,7 @@
         col = self.colors[name]
         self.current_color = col
         self.colorstack.append(col)
-    
+
     def popcolor(self):
         '''Remove color from top of stack and use new top color for following output.'''
         self.colorstack.pop()
--- a/tuikit/editbox.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/editbox.py	Sat Dec 29 12:16:06 2012 +0100
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from tuikit.widget import Widget
+from tuikit.emitter import Event
 
 
 class EditBox(Widget):
@@ -20,13 +21,15 @@
         self.sline = 0
         self.spos = 0
 
-        self.add_events('scroll', 'areasize')
+        self.add_events(
+            'scroll', Event,
+            'areasize', Event)
 
         self.set_text(text)
 
 
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
         for j in range(self.height):
             if self.yofs + j >= len(self.lines):
                 break
@@ -35,52 +38,52 @@
                 #line += ' ' * (self.width - len(line))
             #else:
                 #line = line[:self.width]
-            screen.puts(x, y + j, line)
+            ev.driver.puts(ev.x, ev.y + j, line)
 
         self.cursor = (self.get_cpos() - self.xofs, self.cline - self.yofs)
 
 
-    def _handle_keypress(self, keyname, char):
-        super()._handle_keypress(keyname, char)
-        if keyname:
-            if keyname == 'left':
+    def _handle_keypress(self, ev):
+        super()._handle_keypress(ev)
+        if ev.keyname:
+            if ev.keyname == 'left':
                 self.move_left()
 
-            if keyname == 'right':
+            if ev.keyname == 'right':
                 self.move_right()
 
-            if keyname == 'home':
+            if ev.keyname == 'home':
                 self.move_home()
 
-            if keyname == 'end':
+            if ev.keyname == 'end':
                 self.move_end()
 
-            if keyname == 'up':
+            if ev.keyname == 'up':
                 self.move_up()
 
-            if keyname == 'down':
+            if ev.keyname == 'down':
                 self.move_down()
 
-            if keyname == 'pageup':
+            if ev.keyname == 'pageup':
                 self.move_pageup()
 
-            if keyname == 'pagedown':
+            if ev.keyname == 'pagedown':
                 self.move_pagedown()
 
-            if keyname == 'backspace':
+            if ev.keyname == 'backspace':
                 if self.cline > 0 or self.cpos > 0:
                     self.move_left()
                     self.del_char()
 
-            if keyname == 'delete':
+            if ev.keyname == 'delete':
                 self.del_char()
 
-            if keyname == 'enter':
+            if ev.keyname == 'enter':
                 self.add_newline()
                 self.move_right()
 
-        if char:
-            self.add_char(char)
+        if ev.char:
+            self.add_char(ev.char)
             self.move_right()
 
         self.redraw()
@@ -121,8 +124,9 @@
             yofs = len(self.lines) - self.height
         if yofs < 0:
             yofs = 0
-        self.yofs = yofs
-        self.emit('scroll')
+        if self.yofs != yofs:
+            self.yofs = yofs
+            self.emit('scroll')
 
 
     def move_left(self):
--- a/tuikit/editfield.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/editfield.py	Sat Dec 29 12:16:06 2012 +0100
@@ -22,56 +22,56 @@
         self.pos = len(value)      # position of cursor in value
         self.ofs = 0      # position of value beginning on screen
 
-    def _handle_resize(self):
-        super()._handle_resize()
+    def _handle_resize(self, ev):
+        super()._handle_resize(ev)
         self.tw = self.width - 2
 
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
-        screen.pushcolor('normal')
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
+        ev.driver.pushcolor('normal')
         # draw value
         val = self.value + ' ' * self.tw         # add spaces to fill rest of field
         val = val[self.ofs : self.ofs + self.tw]  # cut value - begin from ofs, limit to tw chars
-        screen.puts(x + 1, y, val.encode(self.code))
+        ev.driver.puts(ev.x + 1, ev.y, val.encode(self.code))
 
         # draw arrows if content overflows
         c = ' '
         if self.ofs > 0:
             c = '<'
-        screen.putch(x, y, c)
+        ev.driver.putch(ev.x, ev.y, c)
 
         c = ' '
         if len(self.value[self.ofs:]) > self.tw:
             c = '>'
-        screen.putch(x + self.width-1, y, c)
+        ev.driver.putch(ev.x + self.width-1, ev.y, c)
 
         self.cursor = (1 + self.pos - self.ofs, 0)
-        screen.popcolor()
+        ev.driver.popcolor()
 
-    def _handle_keypress(self, keyname, char):
-        super()._handle_keypress(keyname, char)
+    def _handle_keypress(self, ev):
+        super()._handle_keypress(ev)
         handled = False
-        if keyname:
+        if ev.keyname:
             handled = True
-            if keyname == 'left':
+            if ev.keyname == 'left':
                 self.move_left()
 
-            elif keyname == 'right':
+            elif ev.keyname == 'right':
                 self.move_right()
 
-            elif keyname == 'backspace':
+            elif ev.keyname == 'backspace':
                 if self.pos > 0:
                     self.move_left()
                     self.del_char()
 
-            elif keyname == 'delete':
+            elif ev.keyname == 'delete':
                 self.del_char()
 
             else:
                 handled = False
 
-        if char:
-            self.add_char(char)
+        if ev.char:
+            self.add_char(ev.char)
             self.move_right()
             handled = True
 
--- a/tuikit/emitter.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/emitter.py	Sat Dec 29 12:16:06 2012 +0100
@@ -1,28 +1,116 @@
 # -*- coding: utf-8 -*-
+"""Event emitter.
+
+This is simple implementation of signals/slots paradigm.
+We deliberately do not use signal or slot words, as they
+could be misleading (there are __slots__ in Python and
+signals in Unix OS).
+
+"""
 
 import logging
 
 
+class Event:
+    def __init__(self):
+        self.originator = None
+
+    def __getitem__(self, key):
+        return self.__dict__[key]
+
+
+class DrawEvent(Event):
+    def __init__(self, driver, x, y):
+        super().__init__()
+        self.driver = driver
+        self.x = x
+        self.y = y
+
+    def __repr__(self):
+        return 'DrawEvent(x={0.x},y={0.y})'.format(self)
+
+
+class FocusEvent(Event):
+    def __init__(self, old=None, new=None):
+        super().__init__()
+        #: Former focused widget.
+        self.old = old
+        #: Current focused widget.
+        self.new = new
+
+    def __repr__(self):
+        return 'FocusEvent(old={0.old},new={0.new})'.format(self)
+
+
+class KeyboardEvent(Event):
+    def __init__(self, keyname, char):
+        super().__init__()
+        self.keyname = keyname
+        self.char = char
+
+    def __repr__(self):
+        return 'KeyboardEvent(keyname={0.keyname},char={0.char})'.format(self)
+
+
+class MouseEvent(Event):
+    def __init__(self, x=0, y=0, button=0):
+        super().__init__()
+        self.x = x   # global coordinates
+        self.y = y
+        self.wx = x  # local widget coordinates
+        self.wy = y
+        self.px = 0  # parent coordinates
+        self.py = 0
+        self.button = button
+
+    def childevent(self, child):
+        ev = MouseEvent(self.x, self.y, self.button)
+        # original local coordinates are new parent coordinates
+        ev.px = self.wx
+        ev.py = self.wy
+        # update local coordinates
+        ev.wx = self.wx - child.x
+        ev.wy = self.wy - child.y
+        return ev
+
+    def __repr__(self):
+        return 'MouseEvent(x={0.x},y={0.y},button={0.button})'.format(self)
+
+
+class GenericEvent(Event):
+    """Generic event which carries an object with additional data."""
+    def __init__(self, data):
+        self.data = data
+
+
 class Emitter:
 
     """Event emitter mixin class."""
 
-    def add_events(self, *event_names):
-        """Add event names which may be registered by user.
+    def add_events(self, *events):
+        """Add events which may be registered by user.
 
         This should be called only by subclasses.
         This serves also as initializer, other methods of Emitter
         will not work if add_events was not called.
 
+        *events -- Arguments must be given in pairs.
+
+        Each pair consists of event_name, event_class:
+            event_name -- a string used in connect(), emit()
+            event_class -- class of event payload
+
         """
         if not hasattr(self, '_event_handlers'):
             self._event_handlers = dict()
-        for event_name in event_names:
+            self._event_class = dict()
+        for event_name, event_class in zip(events[::2], events[1::2]):
             self._event_handlers[event_name] = []
+            self._event_class[event_name] = event_class
             # add default dummy handler if no handler exists for this event
             handler_name = '_handle_' + event_name
             if not hasattr(Emitter, handler_name):
-                setattr(Emitter, handler_name, lambda *args, **kwargs: False)
+                setattr(Emitter, handler_name, lambda self, ev: False)
 
     def connect(self, event_name, handler):
         """Connect event handler to event name.
@@ -69,17 +157,33 @@
 
         Return True when one of the handlers returns True,
         False otherwise.
-
+        
+        This creates new instance of event_class given to
+        add_events() and passes all arguments after event_name
+        to its __init__ method.
+        
+        Unless first of these arguments is Event instance
+        in which case no object is created and the instance
+        is passed to handlers.
+        
         """
         logging.getLogger('tuikit').debug('Emit "%s" on %s %s',
             event_name,
             self.__class__.__name__,
             getattr(self, 'name', None) or id(self))
-        handled = getattr(self, '_handle_' + event_name)(*args, **kwargs)
+        # create event from specified event class, or use first argument
+        if len(args) and isinstance(args[0], Event):
+            event = args[0]
+        else:
+            event = self._event_class[event_name](*args, **kwargs)
+        event.originator = self
+        # try default handler, stop if satisfied
+        handled = getattr(self, '_handle_' + event_name)(event)
         if handled:
             return True
+        # try custom handlers, stop if satisfied
         for handler in self._event_handlers[event_name]:
-            handled = handler(self, *args, **kwargs)
+            handled = handler(event)
             if handled:
                 return True
 
--- a/tuikit/label.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/label.py	Sat Dec 29 12:16:06 2012 +0100
@@ -9,9 +9,9 @@
 
         self.label = label
 
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
-        screen.pushcolor('normal')
-        screen.puts(x, y, self.label)
-        screen.popcolor()
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
+        ev.driver.pushcolor('normal')
+        ev.driver.puts(ev.x, ev.y, self.label)
+        ev.driver.popcolor()
 
--- a/tuikit/layout.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/layout.py	Sat Dec 29 12:16:06 2012 +0100
@@ -25,7 +25,7 @@
         self._container = value
         self._container.connect('resize', self._on_container_resize)
 
-    def _on_container_resize(self, eo):
+    def _on_container_resize(self, ev):
         self.resize()
 
     def _getchildren(self):
--- a/tuikit/menu.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/menu.py	Sat Dec 29 12:16:06 2012 +0100
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from tuikit.widget import Widget
+from tuikit.emitter import GenericEvent
 
 
 class Menu(Widget):
@@ -17,34 +18,36 @@
         self.selected = items[0]
         self.menubar = None
 
-        self.add_events('activate')
+        self.add_events('activate', GenericEvent)
 
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
-        screen.pushcolor(self.bg)
-        screen.frame(x, y, self.width, self.height)
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
+        ev.driver.pushcolor(self.bg)
+        ev.driver.frame(ev.x, ev.y, self.width, self.height)
         i = 1
         for item in self.items:
             if item is None:
-                screen.puts(x, y + i, screen.unigraph.LTEE + \
-                    screen.unigraph.HLINE * (self.width - 2) + screen.unigraph.RTEE)
+                ev.driver.puts(ev.x, ev.y + i, ev.driver.unigraph.LTEE + \
+                    ev.driver.unigraph.HLINE * (self.width - 2) + ev.driver.unigraph.RTEE)
             else:
                 if self.selected == item:
-                    screen.pushcolor(self.highlight)
-                    screen.puts(x + 1, y + i, ' ' + item[0] + ' ' * (self.width - 3 - len(item[0])))
-                    screen.popcolor()
+                    ev.driver.pushcolor(self.highlight)
+                    ev.driver.puts(ev.x + 1, ev.y + i,
+                        ' ' + item[0] + ' ' * (self.width - 3 - len(item[0])))
+                    ev.driver.popcolor()
                 else:
-                    screen.puts(x + 1, y + i, ' ' + item[0] + ' ' * (self.width - 3 - len(item[0])))
+                    ev.driver.puts(ev.x + 1, ev.y + i,
+                        ' ' + item[0] + ' ' * (self.width - 3 - len(item[0])))
             i += 1
-        screen.popcolor()
+        ev.driver.popcolor()
 
-    def _handle_keypress(self, keyname, char):
-        super()._handle_keypress(keyname, char)
-        if keyname == 'up':
+    def _handle_keypress(self, ev):
+        super()._handle_keypress(ev)
+        if ev.keyname == 'up':
             self.move_selected(-1)
-        if keyname == 'down':
+        if ev.keyname == 'down':
             self.move_selected(+1)
-        if keyname == 'enter':
+        if ev.keyname == 'enter':
             self.run_selected()
         self.redraw()
 
--- a/tuikit/menubar.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/menubar.py	Sat Dec 29 12:16:06 2012 +0100
@@ -29,33 +29,33 @@
                 item[1].menubar = self
             i += len(item[0]) + 4
 
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
-        screen.pushcolor(self.bg)
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
+        ev.driver.pushcolor(self.bg)
         i = 0
         for item in self.items:
             if self.selected == item:
-                screen.pushcolor(self.highlight)
-                screen.puts(x + i, y, '  ' + item[0] + '  ')
-                screen.popcolor()
+                ev.driver.pushcolor(self.highlight)
+                ev.driver.puts(ev.x + i, ev.y, '  ' + item[0] + '  ')
+                ev.driver.popcolor()
             else:
-                screen.puts(x + i, y, '  ' + item[0] + '  ')
+                ev.driver.puts(ev.x + i, ev.y, '  ' + item[0] + '  ')
             i += len(item[0]) + 4
         if i < self.width:
-            screen.puts(x + i, y, ' ' * (self.width - i))
-        screen.popcolor()
+            ev.driver.puts(ev.x + i, ev.y, ' ' * (self.width - i))
+        ev.driver.popcolor()
 
 
-    def _handle_keypress(self, keyname, char):
-        super()._handle_draw(keyname, char)
-        if keyname == 'left':
+    def _handle_keypress(self, ev):
+        super()._handle_draw(ev)
+        if ev.keyname == 'left':
             self.move_selected(-1)
-        elif keyname == 'right':
+        elif ev.keyname == 'right':
             self.move_selected(+1)
         else:
             if self.selected:
                 if isinstance(self.selected[1], Widget):
-                    self.selected[1].emit('keypress', keyname, char)
+                    self.selected[1].emit('keypress', ev.keyname, ev.char)
 
 
     def move_selected(self, offset):
@@ -68,25 +68,28 @@
 
     def _handle_mousedown(self, ev):
         super()._handle_mousedown(ev)
+        self._select_xy(ev.wx, ev.wy)
+
+    def _handle_mousemove(self, ev):
+        super()._handle_mousemove(ev)
+        self._select_xy(ev.wx, ev.wy)
+
+    def _select_xy(self, wx, wy):
         i = 0
         self.unselect()
         for item in self.items:
             w = len(item[0]) + 4
-            if ev.wx >= i and ev.wx < i + w:
+            if wx >= i and wx < i + w:
                 self.select(item)
             i += w
 
-    def _handle_mousemove(self, ev):
-        super()._handle_mousemove(ev)
-        self.on_mousedown(ev)
-
-    def _handle_unfocus(self, newfocus):
-        super()._handle_unfocus()
-        if self.selected and newfocus == self.selected[1]:
+    def _handle_unfocus(self, ev):
+        super()._handle_unfocus(ev)
+        if self.selected and ev.new == self.selected[1]:
             return
         self.unselect()
 
-    def on_submenu_focus(self):
+    def on_submenu_focus(self, ev):
         self.set_focus()
 
     def select(self, item):
--- a/tuikit/scrollbar.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/scrollbar.py	Sat Dec 29 12:16:06 2012 +0100
@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from tuikit.widget import Widget
+from tuikit.emitter import Event
 
 
 class VScrollbar(Widget):
@@ -16,7 +17,7 @@
         self.dragging = False
         self.move = None
 
-        self.add_events('change')
+        self.add_events('change', Event)
 
     @property
     def max(self):
@@ -39,9 +40,10 @@
 
     @pos.setter
     def pos(self, value):
-        self._pos = value
-        self._update_thumbpos()
-        self.emit('change')
+        if self._pos != value:
+            self._pos = value
+            self._update_thumbpos()
+            self.emit('change')
 
     def _update_thumbpos(self):
         self._thumbpos = 0
@@ -49,13 +51,13 @@
             self._thumbpos = int(round(self._pos / self._max * (self.height - 3)))
         self.redraw()
 
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
-        screen.putch(x, y, screen.unigraph.UP_ARROW)
-        for i in range(y + 1, y + self.height - 1):
-            screen.putch(x, i, screen.unigraph.LIGHT_SHADE)
-        screen.putch(x, y + 1 + self._thumbpos, screen.unigraph.BLOCK)
-        screen.putch(x, y + self.height - 1, screen.unigraph.DOWN_ARROW)
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
+        ev.driver.putch(ev.x, ev.y, ev.driver.unigraph.UP_ARROW)
+        for i in range(ev.y + 1, ev.y + self.height - 1):
+            ev.driver.putch(ev.x, i, ev.driver.unigraph.LIGHT_SHADE)
+        ev.driver.putch(ev.x, ev.y + 1 + self._thumbpos, ev.driver.unigraph.BLOCK)
+        ev.driver.putch(ev.x, ev.y + self.height - 1, ev.driver.unigraph.DOWN_ARROW)
 
     def _handle_mousedown(self, ev):
         super()._handle_mousedown(ev)
--- a/tuikit/scrollview.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/scrollview.py	Sat Dec 29 12:16:06 2012 +0100
@@ -26,16 +26,16 @@
         if widget != self.vscroll:
             widget.connect('sizereq', self._on_child_sizereq)
 
-    def _handle_resize(self):
-        super()._handle_resize()
+    def _handle_resize(self, ev):
+        super()._handle_resize(ev)
         self.vscroll.x = self.size.w - 1
         self.vscroll.height = self.height
         self._update_vscroll_max()
 
-    def _on_vscroll_change(self, eo):
+    def _on_vscroll_change(self, ev):
         self.offset.y = - self.vscroll.pos
 
-    def _on_child_sizereq(self, eo):
+    def _on_child_sizereq(self, ev):
         self._update_vscroll_max()
 
     def _update_vscroll_max(self):
--- a/tuikit/tableview.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/tableview.py	Sat Dec 29 12:16:06 2012 +0100
@@ -3,14 +3,14 @@
 import math
 import logging
 
-from tuikit.emitter import Emitter
+from tuikit.emitter import Event, Emitter
 from tuikit.widget import Widget
 from tuikit.common import Coords
 
 
 class TableModel(Emitter):
     def __init__(self, list_of_lists):
-        self.add_events('change')
+        self.add_events('change', Event)
         self.data = list_of_lists
 
     def getcount(self):
@@ -82,7 +82,9 @@
         self.acell = Coords()
         '''Active cell (cursor).'''
 
-        self.add_events('scroll', 'areasize')
+        self.add_events(
+            'scroll', Event,
+            'areasize', Event)
 
     def getmodel(self):
         return self._model
@@ -156,32 +158,32 @@
             screen.popcolor()
             x += col.size + self.spacing
 
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
-        screen.pushcolor('normal')
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
+        ev.driver.pushcolor('normal')
         self.rowcount = self.model.getcount()
         numrows = min(self.rowcount - self.offset.y, self.size.h - self.headsize)
         rows = self.model.getrows(self.offset.y, self.offset.y + numrows)
         self.compute_column_sizes()
-        self.draw_head(screen, x, y)
-        y += self.headsize
+        self.draw_head(ev.driver, ev.x, ev.y)
+        y = ev.y + self.headsize
         for row in rows:
             highlight = []
             if self.offset.y + rows.index(row) == self.acell.y:
                 highlight.append(self.acell.x)
-            self.draw_row(screen, x, y, row, highlight)
+            self.draw_row(ev.driver, ev.x, y, row, highlight)
             y += 1
-        screen.popcolor()
+        ev.driver.popcolor()
 
-    def _handle_keypress(self, keyname, char):
-        super()._handle_keypress(keyname, char)
-        if keyname:
-            if keyname == 'up':  self.move_up()
-            if keyname == 'down':  self.move_down()
-            if keyname == 'left':  self.move_left()
-            if keyname == 'right':  self.move_right()
-            if keyname == 'pageup':  self.move_pageup()
-            if keyname == 'pagedown':  self.move_pagedown()
+    def _handle_keypress(self, ev):
+        super()._handle_keypress(ev)
+        if ev.keyname:
+            if ev.keyname == 'up':  self.move_up()
+            if ev.keyname == 'down':  self.move_down()
+            if ev.keyname == 'left':  self.move_left()
+            if ev.keyname == 'right':  self.move_right()
+            if ev.keyname == 'pageup':  self.move_pageup()
+            if ev.keyname == 'pagedown':  self.move_pagedown()
         self.redraw()
 
     def set_yofs(self, yofs):
--- a/tuikit/textedit.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/textedit.py	Sat Dec 29 12:16:06 2012 +0100
@@ -22,33 +22,28 @@
         self.vscroll.y = 1
         self.vscroll.connect('change', self.on_vscroll_change)
 
-        self.on_editbox_areasize()
+        self.on_editbox_areasize(None)
 
     def settext(self, text):
         self.editbox.set_text(text)
 
-
     def scrolltoend(self):
         self.editbox.move_pagelast()
 
-
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
-        screen.frame(x, y, self.width, self.height)
-
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
+        ev.driver.frame(ev.x, ev.y, self.width, self.height)
 
-    def on_editbox_scroll(self):
-        self.vscroll.setpos(self.editbox.yofs)
+    def on_editbox_scroll(self, ev):
+        self.vscroll.pos = self.editbox.yofs
 
-
-    def on_editbox_areasize(self):
+    def on_editbox_areasize(self, ev):
         smax = len(self.editbox.lines) - self.editbox.height
         if smax < 0:
             smax = 0
         self.vscroll.max = smax
 
-
-    def on_vscroll_change(self):
+    def on_vscroll_change(self, ev):
         self.editbox.set_yofs(self.vscroll.pos)
         self.editbox.redraw()
 
--- a/tuikit/treeview.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/treeview.py	Sat Dec 29 12:16:06 2012 +0100
@@ -1,9 +1,13 @@
 # -*- coding: utf-8 -*-
 
-from tuikit.emitter import Emitter
+from tuikit.emitter import Event, Emitter
 from tuikit.widget import Widget
 
-import logging
+
+class TreeEvent(Event):
+    def __init__(self, node):
+        Event.__init__(self)
+        self.node = node
 
 
 class TreeIter:
@@ -99,7 +103,7 @@
     """
 
     def __init__(self):
-        self.add_events('node_added')  # node added, arg is the node
+        self.add_events('node_added', TreeEvent)
         self.root = TreeNode('', model=self)
 
     def __iter__(self):
@@ -185,8 +189,8 @@
         self.collapsed = []
 
         self.add_events(
-            'expand',   # node expanded, the affected node is given in args
-            'collapse') # node collapsed, the affected node is given in args
+            'expand', TreeEvent,  # node expanded, event carries the affected node
+            'collapse', TreeEvent) # node collapsed, event carries the affected node
 
         if model:
             self.model = model
@@ -212,9 +216,9 @@
                 pass
             self._update_sizereq()
 
-    def on_model_node_added(self, node):
+    def on_model_node_added(self, ev):
         if self.cnode is None:
-            self.cnode = node
+            self.cnode = ev.node
         self._update_sizereq()
         self.redraw()
 
@@ -235,25 +239,26 @@
                 pass
         self._update_sizereq()
 
-    def _handle_draw(self, driver, x, y):
-        super()._handle_draw(driver, x, y)
-        driver.pushcolor('normal')
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
+        ev.driver.pushcolor('normal')
 
         lines = 0  # bit array, bit 0 - draw vertical line on first column, etc.
+        y = ev.y
         for level, index, count, node in self:
             # prepare string with vertical lines where they should be
             head = []
             for l in range(level-1):
                 if lines & (1 << l):
-                    head.append(driver.unigraph.VLINE + ' ')
+                    head.append(ev.driver.unigraph.VLINE + ' ')
                 else:
                     head.append('  ')
             # add vertical line if needed
             if index < count:
-                head.append(driver.unigraph.LTEE)
+                head.append(ev.driver.unigraph.LTEE)
                 lines |= 1 << level-1
             else:
-                head.append(driver.unigraph.LLCORNER)
+                head.append(ev.driver.unigraph.LLCORNER)
                 lines &= ~(1 << level-1)
             # draw lines and titles
             head = ''.join(head)
@@ -261,23 +266,22 @@
                 sep = '+'
             else:
                 sep = ' '
-            driver.puts(x, y, head + sep + str(node))
+            ev.driver.puts(ev.x, y, head + sep + str(node))
             if node is self.cnode:
-                driver.pushcolor('active')
-                driver.puts(x + len(head), y, sep + str(node) + ' ')
-                driver.popcolor()
-
+                ev.driver.pushcolor('active')
+                ev.driver.puts(ev.x + len(head), y, sep + str(node) + ' ')
+                ev.driver.popcolor()
             y += 1
 
-        driver.popcolor()
+        ev.driver.popcolor()
 
-    def _handle_keypress(self, keyname, char):
-        super()._handle_keypress(keyname, char)
-        if keyname:
-            if keyname == 'up':    self.move_up()
-            if keyname == 'down':  self.move_down()
-            if keyname == 'left':  self.move_left()
-            if keyname == 'right': self.move_right()
+    def _handle_keypress(self, ev):
+        super()._handle_keypress(ev)
+        if ev.keyname:
+            if ev.keyname == 'up':    self.move_up()
+            if ev.keyname == 'down':  self.move_down()
+            if ev.keyname == 'left':  self.move_left()
+            if ev.keyname == 'right': self.move_right()
 
         self.redraw()
 
--- a/tuikit/widget.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/widget.py	Sat Dec 29 12:16:06 2012 +0100
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-from tuikit.emitter import Emitter
+from tuikit.emitter import Emitter, Event, DrawEvent, FocusEvent, KeyboardEvent, MouseEvent
 from tuikit.common import Coords, Size
 
 
@@ -16,7 +16,7 @@
         self.parent = None
 
         #: Top widget (same for every widget in one application).
-        self.top = None
+        self._top = None
 
         # Position inside parent widget. Modified by layout manager.
         self.position = Coords()
@@ -34,9 +34,9 @@
         self.sizemax = Size(None, None)
 
         #: Size request. This is default size of the widget. Will be fulfilled if possible.
-        #: Tuple (w, h). Integers >= 1 or None (meaning use minumal size).
+        #: Size(w, h). Integers >= 1 or None (meaning use minumal size).
         self._sizereq = Size(10, 10)
-        self._sizereq.connect('change', lambda eo: self.emit('sizereq'))
+        self._sizereq.connect('change', lambda ev: self.emit('sizereq'))
 
         #: When false, the widget is not considered in layout.
         self.allow_layout = True
@@ -58,16 +58,16 @@
 
         # event handlers
         self.add_events(
-            'resize',
-            'sizereq',
-            'draw',
-            'focus',
-            'unfocus',
-            'keypress',
-            'mousedown',
-            'mouseup',
-            'mousemove',
-            'mousewheel')
+            'resize', Event,
+            'sizereq', Event,
+            'draw', DrawEvent,
+            'focus', FocusEvent,
+            'unfocus', FocusEvent,
+            'keypress', KeyboardEvent,
+            'mousedown', MouseEvent,
+            'mouseup', MouseEvent,
+            'mousemove', MouseEvent,
+            'mousewheel', MouseEvent)
 
 
     @property
@@ -107,10 +107,26 @@
 
     @property
     def sizereq(self):
+        """Size request.
+
+        This is default size of the widget. Will be fulfilled if possible.
+        Size(w, h). Integers >= 1 or None (meaning use minumal size).
+
+        """
         return self._sizereq
 
-    def settop(self, top):
-        self.top = top
+    @property
+    def top(self):
+        """Top widget (same for every widget in one application)."""
+        return self._top
+
+    @top.setter
+    def top(self, value):
+        self._set_top(value)
+
+    def _set_top(self, value):
+        """Real setter for top. Allows override."""
+        self._top = value
 
 
     ### events
@@ -124,7 +140,7 @@
         """Draw the widget.
 
         This method should not be overriden by subclasses,
-        use on_draw instead.
+        use _handle_draw instead.
 
         """
         if self.hidden:
@@ -171,10 +187,11 @@
         """
         if self.has_focus() or not self.can_focus():
             return
-        if self.parent.focuschild:
-            self.parent.focuschild.emit('unfocus', self)
+        oldfocuschild = self.parent.focuschild
         self.parent.focuschild = self
-        self.emit('focus')
+        if oldfocuschild:
+            oldfocuschild.emit('unfocus', new=self)
+        self.emit('focus', old=oldfocuschild)
 
 
     def grab_focus(self):
--- a/tuikit/window.py	Wed Dec 26 01:00:31 2012 +0100
+++ b/tuikit/window.py	Sat Dec 29 12:16:06 2012 +0100
@@ -55,24 +55,24 @@
         self.closebtn.hidden = not value
 
 
-    def _handle_draw(self, screen, x, y):
-        super()._handle_draw(screen, x, y)
-        screen.pushcolor('normal')
-        screen.frame(x, y, self.width, self.height)
+    def _handle_draw(self, ev):
+        super()._handle_draw(ev)
+        ev.driver.pushcolor('normal')
+        ev.driver.frame(ev.x, ev.y, self.width, self.height)
 
         if self.resizable:
             if self.resizing:
-                screen.pushcolor('controls-active')
+                ev.driver.pushcolor('controls-active')
             else:
-                screen.pushcolor('controls')
-            screen.puts(x + self.width - 2, y + self.height - 1, '─┘') # '━┛'
-            screen.popcolor()
+                ev.driver.pushcolor('controls')
+            ev.driver.puts(ev.x + self.width - 2, ev.y + self.height - 1, '─┘') # '━┛'
+            ev.driver.popcolor()
 
         if self.title:
-            screen.puts(x + (self.width - len(self.title))//2, y, self.title)
+            ev.driver.puts(ev.x + (self.width - len(self.title))//2, ev.y, self.title)
 
-        screen.fill(x+1, y+1, self.width-2, self.height-2)
-        screen.popcolor()
+        ev.driver.fill(ev.x+1, ev.y+1, self.width-2, self.height-2)
+        ev.driver.popcolor()
 
 
     def _handle_mousedown(self, ev):
@@ -131,12 +131,11 @@
         self.redraw(True)
 
 
-    def _handle_resize(self):
-        super()._handle_resize()
+    def _handle_resize(self, ev):
+        super()._handle_resize(ev)
         self.closebtn.x = self.width - 5
 
 
-    def on_closebtn_click(self):
+    def on_closebtn_click(self, ev):
         self.hide()
 
-