Clean up Emitter class, simplify event handling. Fix Container.focusnext() method. Add events test (handler auto-registration, order).
authorRadek Brich <radek.brich@devl.cz>
Fri, 04 Jan 2013 00:13:59 +0100
changeset 45 43b2279b06e1
parent 44 d77f1ae3786c
child 46 2b43a7f38c34
Clean up Emitter class, simplify event handling. Fix Container.focusnext() method. Add events test (handler auto-registration, order).
demo_checkbox.py
demo_editor.py
demo_input.py
demo_layout.py
demo_menu.py
demo_tableview.py
demo_treeview.py
demo_window.py
docs/emitter.rst
docs/events.rst
docs/index.rst
tests/events.py
tuikit/button.py
tuikit/checkbox.py
tuikit/combobox.py
tuikit/container.py
tuikit/editbox.py
tuikit/editfield.py
tuikit/events.py
tuikit/label.py
tuikit/layout.py
tuikit/menu.py
tuikit/menubar.py
tuikit/pager.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 Jan 02 11:48:36 2013 +0100
+++ b/demo_checkbox.py	Fri Jan 04 00:13:59 2013 +0100
@@ -10,7 +10,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.on_top_keypress)
+        self.top.add_handler('keypress', self.on_top_keypress)
 
         self.top.layout = VerticalLayout(homogeneous=False)
 
--- a/demo_editor.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/demo_editor.py	Fri Jan 04 00:13:59 2013 +0100
@@ -8,49 +8,27 @@
 
 from tuikit.application import Application
 from tuikit.editfield import EditField
-from tuikit.window import Window
-from tuikit.button import Button
-from tuikit.scrollbar import VScrollbar
 from tuikit.textedit import TextEdit
 
 
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.on_top_keypress)
+        self.top.add_handler('keypress', self.on_top_keypress)
 
         #edit = EditField(50, 'DlouhyTest12')
         #self.top.add(edit)
 
-
         t = open('tuikit/widget.py').read()
         textedit = TextEdit(100, 40, t)
         self.top.add(textedit)
         textedit.x = 2
         self.textedit = textedit
 
-        #win = Window()
-        #self.top.add(win)
-
-        #button = Button('click!')
-        #win.add(button)
-        #button.x = 10
-        #button.y = 7
-
-        #button.connect('click', self.buttonclick)
-        #self.button = button
-
-        #subwin = Window(8,8)
-        #win.add(subwin)
-
-
-    def buttonclick(self):
-        self.button.label = 'YES'
-
-
     def on_top_keypress(self, ev):
         if ev.keyname == 'escape':
             self.terminate()
+            return True
 
 
 if __name__ == '__main__':
--- a/demo_input.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/demo_input.py	Fri Jan 04 00:13:59 2013 +0100
@@ -10,7 +10,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.on_top_keypress)
+        self.top.add_handler('keypress', self.on_top_keypress)
 
         self.text = ''
         textedit = TextEdit(100, 40, self.text)
@@ -18,13 +18,13 @@
         textedit.x = 2
         self.textedit = textedit
 
-
     def on_top_keypress(self, ev):
         if ev.char == 'q':
             self.terminate()
         self.text += 'keyname: %(keyname)s  char: %(char)s\n' % ev
         self.textedit.settext(self.text)
         self.textedit.scrolltoend()
+        return True
 
 
 if __name__ == '__main__':
--- a/demo_layout.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/demo_layout.py	Fri Jan 04 00:13:59 2013 +0100
@@ -10,12 +10,13 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.on_top_keypress)
+        self.top.add_handler('keypress', self.on_top_keypress)
 
         #self.top.borders = (1,1,1,1)
 
         self.top.layout = VerticalLayout(homogeneous=False)
 
+        self._row_num = 0
         self.buildrow()
         self.buildrow(expand=True)
         self.buildrow(expand=True, fill=True)
@@ -25,17 +26,20 @@
         self.buildrow(homogeneous=True, fill=True, spacing=2)
 
     def buildrow(self, homogeneous=False, spacing=0, expand=False, fill=False):
-        hbox1 = Container()
-        hbox1.sizereq.h = 2
-        hbox1.layout = HorizontalLayout(homogeneous=homogeneous, spacing=spacing)
-        self.top.add(hbox1)
+        hbox = Container()
+        hbox.sizereq.h = 2
+        hbox.layout = HorizontalLayout(homogeneous=homogeneous, spacing=spacing)
+        self._row_num += 1
+        hbox.name = 'hbox' + str(self._row_num)
+        self.top.add(hbox)
         for i in range(5):
             btn = Button('Btn' + str(i) * i * i)
