Clean up, refactoring. Rename EventSource to Emitter, begin merging emit() method with handle().
authorRadek Brich <radek.brich@devl.cz>
Fri, 14 Dec 2012 10:20:14 +0100
changeset 32 088b92ffb119
parent 31 629d5edb1602
child 33 45f1b6d590bd
Clean up, refactoring. Rename EventSource to Emitter, begin merging emit() method with handle().
tuikit/application.py
tuikit/button.py
tuikit/container.py
tuikit/editbox.py
tuikit/eventsource.py
tuikit/menu.py
tuikit/pager.py
tuikit/scrollbar.py
tuikit/tableview.py
tuikit/treeview.py
tuikit/widget.py
tuikit/window.py
--- a/tuikit/application.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/application.py	Fri Dec 14 10:20:14 2012 +0100
@@ -8,9 +8,9 @@
 
 
 class TopWindow(Container):
-    
+
     '''Top window of an application. Covers entire screen.'''
-    
+
     def __init__(self):
         '''Create top window.'''
         Container.__init__(self)
@@ -71,20 +71,20 @@
 
 
 class Application:
-    
+
     '''Application class. Defines main loop.'''
-       
+
     def __init__(self, driver = 'curses'):
         '''Create application.'''
-        
+
         self.top = TopWindow()
         '''Top window.'''
-        
+
         self.quit = False
-        
+
         self.driver = self.get_driver_instance(driver)
         '''Driver class instance (render + input), e.g. DriverCurses.'''
-        
+
         self.log = logging.getLogger('tuikit')
         self.log.setLevel(logging.DEBUG)
         handler = logging.FileHandler('./tuikit.log')
@@ -104,8 +104,8 @@
     def mainloop(self):
         '''The main loop.'''
         self.applytheme()
-        self.top.size = self.driver.size  # link top widget size to screen size 
-        self.top.emit('resize')
+        self.top.size = self.driver.size  # link top widget size to screen size
+        self.top.handle('resize')
 
         while True:
             self.top.draw(self.driver)
--- a/tuikit/button.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/button.py	Fri Dec 14 10:20:14 2012 +0100
@@ -4,9 +4,9 @@
 
 
 class Button(Widget):
-    
+
     '''Clickable button.'''
-    
+
     def __init__(self, label=''):
         '''Create button with given label, size according to label.'''
         w = len(label) + 4
@@ -21,11 +21,11 @@
         self.suffix = ']'
         #: How should label be aligned if button has excess space - center | left | right
         self.align = 'center'
-        
+
         self.bg = 'button'
         self.bghi = 'button-active'
         self.highlight = False
-        
+
         # size
         self.sizereq.w = w
         self.sizereq.h = h
@@ -35,7 +35,7 @@
         self.connect('mouseup', self.on_mouseup)
         self.connect('keypress', self.on_keypress)
 
-        self.addevents('click')
+        self.add_events('click')
 
 
     def on_draw(self, screen, x, y):
--- a/tuikit/container.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/container.py	Fri Dec 14 10:20:14 2012 +0100
@@ -7,24 +7,24 @@
 
 
 class Container(Widget):
-    
+
     '''Container widget. Base for any widget which can contain other widgets.'''
-    
+
     def __init__(self, width = 10, height = 10):
         '''Create container of requested size.'''
         Widget.__init__(self, width, height)
 
         #: List of child widgets.
         self.children = []
-        
+
         self.mousechild = None
 
         #: Width of borders (left, top, right, bottom).
         #: Child widgets are placed within borders.
         self.borders = Borders()
-        
+
         self._layout = None
-        
+
         self.widthrequest = (None, None)
         self.heightrequest = (None, None)
 
@@ -32,6 +32,8 @@
 
         self.trapfocus = False  # if True, tab cycles inside container
 
+        self.connect('resize', self.on_resize)
+
 
     def add(self, widget, **kw):
         '''Add widget into this container.'''
@@ -75,10 +77,9 @@
         Widget.keypress(self, keyname, char)
 
 
