# HG changeset patch # User Radek Brich # Date 1355476814 -3600 # Node ID 088b92ffb11988004b9cef01739fc43558a2aec4 # Parent 629d5edb16023cf33e238b9e3bc6ee3bd7dbab1d Clean up, refactoring. Rename EventSource to Emitter, begin merging emit() method with handle(). diff -r 629d5edb1602 -r 088b92ffb119 tuikit/application.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) diff -r 629d5edb1602 -r 088b92ffb119 tuikit/button.py --- 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): diff -r 629d5edb1602 -r 088b92ffb119 tuikit/container.py --- 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): diff -r 629d5edb1602 -r 088b92ffb119 tuikit/editbox.py --- 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) diff -r 629d5edb1602 -r 088b92ffb119 tuikit/eventsource.py --- 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 diff -r 629d5edb1602 -r 088b92ffb119 tuikit/menu.py --- 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() diff -r 629d5edb1602 -r 088b92ffb119 tuikit/pager.py --- 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') diff -r 629d5edb1602 -r 088b92ffb119 tuikit/scrollbar.py --- 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): diff -r 629d5edb1602 -r 088b92ffb119 tuikit/tableview.py --- 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) diff -r 629d5edb1602 -r 088b92ffb119 tuikit/treeview.py --- 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) diff -r 629d5edb1602 -r 088b92ffb119 tuikit/widget.py --- 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): diff -r 629d5edb1602 -r 088b92ffb119 tuikit/window.py --- 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]