-            hbox1.add(btn, expand=expand, fill=fill)
+            hbox.add(btn, expand=expand, fill=fill)
 
     def on_top_keypress(self, ev):
         if ev.keyname == 'escape' or ev.char == 'q':
             self.terminate()
+            return True
 
 
 if __name__ == '__main__':
--- a/demo_menu.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/demo_menu.py	Fri Jan 04 00:13:59 2013 +0100
@@ -10,7 +10,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.on_top_keypress)
+        self.top.add_handler('keypress', self.on_top_keypress)
 
         menubar = MenuBar()
         self.top.add(menubar)
@@ -50,22 +50,10 @@
 
         self.top.layout = VerticalLayout()
 
-
-        #button = Button('click!')
-        #win.add(button)
-        #button.x = 10
-        #button.y = 7
-
-        #button.connect('click', self.buttonclick)
-        #self.button = button
-
-        #subwin = Window(8,8)
-        #win.add(subwin)
-
-
     def on_top_keypress(self, ev):
         if ev.keyname == 'escape':
             self.terminate()
+            return True
 
 
 if __name__ == '__main__':
--- a/demo_tableview.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/demo_tableview.py	Fri Jan 04 00:13:59 2013 +0100
@@ -12,7 +12,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.on_top_keypress)
+        self.top.add_handler('keypress', self.on_top_keypress)
 
         data = []
         for y in range(100):
@@ -34,6 +34,7 @@
     def on_top_keypress(self, ev):
         if ev.keyname == 'escape':
             self.terminate()
+            return True
 
 
 if __name__ == '__main__':
--- a/demo_treeview.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/demo_treeview.py	Fri Jan 04 00:13:59 2013 +0100
@@ -10,7 +10,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.on_top_keypress)
+        self.top.add_handler('keypress', self.on_top_keypress)
 
         model = TreeModel()
         model.add('/',  ['a', 'b'])
@@ -34,6 +34,7 @@
     def on_top_keypress(self, ev):
         if ev.keyname == 'escape':
             self.terminate()
+            return True
 
 
 if __name__ == '__main__':
--- a/demo_window.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/demo_window.py	Fri Jan 04 00:13:59 2013 +0100
@@ -13,7 +13,7 @@
 class MyApplication(Application):
     def __init__(self):
         Application.__init__(self)
-        self.top.connect('keypress', self.globalkeypress)
+        self.top.add_handler('keypress', self.on_top_keypress)
 
         #edit = EditField(50, 'DlouhyTest12')
         #self.top.add(edit)
@@ -26,20 +26,22 @@
         button.x = 10
         button.y = 7
 
-        button.connect('click', self.buttonclick)
+        button.add_handler('click', self.on_button_click)
         self.button = button
 
         subwin = Window(8,8)
         win.add(subwin)
 
 
-    def buttonclick(self):
+    def on_button_click(self, ev):
         self.button.label = 'YES'
+        return True
 
 
-    def globalkeypress(self, keyname, char):
-        if keyname == 'escape':
+    def on_top_keypress(self, ev):
+        if ev.keyname == 'escape':
             self.terminate()
+            return True
 
 
 if __name__ == '__main__':
@@ -47,7 +49,7 @@
     os.environ['ESCDELAY'] = '25' # do not wait 1 second after pressing Escape key
     app = MyApplication()
     #app.start()
-    
+
     cProfile.run('app.start()', 'demo_window.appstats')
     p = pstats.Stats('demo_window.appstats')
     p.sort_stats('time', 'cumulative').print_stats(20)
--- a/docs/emitter.rst	Wed Jan 02 11:48:36 2013 +0100
+++ b/docs/emitter.rst	Fri Jan 04 00:13:59 2013 +0100
@@ -7,7 +7,7 @@
 
    widget
 
-.. automodule:: tuikit.emitter
+.. autoclass:: tuikit.events.Emitter
    :members:
    :show-inheritance:
 
--- a/docs/events.rst	Wed Jan 02 11:48:36 2013 +0100
+++ b/docs/events.rst	Fri Jan 04 00:13:59 2013 +0100
@@ -1,5 +1,5 @@
-Event handling
-==============
+Events
+======
 
 Draw event
 ----------