-    def resize(self):
-        Widget.resize(self)
+    def on_resize(self):
         for child in self.children:
-            child.emit('resize')
+            child.handle('resize')
 
 
     def draw(self, driver, x=0, y=0):
--- a/tuikit/editbox.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/editbox.py	Fri Dec 14 10:20:14 2012 +0100
@@ -22,7 +22,7 @@
         self.connect('keypress', self.on_keypress)
         self.connect('mousewheel', self.on_mousewheel)
 
-        self.addevents('scroll', 'areasize')
+        self.add_events('scroll', 'areasize')
 
         self.set_text(text)
 
--- a/tuikit/eventsource.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/eventsource.py	Fri Dec 14 10:20:14 2012 +0100
@@ -1,46 +1,59 @@
 # -*- coding: utf-8 -*-
 
-class EventSource:
-    
-    '''Event source base class.'''
+class Emitter:
+
+    '''Event emitter mixin class.'''
 
-    def __init__(self):
-        self.event = dict()
+    def add_events(self, *event_names):
+        '''Add event names 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.
 
-    def addevents(self, *events):
-        '''Create new events with empty handler list.'''
-        for event in events:
-            self.event[event] = []
+        '''
+        if not hasattr(self, '_event_handlers'):
+            self._event_handlers = dict()
+        for event_name in event_names:
+            self._event_handlers[event_name] = []
+
+    def connect(self, event_name, handler):
+        '''Connect event handler to event name.
 
-    def connect(self, event, handler):
-        '''Add handler to handler list of the event.'''
-        if event in list(self.event.keys()):
-            self.event[event].append(handler)
+        Add handler to the end of handler list.
+
+        '''
+        if event_name in list(self._event_handlers.keys()):
+            self._event_handlers[event_name].append(handler)
         else:
-            raise KeyError('Event %s not known.', event)
+            raise KeyError('Unknown event: %s', event_name)
 
-    def disconnect(self, event, handler=None):
-        '''Remove handler from event's handler list.
+    def disconnect(self, event_name, handler=None):
+        '''Remove event handler from the list.
 
         If no handler is given, remove all handlers.
 
         '''
-        if event in list(self.event.keys()):
+        if event_name in list(self._event_handlers.keys()):
             if handler:
-                self.event[event].remove(handler)
+                self._event_handlers[event_name].remove(handler)
             else:
-                self.event[event] = []
+                self._event_handlers[event_name][:] = []
         else:
-            raise KeyError('Event %s not known.', event)
+            raise KeyError('Unknown event: %s', event_name)
+
+    def handle(self, event_name, *args, **kwargs):
+        '''Emit the event.
 
-    def handle(self, event, *args, **kwargs):
-        '''Call all handlers from event's handler list.
+        Call all handlers from event's handler list,
+        starting from first added handler.
 
-        This is used when user defined handlers are to be called.
+        Return True when one of the handlers returns True,
+        False otherwise.
 
         '''
         handled = False
-        for handler in self.event[event]:
+        for handler in self._event_handlers[event_name]:
             res = handler(*args, **kwargs)
             if res:
                 handled = True
--- a/tuikit/menu.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/menu.py	Fri Dec 14 10:20:14 2012 +0100
@@ -20,9 +20,8 @@
         self.connect('mousedown', self.on_mousedown)
         self.connect('mousemove', self.on_mousemove)
         self.connect('mouseup', self.on_mouseup)
-        
-        self.addevents('activate')
 
+        self.add_events('activate')
 
     def on_draw(self, screen, x, y):
         screen.pushcolor(self.bg)
@@ -42,7 +41,6 @@
             i += 1
         screen.popcolor()
 
-
     def on_keypress(self, keyname, char):
         if keyname == 'up':
             self.move_selected(-1)
@@ -52,21 +50,17 @@
             self.run_selected()
         self.redraw()
 
-
     def on_mousedown(self, ev):
         self.select_at_pos(ev.wy - 1)
 
