# HG changeset patch # User Radek Brich # Date 1312110279 -7200 # Node ID 7175ed629a76e3a9b5bbe8025851d2f35ff14f51 # Parent fcaabd81777453fea693eaaad632e30361c5f795 Added ComboBox, HorizontalLayout, TreeNode, TreeModel, TreeView. Widget is now descendant of EventSource. Improved color management (color prefixes). diff -r fcaabd817774 -r 7175ed629a76 docs/events.rst --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/docs/events.rst Sun Jul 31 13:04:39 2011 +0200 @@ -0,0 +1,16 @@ +Event handling +============== + +Keyboard event propagation +-------------------------- + +top widget -> focuswidget -> focuswidget's parent -> focuswidget's parent's parent -> ...and so forth + +1 top window +4 window +3 container +2 edit box + + +Other event propagation +----------------------- diff -r fcaabd817774 -r 7175ed629a76 docs/focus.rst --- a/docs/focus.rst Wed Apr 13 13:07:26 2011 +0200 +++ b/docs/focus.rst Sun Jul 31 13:04:39 2011 +0200 @@ -14,5 +14,6 @@ hide() -> unfocus tab/shift-tab into / out off containers? +trapfocus # if True, tab cycles inside container widget.hasfocus() diff -r fcaabd817774 -r 7175ed629a76 tuikit/__init__.py --- a/tuikit/__init__.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/__init__.py Sun Jul 31 13:04:39 2011 +0200 @@ -2,15 +2,17 @@ from tuikit.application import Application from tuikit.button import Button +from tuikit.combobox import ComboBox from tuikit.common import Rect from tuikit.container import Container from tuikit.editbox import EditBox from tuikit.editfield import EditField from tuikit.label import Label -from tuikit.layout import VerticalLayout, GridLayout +from tuikit.layout import VerticalLayout, HorizontalLayout, GridLayout from tuikit.menu import Menu from tuikit.menubar import MenuBar from tuikit.scrollbar import VScrollbar from tuikit.textedit import TextEdit +from tuikit.treeview import TreeNode, TreeModel, TreeView from tuikit.widget import Widget from tuikit.window import Window diff -r fcaabd817774 -r 7175ed629a76 tuikit/application.py --- a/tuikit/application.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/application.py Sun Jul 31 13:04:39 2011 +0200 @@ -23,10 +23,12 @@ self.connect('draw', self.on_draw) + def keypress(self, keyname, char): + if self.handle('keypress', keyname, char): + return if self.focuswidget and self.focuswidget != self: self.focuswidget.emit('keypress', keyname, char) - self.handle('keypress', keyname, char) def on_draw(self, screen, x, y): @@ -118,10 +120,10 @@ def applytheme(self): screen = self.screen - screen.setcolor('normal', 'white on blue') - screen.setcolor('window', 'white on blue') - screen.setcolor('window-controls', 'white on blue, bold') - screen.setcolor('window-controls-active', 'cyan on blue, bold') + screen.setcolor('normal', 'white on black') + screen.setcolor('window:normal', 'white on blue') + screen.setcolor('window:controls', 'white on blue, bold') + screen.setcolor('window:controls-active', 'cyan on blue, bold') screen.setcolor('button', 'black on white') screen.setcolor('button-active', 'black on cyan') screen.setcolor('menu', 'black on cyan') diff -r fcaabd817774 -r 7175ed629a76 tuikit/backend_curses.py --- a/tuikit/backend_curses.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/backend_curses.py Sun Jul 31 13:04:39 2011 +0200 @@ -90,6 +90,7 @@ self.colors = {} # maps names to curses attributes self.colorpairs = {} # maps tuple (fg,bg) to curses color_pair self.colorstack = [] # pushcolor/popcolor puts or gets attributes from this + self.colorprefix = [] # stack of color prefixes self.inputqueue = [] self.mbtnstack = [] @@ -174,7 +175,7 @@ return Rect(x, y, w, h) - ## attributes ## + ## colors, attributes ## def _parsecolor(self, name): name = name.lower().strip() @@ -218,6 +219,11 @@ def pushcolor(self, name): + # add prefix if available + if len(self.colorprefix): + prefixname = self.colorprefix[-1] + name + if prefixname in self.colors: + name = prefixname attr = self.colors[name] self.screen.attrset(attr) self.colorstack.append(attr) @@ -232,6 +238,14 @@ self.screen.attrset(attr) + def pushcolorprefix(self, name): + self.colorprefix.append(name) + + + def popcolorprefix(self): + self.colorprefix.pop() + + ## drawing ## def putch(self, x, y, c): diff -r fcaabd817774 -r 7175ed629a76 tuikit/button.py --- a/tuikit/button.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/button.py Sun Jul 31 13:04:39 2011 +0200 @@ -21,8 +21,9 @@ self.connect('draw', self.on_draw) self.connect('mousedown', self.on_mousedown) self.connect('mouseup', self.on_mouseup) + self.connect('keypress', self.on_keypress) - self.newevent('click') + self.addevents('click') def on_draw(self, screen, x, y): @@ -51,8 +52,13 @@ self.handle('click') + def on_keypress(self, keyname, char): + if keyname == 'enter': + self.handle('click') + + def getcolor(self): - if self.highlight: + if self.highlight or self.hasfocus(): return self.bghi return self.bg diff -r fcaabd817774 -r 7175ed629a76 tuikit/combobox.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tuikit/combobox.py Sun Jul 31 13:04:39 2011 +0200 @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +from .container import Container +from .editfield import EditField +from .button import Button +from .menu import Menu + + +class ComboBox(Container): + def __init__(self, width=15, value='', items=[]): + Container.__init__(self, width, 1) + + self._edit = EditField(width - 3, value) + self.add(self._edit) + + self._btn = Button('v') + self._btn.x = width - 3 + self._btn.width = 3 + self._btn.connect('click', self._on_btn_click) + self.add(self._edit) + + self._menu = Menu(items) + self._menu.hide() + self._menu.allowlayout = False +# self.top.add(self._menu) + + + def _on_btn_click(self): + pass + + diff -r fcaabd817774 -r 7175ed629a76 tuikit/container.py --- a/tuikit/container.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/container.py Sun Jul 31 13:04:39 2011 +0200 @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from .widget import Widget - +import logging class Container(Widget): def __init__(self, width = 10, height = 10): @@ -14,6 +14,10 @@ self.widthrequest = (None, None) self.heightrequest = (None, None) + self.colorprefix = None + + self.trapfocus = False # if True, tab cycles inside container + def add(self, widget, **kw): self.children.append(widget) @@ -34,6 +38,26 @@ child.settop(top) + def focusnext(self): + i = self.children.index(self.top.focuswidget) + while True: + i += 1 + if i >= len(self.children): + i = 0 + if self.children[i].canfocus(): + self.children[i].setfocus() + break + log = logging.getLogger('tuikit') + log.debug(str(self.top.focuswidget.__class__)) + + + def keypress(self, keyname, char): + if keyname == 'tab': + self.focusnext() + return + Widget.keypress(self, keyname, char) + + def resize(self): Widget.resize(self) for child in self.children: @@ -45,6 +69,8 @@ return screen.pushclip(x, y, self.width, self.height) + if self.colorprefix: + screen.pushcolorprefix(self.colorprefix) Widget.draw(self, screen, x, y) @@ -59,6 +85,8 @@ screen.popclip() + if self.colorprefix: + screen.popcolorprefix() screen.popclip() diff -r fcaabd817774 -r 7175ed629a76 tuikit/editbox.py --- a/tuikit/editbox.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/editbox.py Sun Jul 31 13:04:39 2011 +0200 @@ -21,8 +21,7 @@ self.connect('keypress', self.on_keypress) self.connect('mousewheel', self.on_mousewheel) - self.newevent('scroll') - self.newevent('areasize') + self.addevents('scroll', 'areasize') self.set_text(text) diff -r fcaabd817774 -r 7175ed629a76 tuikit/editfield.py --- a/tuikit/editfield.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/editfield.py Sun Jul 31 13:04:39 2011 +0200 @@ -50,27 +50,35 @@ def on_keypress(self, keyname, char): + handled = False if keyname: + handled = True if keyname == 'left': self.move_left() - if keyname == 'right': + elif keyname == 'right': self.move_right() - if keyname == 'backspace': + elif keyname == 'backspace': if self.pos > 0: self.move_left() self.del_char() - if keyname == 'delete': + elif keyname == 'delete': self.del_char() + else: + handled = False + if char: self.add_char(char) self.move_right() + handled = True self.redraw() + return handled + def move_left(self): if self.pos - self.ofs > 1 or (self.ofs == 0 and self.pos == 1): diff -r fcaabd817774 -r 7175ed629a76 tuikit/eventsource.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tuikit/eventsource.py Sun Jul 31 13:04:39 2011 +0200 @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + + +class EventSource: + + def __init__(self): + self.event = dict() + + def addevents(self, *events): + '''Create new events with empty handler list.''' + for event in events: + self.event[event] = [] + + 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) + else: + raise KeyError('Event %s not known.', event) + + def disconnect(self, event, handler=None): + '''Remove handler from event's handler list. + + If no handler is given, remove all handlers. + + ''' + if event in list(self.event.keys()): + if handler: + self.event[event].remove(handler) + else: + self.event[event] = [] + else: + raise KeyError('Event %s not known.', event) + + def handle(self, event, *args, **kwargs): + '''Call all handlers from event's handler list. + + This is used when user defined handlers are to be called. + + ''' + handled = False + for handler in self.event[event]: + res = handler(*args, **kwargs) + if res: + handled = True + return handled + + def emit(self, event, *args, **kwargs): + '''Emit event. + + This is used by original event source when the event is detected. + + ''' + try: + getattr(self, event)(*args, **kwargs) + except AttributeError: + self.handle(event, *args, **kwargs) diff -r fcaabd817774 -r 7175ed629a76 tuikit/layout.py --- a/tuikit/layout.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/layout.py Sun Jul 31 13:04:39 2011 +0200 @@ -18,6 +18,8 @@ v = 0 c = self.container bl, bt, br, bb = c.borders + + last = None for child in c.children: if not child.allowlayout: continue @@ -26,6 +28,29 @@ child.y = bt + v v += child.height child.handle('resize') + last = child + + if last and v < c.height - bt - bb: + last.height += c.height - bt - bb - v + + c.redraw() + + +class HorizontalLayout: + def resize(self): + v = 0 + c = self.container + bl, bt, br, bb = c.borders + + for child in c.children: + if not child.allowlayout: + continue + child.y = bt + child.height = c.height - bt - bb + child.x = bl + v + v += child.width + child.handle('resize') + c.redraw() diff -r fcaabd817774 -r 7175ed629a76 tuikit/menu.py --- a/tuikit/menu.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/menu.py Sun Jul 31 13:04:39 2011 +0200 @@ -20,6 +20,8 @@ self.connect('mousedown', self.on_mousedown) self.connect('mousemove', self.on_mousemove) self.connect('mouseup', self.on_mouseup) + + self.addevents('activate') def on_draw(self, screen, x, y): @@ -90,12 +92,18 @@ 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], Widget): + if type(self.selected[1]) is str: + self.emit('activate', self.selected[1]) + elif isinstance(self.selected[1], Widget): self.selected[1].show() self.selected[1].setfocus() else: - self.menubar.unfocus() + self.menubar.resetfocus() self.selected[1]() diff -r fcaabd817774 -r 7175ed629a76 tuikit/scrollbar.py --- a/tuikit/scrollbar.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/scrollbar.py Sun Jul 31 13:04:39 2011 +0200 @@ -21,7 +21,7 @@ self.connect('mouseup', self.on_mouseup) self.connect('mousemove', self.on_mousemove) - self.newevent('change') + self.addevents('change') def setpos(self, pos): diff -r fcaabd817774 -r 7175ed629a76 tuikit/treeview.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tuikit/treeview.py Sun Jul 31 13:04:39 2011 +0200 @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- + +from .eventsource import EventSource +from .widget import Widget + +class TreeIter: + def __init__(self, model): + self.model = model + self._node = self.model.root + self._index = 0 + self._stack = [] + + def __next__(self): + node = None + while node is None: + try: + node = self._node[self._index] + if node is None: + raise Exception('Bad node: None') + except IndexError: + if len(self._stack): + self._node, self._index = self._stack.pop() + else: + raise StopIteration + + name = node.name + level = len(self._stack) + 1 + count = len(self._node) + + self._index += 1 + + self._stack.append((self._node, self._index)) + self._node = node + self._index = 0 + + return (level, count, name) + + +class TreeNode(list): + def __init__(self, name=''): + list.__init__(self) + self.name = name + + +class TreeModel(EventSource): + def __init__(self): + EventSource.__init__(self) + self.addevents('change') + self.root = TreeNode() + + def __iter__(self): + return TreeIter(self) + + def add(self, path, names): + if isinstance(path, str): + path = path.split('/') + # strip empty strings from both ends + while path and path[0] == '': + del path[0] + while path and path[-1] == '': + del path[-1] + + node = self.root + for item in path: + if isinstance(item, int): + node = node[item] + else: + found = False + for subnode in node: + if subnode.name == item: + node = subnode + found = True + break + if not found: + item = int(item) + node = node[item] + + if isinstance(names, str): + names = (names,) + + for name in names: + node.append(TreeNode(name)) + + self.emit('change') + + +class TreeView(Widget): + def __init__(self, width=20, height=20, model=None): + Widget.__init__(self, width, height) + self._model = None + self.setmodel(model) + self.connect('draw', self.on_draw) + + def getmodel(self): + '''TreeModel in use by this TreeView.''' + 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 on_draw(self, screen, x, y): + screen.pushcolor('normal') + + self.draw_children(self._model.root, screen, x, y) + + screen.popcolor() + + #screen.VLINE + #screen.LTEE + #screen.LLCORNER + + + def draw_children(self, parent, screen, x, y): + orig_y = y + for node in parent: + screen.puts(x, y, '- ' + node.name) + y += 1 + if len(node): + y += self.draw_children(node, screen, x + 2, y) + return y - orig_y + diff -r fcaabd817774 -r 7175ed629a76 tuikit/widget.py --- a/tuikit/widget.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/widget.py Sun Jul 31 13:04:39 2011 +0200 @@ -2,9 +2,16 @@ import curses +from .eventsource import EventSource -class Widget: + +class Widget(EventSource): + + """Base class for all widgets.""" + def __init__(self, width = 10, height = 10): + EventSource.__init__(self) + self.parent = None self.top = None # placing - set by parent widget's layout manager @@ -24,17 +31,16 @@ self._redraw = True self.hidden = False # event handlers - self.event = { - 'resize' : [], - 'draw' : [], - 'focus' : [], - 'unfocus' : [], - 'keypress' : [], - 'mousedown' : [], - 'mouseup' : [], - 'mousemove' : [], - 'mousewheel' : [], - } + self.addevents( + 'resize', + 'draw', + 'focus', + 'unfocus', + 'keypress', + 'mousedown', + 'mouseup', + 'mousemove', + 'mousewheel') @property @@ -60,37 +66,7 @@ self.top = top - ### event management - - - def newevent(self, event): - self.event[event] = [] - - - def connect(self, event, handler): - if event in list(self.event.keys()): - self.event[event] += [handler] - - - def disconnect(self, event, handler=None): - if event in list(self.event.keys()): - if handler: - i = self.event[event].index(handler) - del self.event[event][i] - else: - self.event[event] = [] - - - def handle(self, event, *args, **kwargs): - for handler in self.event[event]: - handler(*args, **kwargs) - - - def emit(self, event, *args, **kwargs): - getattr(self, event)(*args, **kwargs) - - - ### + ### events def resize(self): @@ -124,26 +100,26 @@ return bool(self.event['keypress']) + def hasfocus(self): + return self.top.focuswidget == self + + def setfocus(self): - if self.hasfocus(): + if self.hasfocus() or not self.canfocus(): return if self.top.focuswidget: - self.top.focuswidget.unfocus(self) + self.top.focuswidget.resetfocus() self.top.focuswidget = self self.emit('focus') - def unfocus(self): + def resetfocus(self): if self.top.focuswidget != self: return self.top.focuswidget = None self.emit('unfocus') - def hasfocus(self): - return self.top.focuswidget == self - - def focus(self): '''handle focus event''' self.handle('focus') @@ -154,11 +130,13 @@ self.handle('unfocus', newfocus) - ### + ### events def keypress(self, keyname, char): - self.handle('keypress', keyname, char) + handled = self.handle('keypress', keyname, char) + if not handled and self.parent and self.parent != self.top: + self.parent.emit('keypress', keyname, char) def mousedown(self, ev): @@ -178,6 +156,9 @@ self.handle('mousewheel', ev) + ### + + def enclose(self, x, y): if self.hidden: return False diff -r fcaabd817774 -r 7175ed629a76 tuikit/window.py --- a/tuikit/window.py Wed Apr 13 13:07:26 2011 +0200 +++ b/tuikit/window.py Sun Jul 31 13:04:39 2011 +0200 @@ -27,11 +27,12 @@ self.closebtn.x = self.width - 5 self.closebtn.width = 3 self.closebtn.connect('click', self.on_closebtn) - self.closebtn.bg = 'window-controls' - self.closebtn.bghi = 'window-controls-active' + self.closebtn.bg = 'controls' + self.closebtn.bghi = 'controls-active' self.add(self.closebtn) self.borders = (1, 1, 1, 1) + self.colorprefix = 'window:' @property @@ -45,14 +46,14 @@ def on_draw(self, screen, x, y): - screen.pushcolor('window') + screen.pushcolor('normal') screen.frame(x, y, self.width, self.height) if self.resizable: if self.resizing: - screen.pushcolor('window-controls-active') + screen.pushcolor('controls-active') else: - screen.pushcolor('window-controls') + screen.pushcolor('controls') screen.puts(x + self.width - 2, y + self.height - 1, '─┘') # '━┛' screen.popcolor()