--- a/docs/index.rst	Wed Jan 02 11:48:36 2013 +0100
+++ b/docs/index.rst	Fri Jan 04 00:13:59 2013 +0100
@@ -18,7 +18,7 @@
    colors
 
 .. inheritance-diagram:: tuikit.application
-                         tuikit.emitter
+                         tuikit.events
                          tuikit.widget
                          tuikit.container
                          tuikit.window
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/events.py	Fri Jan 04 00:13:59 2013 +0100
@@ -0,0 +1,61 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import sys
+sys.path.append('..')
+
+from tuikit.events import Emitter, KeyboardEvent
+
+class A(Emitter):
+    def __init__(self):
+        self.add_events('keypress', KeyboardEvent)
+
+    def on_keypress(self, ev):
+        """A"""
+        print('A on', ev.keyname, self)
+
+    def after_keypress(self, ev):
+        """A"""
+        print('A after', ev.keyname, self)
+
+
+class B(A):
+    def __init__(self):
+        super().__init__()
+
+    def on_keypress(self, ev):
+        """B"""
+        print('B on', ev.keyname, self)
+        #return True
+
+    def after_keypress(self, ev):
+        """B"""
+        print('B after', ev.keyname, self)
+
+
+class C(B):
+    def __init__(self):
+        super().__init__()
+
+
+def on_keypress(ev):
+    """global"""
+    print('global on', ev.keyname)
+
+
+def after_keypress(ev):
+    """global"""
+    print('global after', ev.keyname)
+
+
+if __name__ == '__main__':
+    c = C()
+    c.add_handler('keypress', after_keypress, last=True)
+    c.add_handler('keypress', on_keypress)
+
+    for x in c._event_handlers['keypress']:
+        print(x, x.__doc__)
+
+    print()
+    c.emit('keypress', keyname='XKey', char=None)
+
--- a/tuikit/button.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/button.py	Fri Jan 04 00:13:59 2013 +0100
@@ -36,8 +36,7 @@
         self.add_events('click', Event)
 
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         pad = self.width - len(self.label) - len(self.prefix) - len(self.suffix)
         lpad, rpad = self._divide_padding(pad)
         ev.driver.pushcolor(self.getcolor())
@@ -57,21 +56,18 @@
         ev.driver.puts(ev.x + pos, ev.y, self.suffix)
         ev.driver.popcolor()
 
-    def _handle_mousedown(self, ev):
-        super()._handle_mousedown(ev)
+    def on_mousedown(self, ev):
         self.highlight = True
         self.redraw()
 
-    def _handle_mouseup(self, ev):
-        super()._handle_mouseup(ev)
+    def on_mouseup(self, ev):
         self.highlight = False
         self.redraw()
 
         if self.enclose(ev.px, ev.py):
             self.emit('click')
 
-    def _handle_keypress(self, ev):
-        super()._handle_keypress(ev)
+    def on_keypress(self, ev):
         if ev.keyname == 'enter':
             self.emit('click')
 
--- a/tuikit/checkbox.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/checkbox.py	Fri Jan 04 00:13:59 2013 +0100
@@ -18,8 +18,7 @@
         self.bg = 'normal'
         self.bghi = 'active'
 
-    def _handle_click(self, ev):
-        super()._handle_click(ev)
+    def on_click(self, ev):
         if self.checked:
             self.checked = False
             self.prefix = '[ ] '
--- a/tuikit/combobox.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/combobox.py	Fri Jan 04 00:13:59 2013 +0100
@@ -22,7 +22,7 @@
         self._btn.suffix = ''
         self._btn.x = width - 3
         self._btn.width = 3
-        self._btn.connect('click', self._on_btn_click)
+        self._btn.add_handler('click', self._on_btn_click)
         self.add(self._btn)
 
         self._menu = Menu(items)
--- a/tuikit/container.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/container.py	Fri Jan 04 00:13:59 2013 +0100
@@ -72,7 +72,8 @@
         this cycling:
 
          * False means there wasn't any child to focus
-           before end of list, so first child was focused.
+           before end of list. Focus was either not changed
+           or first child was focused.
 
          * True when focus is set to next child in normal
            way or when self.trap_focus is set.