-
     def on_mousemove(self, ev):
         self.select_at_pos(ev.wy - 1)
 
-
     def on_mouseup(self, ev):
         ok = self.select_at_pos(ev.wy - 1)
         if ok:
             self.run_selected()
 
-
     def select_at_pos(self, pos):
         if pos < 0:
             return False
@@ -80,7 +74,6 @@
         self.redraw()
         return True
 
-
     def move_selected(self, offset):
         if self.selected:
             i = self.items.index(self.selected)
@@ -92,15 +85,10 @@
             self.selected = item
         self.redraw()
 
-
-    def activate(self, name):
-        self.handle('activate', name)
-
-
     def run_selected(self):
         if self.selected and self.selected[1] is not None:
             if isinstance(self.selected[1], str):
-                self.emit('activate', self.selected[1])
+                self.handle('activate', self.selected[1])
             elif isinstance(self.selected[1], Widget):
                 self.selected[1].show()
                 self.selected[1].setfocus()
--- a/tuikit/pager.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/pager.py	Fri Dec 14 10:20:14 2012 +0100
@@ -9,38 +9,38 @@
 
 class Pager(Container):
     '''Only one of children is visible at the time.
-    
+
     Pages are switched using buttons at top (tabs).
-    
+
     page_one = Container()
     pager.add(page_one, title="page one")
     pager.select(page_one)
-    
+
     '''
     def __init__(self, width=20, height=20):
         Container.__init__(self, width, height)
 
         # selected child (only one visible)
         self.selected = None
-        
+
         vert = VerticalLayout()
         self.layout(vert)
-        
+
         self.buttons = Container(20, 1)
         self.buttons.sizereq.h = 1
         Container.add(self, self.buttons)
         horz = HorizontalLayout(homogeneous=True, spacing=1)
         self.buttons.layout(horz)
-        
+
     def add(self, widget, **kw):
         Container.add(self, widget, expand=True, fill=True)
-        
+
         if self.selected is None:
             self.selected = widget
             widget.hidden = False
         else:
             widget.hidden = True
-        
+
         btn = Button(kw['title'])
         btn.connect('click', lambda: self.select(widget))
         self.buttons.add(btn, fill=True)
@@ -53,5 +53,5 @@
         child.hidden = False
         self.selected = child
 
-        self.resize()
+        self.handle('resize')
 
--- a/tuikit/scrollbar.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/scrollbar.py	Fri Dec 14 10:20:14 2012 +0100
@@ -21,7 +21,7 @@
         self.connect('mouseup', self.on_mouseup)
         self.connect('mousemove', self.on_mousemove)
 
-        self.addevents('change')
+        self.add_events('change')
 
 
     def setpos(self, pos):
--- a/tuikit/tableview.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/tableview.py	Fri Dec 14 10:20:14 2012 +0100
@@ -3,62 +3,62 @@
 import math
 import logging
 
-from tuikit.widget import EventSource, Widget
+from tuikit.eventsource import Emitter
+from tuikit.widget import Widget
 from tuikit.common import Coords
 
 
-class TableModel(EventSource):
+class TableModel(Emitter):
     def __init__(self, list_of_lists):
-        EventSource.__init__(self)
-        self.addevents('change')
+        self.add_events('change')
         self.data = list_of_lists
-    
+
     def getcount(self):
         '''Get number of rows.'''
         return len(self.data)
-    
+
     def getrows(self, begin, end):
         '''Get rows from begin to end, including begin, excluding end.'''
         return self.data[begin:end]
-    
+
     def update(self, row, col, val):
         self.data[row][col] = val
 
 
 class Column:
-    
+
     '''Columns description.'''
-    
+
     def __init__(self, title='', header=False, expand=True, sizereq=1,
         readonly=False, maxlength=None):
         '''Create column with default values.'''
-        
+
         self.title = title
         '''Title is displayed in heading before first row.'''
 
         self.header = header
         '''Header column is highlighted, values in this column cannot be edited.'''
