Clean up, refactoring. Rename EventSource to Emitter, begin merging emit() method with handle().
--- 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]