@@ -82,16 +83,18 @@
 
         """
         idx_current = self.children.index(self.focuschild)
-        idx_new = idx_current + 1
+        idx_new = idx_current
         cycled = False
-        while idx_current != idx_new:
+        while True:
+            idx_new += 1
             if idx_new >= len(self.children):
                 idx_new = 0
                 cycled = True
+            if idx_current == idx_new:
+                return False
             if self.children[idx_new].can_focus():
                 self.children[idx_new].set_focus()
                 return self.trap_focus or not cycled
-            idx_new += 1
 
     def draw(self, driver, x, y):
         """Draw the container and its children.
@@ -126,13 +129,11 @@
             driver.popcolorprefix()
         driver.clipstack.pop()
 
-    def _handle_resize(self, ev):
-        super()._handle_resize(ev)
+    def on_resize(self, ev):
         for child in self.children:
             child.emit('resize')
 
-    def _handle_keypress(self, ev):
-        super()._handle_keypress(ev)
+    def on_keypress(self, ev):
         if self.focuschild is not None:
             handled = self.focuschild.emit('keypress', ev)
             if handled:
@@ -140,8 +141,7 @@
         if ev.keyname == 'tab':
             return self.focus_next()
 
-    def _handle_mousedown(self, ev):
-        super()._handle_mousedown(ev)
+    def on_mousedown(self, ev):
         handled = False
         for child in reversed(self.children):
             if child.enclose(ev.wx, ev.wy):
@@ -152,23 +152,20 @@
                 break
         return handled
 
-    def _handle_mouseup(self, ev):
-        super()._handle_mouseup(ev)
+    def on_mouseup(self, ev):
         if self.mousechild:
             childev = ev.childevent(self.mousechild)
             self.mousechild.emit('mouseup', childev)
             self.mousechild = None
             return True
 
-    def _handle_mousemove(self, ev):
-        super()._handle_mousemove(ev)
+    def on_mousemove(self, ev):
         if self.mousechild:
             childev = ev.childevent(self.mousechild)
             self.mousechild.emit('mousemove', childev)
             return True
 
-    def _handle_mousewheel(self, ev):
-        super()._handle_mousewheel(ev)
+    def on_mousewheel(self, ev):
         handled = False
         for child in reversed(self.children):
             if child.enclose(ev.wx, ev.wy):
--- a/tuikit/editbox.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/editbox.py	Fri Jan 04 00:13:59 2013 +0100
@@ -28,8 +28,7 @@
         self.set_text(text)
 
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         for j in range(self.height):
             if self.yofs + j >= len(self.lines):
                 break
@@ -43,8 +42,7 @@
         self.cursor = (self.get_cpos() - self.xofs, self.cline - self.yofs)
 
 
-    def _handle_keypress(self, ev):
-        super()._handle_keypress(ev)
+    def on_keypress(self, ev):
         if ev.keyname:
             if ev.keyname == 'left':
                 self.move_left()
@@ -89,8 +87,7 @@
         self.redraw()
 
 
-    def _handle_mousewheel(self, ev):
-        super()._handle_mousewheel(ev)
+    def on_mousewheel(self, ev):
         # up
         if ev.button == 4:
             self.move_up()
--- a/tuikit/editfield.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/editfield.py	Fri Jan 04 00:13:59 2013 +0100
@@ -22,12 +22,10 @@
         self.pos = len(value)      # position of cursor in value
         self.ofs = 0      # position of value beginning on screen
 
-    def _handle_resize(self, ev):
-        super()._handle_resize(ev)
+    def on_resize(self, ev):
         self.tw = self.width - 2
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         ev.driver.pushcolor('normal')
         # draw value
         val = self.value + ' ' * self.tw         # add spaces to fill rest of field
@@ -48,8 +46,7 @@
         self.cursor = (1 + self.pos - self.ofs, 0)
         ev.driver.popcolor()
 
-    def _handle_keypress(self, ev):
-        super()._handle_keypress(ev)
+    def on_keypress(self, ev):
         handled = False
         if ev.keyname:
             handled = True
--- a/tuikit/events.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/events.py	Fri Jan 04 00:13:59 2013 +0100
@@ -9,6 +9,7 @@
 """
 
 import logging
+import inspect
 
 
 class Event:
@@ -88,57 +89,69 @@
     """Event emitter mixin class."""
 
     def add_events(self, *events):
-        """Add events which may be registered by user.
+        """Add events which may be emitted on instances of this class.
 
         This should be called only by subclasses.
-        This serves also as initializer, other methods of Emitter
+        It also serves 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_name -- a string used in add_handler(), emit() etc.
             event_class -- class of event payload
 
+        It will also inspect base classes of instance on which add_events()
+        is called, adding all methods meeting naming convention to handler list.
+        The convention for method names is "on_event_name", "after_event_name".
+        Prefix on/after determines whether the handler is added as first or last.
+
         """