-        
+
         self.expand = expand
         '''If true, this column will autoresize to consume any free space.'''
-        
+
         self.sizereq = sizereq
         '''Size request. Meaning depends on value of expand:
-        
+
         When false, sizereq is number of characters.
         When true, sizereq is relative size ratio.
-        
+
         '''
-        
+
         self.size = None
         '''Computed size of column.'''
-        
+
         self.index = None
         '''Computed index.'''
-        
+
         self.readonly = readonly
         '''If not readonly, values in this column can be changed by user.'''
-        
+
         self.maxlength = maxlength
         '''Maximum length of value (for EditField).'''
 
@@ -66,44 +66,44 @@
 class TableView(Widget):
     def __init__(self, model=None, width=20, height=20):
         Widget.__init__(self, width, height)
-        
+
         # model
         self._model = None
         self.setmodel(model)
-        
+
         self.columns = []
         self.spacing = 1
         self.rowcount = 0
         self.headsize = 1
-        
+
         self.offset = Coords()
         self.acell = Coords()
         '''Active cell (cursor).'''
-        
+
         self.connect('draw', self.on_draw)
         self.connect('keypress', self.on_keypress)
-        
-        self.addevents('scroll', 'areasize')
-    
+
+        self.add_events('scroll', 'areasize')
+
     def getmodel(self):
         return self._model
-    
+
     def setmodel(self, value):
         if self._model:
             self._model.disconnect('change', self.redraw)
         self._model = value
         if self._model:
             self._model.connect('change', self.redraw)
-    
+
     model = property(getmodel, setmodel)
-    
+
     def addcolumn(self, *args, **kwargs):
         for col in args:
             self.columns.append(col)
         if len(args) == 0:
             col = Column(**kwargs)
             self.columns.append(col)
-    
+
     def compute_column_sizes(self):
         total_space = self.size.w - self.spacing * len(self.columns)
         no_expand_cols = [col for col in self.columns if not col.expand]
@@ -111,11 +111,11 @@
         expand_cols = [col for col in self.columns if col.expand]
         expand_num = len(expand_cols)
         expand_size = total_space - no_expand_size
-        
+
         # compute size of cols without expand
         for col in no_expand_cols:
             col.size = col.sizereq
-            
+
         # compute size of cols with expand
         if no_expand_size > total_space + expand_num:
             for col in expand_cols:
@@ -130,21 +130,21 @@
                 if remaining_space > 0.99:
                     remaining_space -= 1.
                     col.size += 1
-        
+
         # compute indexes
         idx = 0
         for col in self.columns:
             if not col.header:
                 col.index = idx
                 idx += 1
-    
+
     def draw_head(self, screen, x, y):
         screen.pushcolor('strong')
         for col in self.columns:
             screen.puts(x, y, col.title[:col.size])
             x += col.size + self.spacing
         screen.popcolor()
-    
+
     def draw_row(self, screen, x, y, row, highlight):
         for col, data in zip(self.columns, row):
             if col.header:
@@ -156,9 +156,9 @@
             screen.puts(x, y, data[:col.size])
             screen.popcolor()
             x += col.size + self.spacing
-    
+
     def on_draw(self, screen, x, y):
-        screen.pushcolor('normal') 
+        screen.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)
@@ -171,7 +171,7 @@
                 highlight.append(self.acell.x)
             self.draw_row(screen, x, y, row, highlight)
             y += 1
-        screen.popcolor()        
+        screen.popcolor()
 
     def on_keypress(self, keyname, char):
         if keyname:
@@ -190,7 +190,7 @@
             yofs = 0
         self.offset.y = yofs
         self.handle('scroll')
-    
+
     def move_up(self):
         if self.acell.y > 0:
             self.acell.y -= 1
@@ -198,7 +198,7 @@
                 self.set_yofs(self.acell.y)
             return True
         return False
-    
+
     def move_down(self):
         log=logging.getLogger('tuikit')
         log.debug('height %d', self.height)