+        if len(events) % 2:
+            raise ValueError('add_events(): Event names and classes must be passed in pairs.')
         if not hasattr(self, '_event_handlers'):
             self._event_handlers = dict()
             self._event_class = dict()
         for event_name, event_class in zip(events[::2], events[1::2]):
+            if not isinstance(event_name, str):
+                raise TypeError('add_events(): Event name must be a string: %r.' % event_name)
+            if not issubclass(event_class, Event):
+                raise TypeError('add_events(): Class %s does not inherit from Event.' % event_class)
             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 self, ev: False)
+            self._add_default_handlers(event_name)
 
-    def connect(self, event_name, handler):
-        """Connect event handler to event name.
+    def add_handler(self, event_name, handler, last=False):
+        """Add handler to event name.
 
-        Add handler to the end of handler list.
+        last=False -- Add handler as first in handler list.
+        last=True -- Add handler to the end of handler list.
 
         """
         if event_name in self._event_handlers:
-            self._event_handlers[event_name].append(handler)
+            if last:
+                self._event_handlers[event_name].append(handler)
+            else:
+                self._event_handlers[event_name].insert(0, handler)
         else:
             raise KeyError('Unknown event: %s', event_name)
 
-    def disconnect(self, event_name, handler=None):
-        """Remove event handler from the list.
-
-        If no handler is given, remove all handlers.
-
-        """
+    def remove_handler(self, event_name, handler):
+        """Remove event handler from the list."""
         if event_name in self._event_handlers:
-            if handler:
-                self._event_handlers[event_name].remove(handler)
-            else:
-                self._event_handlers[event_name][:] = []
+            self._event_handlers[event_name].remove(handler)
         else:
             raise KeyError('Unknown event: %s', event_name)
 
-    def is_connected(self, event_name):
-        """Test if any handlers are connected to event name.
+    def remove_all_handlers(self, event_name):
+        """Remove all handlers for event."""
+        if event_name in self._event_handlers:
+            self._event_handlers[event_name][:] = []
+        else:
+            raise KeyError('Unknown event: %s', event_name)
+
+    def has_handlers(self, event_name):
+        """Test if any handlers are attached to event name.
 
         Return True if event handler list is not empty,
         False otherwise.
@@ -153,17 +166,20 @@
         """Emit the event.
 
         Call all handlers from event's handler list,
-        starting from first added handler.
+        starting from last added handler, going to those added
+        before, ending with those added with last=True.
+
+        Stop if any handler returns True.
 
         Return True when one of the handlers returns True,
         False otherwise.
 