--- a/tuikit/treeview.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/treeview.py	Fri Dec 14 10:20:14 2012 +0100
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 
-from tuikit.eventsource import EventSource
+from tuikit.eventsource import Emitter
 from tuikit.widget import Widget
 
 
@@ -10,7 +10,7 @@
         self._index = 0
         self._stack = []
         self._collapsed = collapsed
-        
+
     def __next__(self):
         node = None
         while node is None:
@@ -31,11 +31,11 @@
         count = len(self._node)
 
         self._index += 1
-        
+
         self._stack.append((self._node, self._index))
         self._node = node
         self._index = 0
-        
+
         return (level, index, count, node)
 
 
@@ -44,18 +44,17 @@
         list.__init__(self)
         self.parent = parent
         self.name = name
-    
+
     def __eq__(self, other):
         # do not compare by list content
         return self is other
 
 
-class TreeModel(EventSource):
+class TreeModel(Emitter):
     def __init__(self):
-        EventSource.__init__(self)
-        self.addevents('change')
+        self.add_events('change')
         self.root = TreeNode()
-        
+
     def __iter__(self):
         return TreeIter(self.root)
 
@@ -82,34 +81,34 @@
                 if not found:
                     item = int(item)
                     node = node[item]
-        
+
         return node
 
     def add(self, path, names):
         node = self.find(path)
-        
+
         if isinstance(names, str):
             names = (names,)
-            
+
         for name in names:
             node.append(TreeNode(node, name))
-            
-        self.emit('change')
+
+        self.handle('change')
 
 
 class TreeView(Widget):
     def __init__(self, model=None, width=20, height=20):
         Widget.__init__(self, width, height)
-        
+
         # cursor
         self.cnode = None
 
         # model
         self._model = None
         self.setmodel(model)
-        
+
         self.collapsed = []
-        
+
         self.connect('draw', self.on_draw)
         self.connect('keypress', self.on_keypress)
 
@@ -132,7 +131,7 @@
                 pass
 
     model = property(getmodel, setmodel)
-    
+
     def model_change(self):
         if self.cnode is None:
             try:
@@ -144,7 +143,7 @@
     def collapse(self, path, collapse=True):
         node = self._model.find(path)
         self.collapse_node(node, collapse)
-    
+
     def collapse_node(self, node, collapse=True):
         if collapse:
             if not node in self.collapsed and len(node) > 0:
@@ -157,8 +156,8 @@
 
     def on_draw(self, screen, x, y):
         screen.pushcolor('normal')
-        
-        lines = 0  # bit array, bit 0 - draw vertical line on first column, etc. 
+
+        lines = 0  # bit array, bit 0 - draw vertical line on first column, etc.
         for level, index, count, node in self:
             # prepare string with vertical lines where they should be
             head = []
@@ -185,10 +184,10 @@
                 screen.pushcolor('active')
                 screen.puts(x + len(head), y, sep + node.name + ' ')
                 screen.popcolor()
-                
+
             y += 1
-        
-        screen.popcolor()        
+
+        screen.popcolor()
 
     def on_keypress(self, keyname, char):
         if keyname:
@@ -245,7 +244,7 @@
 
     def move_left(self):
         self.collapse_node(self.cnode, True)
-    
+
     def move_right(self):
         self.collapse_node(self.cnode, False)
 
--- a/tuikit/widget.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/widget.py	Fri Dec 14 10:20:14 2012 +0100
@@ -2,60 +2,57 @@
 
 import logging
 
-from tuikit.eventsource import EventSource
+from tuikit.eventsource import Emitter
 from tuikit.common import Coords, Size
 
 
-class Widget(EventSource):
+class Widget(Emitter):
 
     '''Base class for all widgets.'''
 
     def __init__(self, width = 10, height = 10):
-        '''Blah.'''
-        EventSource.__init__(self)
-
         #: Parent widget.
         self.parent = None
-        
+
         #: Top widget (same for every widget in one application).
         self.top = None
-        
+
         # Position inside parent widget. Modified by layout manager.
         self.position = Coords()
-        
+
         # Actual size. Modified by layout manager.
         self.size = Size(width, height)
-        
+
         #: Minimal size of widget. Under normal circumstances
         #: widget will never be sized smaller than this.
         #: Tuple (w, h). Both must be integers >= 1.
         self.sizemin = Size(1,1)
-        
+
         #: Maximum size of widget. Widget will never be sized bigger than this.
         #: Tuple (w, h). Integers >= 1 or None (meaning no maximum size or infinite).
         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).
         self.sizereq = Size(10,10)
-        
+
         #: When false, the widget is not considered in layout.
         self.allowlayout = True
-        
+
         #: Dictionary containing optional parameters for layout managers etc.
         self.hints = {}
-        
+
         #: Hidden widget does not affect layout.
         self.hidden = False
-        
+
         # cursor
         self.cursor = None
-        
+
         # redraw request
         self._redraw = True
-        
+
         # event handlers
-        self.addevents(
+        self.add_events(
             'resize',
             'draw',
             'focus',
@@ -65,6 +62,7 @@
             'mouseup',
             'mousemove',
             'mousewheel')
+        self.connect('resize', self.on_resize)
 
 
     @property
@@ -74,7 +72,7 @@
     @x.setter
     def x(self, value):
         self.position.x = value
-        #self.emit('resize')
+        #self.handle('resize')
 
     @property
     def y(self):
@@ -83,7 +81,7 @@
     @y.setter
     def y(self, value):
         self.position.y = value
-        #self.emit('resize')
+        #self.handle('resize')
 
 
     @property
@@ -93,7 +91,7 @@
     @width.setter
     def width(self, value):
         self.size.w = value
-        self.emit('resize')
+        self.handle('resize')
 
     @property
     def height(self):
@@ -102,7 +100,7 @@
     @height.setter
     def height(self, value):
         self.size.h = value
-        self.emit('resize')
+        self.handle('resize')
 
 
     def settop(self, top):
@@ -112,10 +110,9 @@
     ### events
 
 
-    def resize(self):
+    def on_resize(self):
         log = logging.getLogger('tuikit')
         log.debug('%r: resize', self)
-        self.handle('resize')
 
 
     def redraw(self, parent=True):
@@ -165,7 +162,7 @@
 
 
     def canfocus(self):
-        return bool(self.event['keypress'])
+        return bool(self._event_handlers['keypress'])
 
 
     def hasfocus(self):
--- a/tuikit/window.py	Fri Dec 14 10:16:33 2012 +0100
+++ b/tuikit/window.py	Fri Dec 14 10:20:14 2012 +0100
@@ -5,12 +5,12 @@
 
 
 class Window(Container):
-    
+
     '''Window widget.
-    
+
     It represents part of screen with border, close button and contents.
     Window can be moved, resized or closed by user.'''
-    
+
     def __init__(self, width=40, height=10):
         '''Create window of requested size.'''
         Container.__init__(self, width, height)
@@ -23,12 +23,12 @@
 
         #: Window title.
         self.title = ''
-        
+
         self._closebutton = True
-        
+
         #: Allow user to resize window.
         self.resizable = True
-        
+
         #: Allow user to move window.
         self.movable = True
 
@@ -94,7 +94,7 @@
             self.width = self.origsize[0] + ev.wx - self.dragstart[0]
             self.height = self.origsize[1] + ev.wy - self.dragstart[1]
             self.resizing = False
-            self.emit('resize')
+            self.handle('resize')
         elif self.moving:
             self.x = ev.px - self.dragstart[0]
             self.y = ev.py - self.dragstart[1]
@@ -114,7 +114,7 @@
         if self.resizing:
             self.width = self.origsize[0] + ev.wx - self.dragstart[0]
             self.height = self.origsize[1] + ev.wy - self.dragstart[1]
-            self.emit('resize')
+            self.handle('resize')
         elif self.moving:
             self.x = ev.px - self.dragstart[0]
             self.y = ev.py - self.dragstart[1]