-        This creates new instance of event_class given to
+        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
+        in which case no object is created and the Event instance
         is passed to handlers.
 
         """
@@ -172,18 +188,50 @@
             self.__class__.__name__,
             getattr(self, 'name', None) or id(self))
         # create event from specified event class, or use first argument
-        if len(args) and isinstance(args[0], Event):
+        if len(args) == 1 and isinstance(args[0], Event):
             event = args[0]
         else:
             event = self._event_class[event_name](*args, **kwargs)
+        # set originator to instance on which emit() was called
         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
+        # call handlers from first to last, stop if satisfied
         for handler in self._event_handlers[event_name]:
             handled = handler(event)
             if handled:
                 return True
+        return False
 
+    def _add_default_handlers(self, event_name):
+        """Add default handlers from the instance and its base classes.
+
+        Handlers are looked up in methods by their names.
+        Method name is composited from prefix and event_name.
+        Prefix is one of "on_" or "after_".
+        Depending on prefix the handler is added to top or bottom of handler stack.
+
+        See _add_default_handler method for more information.
+
+        """
+        for cls in reversed(inspect.getmro(self.__class__)):
+            self._add_default_handler(cls, 'on_', event_name, last=False)
+            self._add_default_handler(cls, 'after_', event_name, last=True)
+
+    def _add_default_handler(self, cls, prefix, event_name, last):
+        """Add handler from one of base classes to handler list.
+
+        cls -- one of self's base classes
+        prefix -- method name prefix
+        event_name -- event name
+        last -- if True, add handler to end of handler list (will be called last)
+
+        Look for method of name prefix + event_name in class 'cls',
+        if exists, add it to handler list.
+
+        """
+        method_name = prefix + event_name
+        if method_name in cls.__dict__:
+            unbound_handler = cls.__dict__[method_name]
+            if callable(unbound_handler):
+                handler = unbound_handler.__get__(self, cls)
+                self.add_handler(event_name, handler, last)
+
--- a/tuikit/label.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/label.py	Fri Jan 04 00:13:59 2013 +0100
@@ -9,8 +9,7 @@
 
         self.label = label
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         ev.driver.pushcolor('normal')
         ev.driver.puts(ev.x, ev.y, self.label)
         ev.driver.popcolor()
--- a/tuikit/layout.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/layout.py	Fri Jan 04 00:13:59 2013 +0100
@@ -23,7 +23,7 @@
     @container.setter
     def container(self, value):
         self._container = value
-        self._container.connect('resize', self._on_container_resize)
+        self._container.add_handler('resize', self._on_container_resize)
 
     def _on_container_resize(self, ev):
         self.resize()
--- a/tuikit/menu.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/menu.py	Fri Jan 04 00:13:59 2013 +0100
@@ -20,8 +20,7 @@
 
         self.add_events('activate', GenericEvent)
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         ev.driver.pushcolor(self.bg)
         ev.driver.frame(ev.x, ev.y, self.width, self.height)
         i = 1
@@ -41,8 +40,7 @@
             i += 1
         ev.driver.popcolor()
 
-    def _handle_keypress(self, ev):
-        super()._handle_keypress(ev)
+    def on_keypress(self, ev):
         if ev.keyname == 'up':
             self.move_selected(-1)
         if ev.keyname == 'down':
@@ -51,16 +49,13 @@
             self.run_selected()
         self.redraw()
 
-    def _handle_mousedown(self, ev):
-        super()._handle_mousedown(ev)
+    def on_mousedown(self, ev):
         self.select_at_pos(ev.wy - 1)
 
-    def _handle_mousemove(self, ev):
-        super()._handle_mousemove(ev)
+    def on_mousemove(self, ev):
         self.select_at_pos(ev.wy - 1)
 
-    def _handle_mouseup(self, ev):
-        super()._handle_mousemove(ev)
+    def on_mouseup(self, ev):
         ok = self.select_at_pos(ev.wy - 1)
         if ok:
             self.run_selected()
--- a/tuikit/menubar.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/menubar.py	Fri Jan 04 00:13:59 2013 +0100
@@ -25,12 +25,11 @@
                 item[1].y = self.y + 1
                 item[1].allow_layout = False
                 item[1].hidden = True
-                item[1].connect('focus', self.on_submenu_focus)
+                item[1].add_handler('focus', self.on_submenu_focus)
                 item[1].menubar = self
             i += len(item[0]) + 4
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         ev.driver.pushcolor(self.bg)
         i = 0
         for item in self.items:
@@ -46,8 +45,7 @@
         ev.driver.popcolor()
 
 
-    def _handle_keypress(self, ev):
-        super()._handle_draw(ev)
+    def on_keypress(self, ev):
         if ev.keyname == 'left':
             self.move_selected(-1)
         elif ev.keyname == 'right':
@@ -66,12 +64,10 @@
             self.select(item)
         self.redraw()
 
-    def _handle_mousedown(self, ev):
-        super()._handle_mousedown(ev)
+    def on_mousedown(self, ev):
         self._select_xy(ev.wx, ev.wy)
 
-    def _handle_mousemove(self, ev):
-        super()._handle_mousemove(ev)
+    def on_mousemove(self, ev):
         self._select_xy(ev.wx, ev.wy)
 
     def _select_xy(self, wx, wy):
@@ -83,8 +79,7 @@
                 self.select(item)
             i += w
 
-    def _handle_unfocus(self, ev):
-        super()._handle_unfocus(ev)
+    def on_unfocus(self, ev):
         if self.selected and ev.new == self.selected[1]:
             return
         self.unselect()
--- a/tuikit/pager.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/pager.py	Fri Jan 04 00:13:59 2013 +0100
@@ -41,7 +41,7 @@
             widget.hidden = True
 
         btn = Button(kw['title'])
-        btn.connect('click', lambda: self.select(widget))
+        btn.add_handler('click', lambda: self.select(widget))
         self.buttons.add(btn, fill=True)
 
     def select(self, child):
--- a/tuikit/scrollbar.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/scrollbar.py	Fri Jan 04 00:13:59 2013 +0100
@@ -51,16 +51,14 @@
             self._thumbpos = int(round(self._pos / self._max * (self.height - 3)))
         self.redraw()
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, 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)
+    def on_mousedown(self, ev):
         self.dragging = False
         self.move = None
         # arrow buttons
@@ -76,8 +74,7 @@
             self.dragging = True
             return
 
-    def _handle_mouseup(self, ev):
-        super()._handle_mouseup(ev)
+    def on_mouseup(self, ev):
         if self.dragging:
             self.drag(ev.wy)
             self.dragging = False
@@ -87,8 +84,7 @@
             self.move = None
             return
 
-    def _handle_mousemove(self, ev):
-        super()._handle_mousemove(ev)
+    def on_mousemove(self, ev):
         if self.dragging:
             self.drag(ev.wy)
 
--- a/tuikit/scrollview.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/scrollview.py	Fri Jan 04 00:13:59 2013 +0100
@@ -16,18 +16,17 @@
         Container.__init__(self, width, height)
 
         self.vscroll = VScrollbar(height)
-        self.vscroll.connect('change', self._on_vscroll_change)
+        self.vscroll.add_handler('change', self._on_vscroll_change)
         self.vscroll.allow_layout = False
         self.add(self.vscroll)
 
     def add(self, widget, **kwargs):
         super().add(widget, **kwargs)
         if widget != self.vscroll:
-            widget.connect('sizereq', self._on_child_sizereq)
-            widget.connect('spotmove', self._on_child_spotmove)
+            widget.add_handler('sizereq', self._on_child_sizereq)
+            widget.add_handler('spotmove', self._on_child_spotmove)
 
-    def _handle_resize(self, ev):
-        super()._handle_resize(ev)
+    def on_resize(self, ev):
         self.vscroll.x = self.size.w - 1
         self.vscroll.height = self.height
         self._update_vscroll_max()
--- a/tuikit/tableview.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/tableview.py	Fri Jan 04 00:13:59 2013 +0100
@@ -91,10 +91,10 @@
 
     def setmodel(self, value):
         if self._model:
-            self._model.disconnect('change', self.redraw)
+            self._model.remove_handler('change', self.redraw)
         self._model = value
         if self._model:
-            self._model.connect('change', self.redraw)
+            self._model.add_handler('change', self.redraw)
 
     model = property(getmodel, setmodel)
 
@@ -158,8 +158,7 @@
             screen.popcolor()
             x += col.size + self.spacing
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         ev.driver.pushcolor('normal')
         self.rowcount = self.model.getcount()
         numrows = min(self.rowcount - self.offset.y, self.size.h - self.headsize)
@@ -175,15 +174,17 @@
             y += 1
         ev.driver.popcolor()
 
-    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()
+    def on_keypress(self, ev):
+        key_map = {
+            'up': self.move_up,
+            'down': self.move_down,
+            'left': self.move_left,
+            'right': self.move_right,
+            'pageup':  self.move_pageup,
+            'pagedown':  self.move_pagedown}
+        if ev.keyname in key_map:
+            key_map[ev.keyname]()
+            return True
         self.redraw()
 
     def set_yofs(self, yofs):
--- a/tuikit/textedit.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/textedit.py	Fri Jan 04 00:13:59 2013 +0100
@@ -13,14 +13,14 @@
         self.add(self.editbox)
         self.editbox.x = 1
         self.editbox.y = 1
-        self.editbox.connect('scroll', self.on_editbox_scroll)
-        self.editbox.connect('areasize', self.on_editbox_areasize)
+        self.editbox.add_handler('scroll', self.on_editbox_scroll)
+        self.editbox.add_handler('areasize', self.on_editbox_areasize)
 
         self.vscroll = VScrollbar(height - 2)
         self.add(self.vscroll)
         self.vscroll.x = width - 1
         self.vscroll.y = 1
-        self.vscroll.connect('change', self.on_vscroll_change)
+        self.vscroll.add_handler('change', self.on_vscroll_change)
 
         self.on_editbox_areasize(None)
 
@@ -30,8 +30,7 @@
     def scrolltoend(self):
         self.editbox.move_pagelast()
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         ev.driver.frame(ev.x, ev.y, self.width, self.height)
 
     def on_editbox_scroll(self, ev):
--- a/tuikit/treeview.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/treeview.py	Fri Jan 04 00:13:59 2013 +0100
@@ -205,10 +205,10 @@
     @model.setter
     def model(self, value):
         if self._model:
-            self._model.disconnect('node_added', self.on_model_node_added)
+            self._model.remove_handler('node_added', self.on_model_node_added)
         self._model = value
         if self._model:
-            self._model.connect('node_added', self.on_model_node_added)
+            self._model.add_handler('node_added', self.on_model_node_added)
             try:
                 self.cursor_node = self._model.root.children[0]
             except IndexError:
@@ -247,8 +247,7 @@
                 pass
         self._update_sizereq()
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         ev.driver.pushcolor('normal')
 
         lines = 0  # bit array, bit 0 - draw vertical line on first column, etc.
@@ -283,14 +282,15 @@
 
         ev.driver.popcolor()
 
-    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()
-
+    def on_keypress(self, ev):
+        key_map = {
+            'up': self.move_up,
+            'down': self.move_down,
+            'left': self.move_left,
+            'right': self.move_right}
+        if ev.keyname in key_map:
+            key_map[ev.keyname]()
+            return True
         self.redraw()
 
     def prev_node(self, node):
--- a/tuikit/widget.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/widget.py	Fri Jan 04 00:13:59 2013 +0100
@@ -36,7 +36,7 @@
         #: 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).
         self._sizereq = Size(10, 10)
-        self._sizereq.connect('change', lambda ev: self.emit('sizereq'))
+        self._sizereq.add_handler('change', lambda ev: self.emit('sizereq'))
 
         #: When false, the widget is not considered in layout.
         self.allow_layout = True
@@ -157,7 +157,7 @@
         """Draw the widget.
 
         This method should not be overriden by subclasses,
-        use _handle_draw instead.
+        use on_draw method instead.
 
         """
         if self.hidden:
@@ -172,8 +172,8 @@
             else:
                 driver.hidecursor()
 
-    def _handle_mousedown(self, ev):
-        self.set_focus()
+    def on_mousedown(self, ev):
+        self.grab_focus()
 
 
     ### focus
--- a/tuikit/window.py	Wed Jan 02 11:48:36 2013 +0100
+++ b/tuikit/window.py	Fri Jan 04 00:13:59 2013 +0100
@@ -3,8 +3,6 @@
 from tuikit.container import Container
 from tuikit.button import Button
 
-import logging
-
 
 class Window(Container):
 
@@ -35,7 +33,7 @@
         self.closebtn.allow_layout = False
         self.closebtn.x = self.width - 5
         self.closebtn.width = 3
-        self.closebtn.connect('click', self.on_closebtn_click)
+        self.closebtn.add_handler('click', self.on_closebtn_click)
         self.closebtn.bg = 'controls'
         self.closebtn.bghi = 'controls-active'
         self.add(self.closebtn)
@@ -55,8 +53,7 @@
         self.closebtn.hidden = not value
 
 
-    def _handle_draw(self, ev):
-        super()._handle_draw(ev)
+    def on_draw(self, ev):
         ev.driver.pushcolor('normal')
         ev.driver.frame(ev.x, ev.y, self.width, self.height)
 
@@ -75,11 +72,7 @@
         ev.driver.popcolor()
 
 
-    def _handle_mousedown(self, ev):
-        handled = super()._handle_mousedown(ev)
-        if handled:
-            return True
-
+    def after_mousedown(self, ev):
         self.dragstart = (ev.wx, ev.wy)
         if self.resizable and ev.wx >= self.width - 1 and ev.wy >= self.height - 1:
             self.resizing = True
@@ -90,11 +83,7 @@
         self.redraw(True)
 
 
-    def _handle_mouseup(self, ev):
-        handled = super()._handle_mouseup(ev)
-        if handled:
-            return True
-
+    def after_mouseup(self, ev):
         if self.resizing:
             self.width = self.origsize[0] + ev.wx - self.dragstart[0]
             self.height = self.origsize[1] + ev.wy - self.dragstart[1]
@@ -108,11 +97,7 @@
         self.redraw(True)
 
 
-    def _handle_mousemove(self, ev):
-        handled = super()._handle_mousemove(ev)
-        if handled:
-            return True
-
+    def after_mousemove(self, ev):
         if ev.px == self.x + self.dragstart[0] \
         and ev.py == self.y + self.dragstart[1]:
             return
@@ -131,8 +116,7 @@
         self.redraw(True)
 
 
-    def _handle_resize(self, ev):
-        super()._handle_resize(ev)
+    def on_resize(self, ev):
         self.closebtn.x = self.width - 5