Add ScrollView widget. Update Emitter, rename "on_event" methods to "_handle_event". Update VScrollbar, Layout.
authorRadek Brich <radek.brich@devl.cz>
Wed, 26 Dec 2012 01:00:31 +0100
changeset 40 5faa38c10b67
parent 39 5e5deb1d3945
child 41 37b7dfc3eae6
Add ScrollView widget. Update Emitter, rename "on_event" methods to "_handle_event". Update VScrollbar, Layout.
demo_menu.py
demo_treeview.py
tuikit/__init__.py
tuikit/button.py
tuikit/checkbox.py
tuikit/combobox.py
tuikit/common.py
tuikit/container.py
tuikit/editbox.py
tuikit/editfield.py
tuikit/emitter.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_menu.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/demo_menu.py	Wed Dec 26 01:00:31 2012 +0100
@@ -19,7 +19,7 @@
         self.top.add(helpwin)
         helpwin.x = 10
         helpwin.y = 5
-        helpwin.allowlayout = False
+        helpwin.allow_layout = False
         helpwin.hidden = True
         helpwin.title = 'About'
         #helpwin.closebutton = False
--- a/demo_treeview.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/demo_treeview.py	Wed Dec 26 01:00:31 2012 +0100
@@ -19,14 +19,20 @@
         model.add(['a', 'd', 'e'], 'g')
         model.add('/a/d/f', 'h')
 
+        for i in range(100):
+            model.add('/b', ['x'+str(i)])
+
         view = TreeView(model)
-        view.collapse('/a/d')
-        self.top.add(view)
+        #view.collapse('/a/d')
+
+        scroll = ScrollView()
+        scroll.add(view)
+        self.top.add(scroll, expand=True, fill=True)
 
         vert = VerticalLayout()
         self.top.layout(vert)
 
-    def globalkeypress(self, keyname, char):
+    def globalkeypress(self, eo, keyname, char):
         if keyname == 'escape':
             self.terminate()
 
--- a/tuikit/__init__.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/__init__.py	Wed Dec 26 01:00:31 2012 +0100
@@ -14,6 +14,7 @@
 from tuikit.menubar import MenuBar
 from tuikit.pager import Pager
 from tuikit.scrollbar import VScrollbar
+from tuikit.scrollview import ScrollView
 from tuikit.tableview import TableModel, TableView
 from tuikit.textedit import TextEdit
 from tuikit.treeview import TreeNode, TreeModel, TreeView
--- a/tuikit/button.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/button.py	Wed Dec 26 01:00:31 2012 +0100
@@ -35,8 +35,8 @@
         self.add_events('click')
 
 
-    def on_draw(self, screen, x, y):
-        super().on_draw(screen, x, y)
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         pad = self.width - len(self.label) - len(self.prefix) - len(self.suffix)
         lpad, rpad = self._divide_padding(pad)
         screen.pushcolor(self.getcolor())
@@ -56,28 +56,24 @@
         screen.puts(x + pos, y, self.suffix)
         screen.popcolor()
 
-
-    def on_mousedown(self, ev):
-        super().on_mousedown(ev)
+    def _handle_mousedown(self, ev):
+        super()._handle_mousedown(ev)
         self.highlight = True
         self.redraw()
 
-
-    def on_mouseup(self, ev):
-        super().on_mouseup(ev)
+    def _handle_mouseup(self, ev):
+        super()._handle_mouseup(ev)
         self.highlight = False
         self.redraw()
 
         if self.enclose(ev.px, ev.py):
             self.emit('click')
 
-
-    def on_keypress(self, keyname, char):
-        super().on_keypress(keyname, char)
+    def _handle_keypress(self, keyname, char):
+        super()._handle_keypress(keyname, char)
         if keyname == 'enter':
             self.emit('click')
 
-
     def getcolor(self):
         if self.highlight or self.has_focus():
             return self.bghi
--- a/tuikit/checkbox.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/checkbox.py	Wed Dec 26 01:00:31 2012 +0100
@@ -18,8 +18,8 @@
         self.bg = 'normal'
         self.bghi = 'active'
 
-    def on_click(self):
-        super().on_click()
+    def _handle_click(self):
+        super()._handle_click()
         if self.checked:
             self.checked = False
             self.prefix = '[ ] '
--- a/tuikit/combobox.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/combobox.py	Wed Dec 26 01:00:31 2012 +0100
@@ -27,7 +27,7 @@
 
         self._menu = Menu(items)
         self._menu.hide()
-        self._menu.allowlayout = False
+        self._menu.allow_layout = False
         #self.top.add(self._menu)
 
     def _on_btn_click(self):
--- a/tuikit/common.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/common.py	Wed Dec 26 01:00:31 2012 +0100
@@ -1,5 +1,7 @@
 # -*- coding: utf-8 -*-
 
+from tuikit.emitter import Emitter
+
 
 class Coords:
 
@@ -27,7 +29,7 @@
         return 'Coords(x={0.x},y={0.y})'.format(self)
 
 
-class Size:
+class Size(Emitter):
 
     '''Size class.
 
@@ -37,8 +39,27 @@
     '''
 
     def __init__(self, w=None, h=None):
-        self.w = w
-        self.h = h
+        self._w = w
+        self._h = h
+        self.add_events('change')
+
+    @property
+    def w(self):
+        return self._w
+
+    @w.setter
+    def w(self, value):
+        self._w = value
+        self.emit('change')
+
+    @property
+    def h(self):
+        return self._h
+
+    @h.setter
+    def h(self, value):
+        self._h = value
+        self.emit('change')
 
     def __getitem__(self, key):
         try:
@@ -55,7 +76,7 @@
             self.h = value
 
     def __repr__(self):
-        return 'Size(w={0.w},h={0.h})'.format(self)
+        return 'Size(w={0._w},h={0._h})'.format(self)
 
 
 class Rect:
--- a/tuikit/container.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/container.py	Wed Dec 26 01:00:31 2012 +0100
@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 
 from tuikit.widget import Widget
-from tuikit.common import Borders
+from tuikit.common import Borders, Coords
 
 import logging
 
@@ -17,6 +17,9 @@
         #: List of child widgets.
         self.children = []
 
+        #: Offset for child widgets
+        self.offset = Coords()
+
         self.focuschild = None
         self.mousechild = None
 
@@ -36,12 +39,12 @@
         self.trap_focus = False  # if True, tab cycles inside container
 
 
-    def add(self, widget, **kw):
+    def add(self, widget, **kwargs):
         '''Add widget into this container.'''
         self.children.append(widget)
         widget.parent = self
         widget.settop(self.top)
-        widget.hints.update(kw)
+        widget.hints.update(kwargs)
         if self.focuschild is None and widget.can_focus():
             self.focuschild = widget
 
@@ -50,7 +53,6 @@
         '''Set layout manager for placing child widgets.'''
         self._layout = layout
         layout.container = self
-        self.connect('resize', layout.resize)
 
 
     def settop(self, top):
@@ -90,8 +92,8 @@
                 return self.trap_focus or not cycled
             idx_new += 1
 
-    def on_keypress(self, keyname, char):
-        super().on_keypress(keyname, char)
+    def _handle_keypress(self, keyname, char):
+        super()._handle_keypress(keyname, char)
         if self.focuschild is not None:
             handled = self.focuschild.emit('keypress', keyname, char)
             if handled:
@@ -99,8 +101,8 @@
         if keyname == 'tab':
             return self.focus_next()
 
-    def on_resize(self):
-        super().on_resize()
+    def _handle_resize(self):
+        super()._handle_resize()
         for child in self.children:
             child.emit('resize')
 
@@ -121,14 +123,16 @@
 
         super().draw(driver, x, y)
 
-        for child in [x for x in self.children if not x.allowlayout]:
+        for child in [x for x in self.children if not x.allow_layout]:
             child.draw(driver, x + child.x, y + child.y)
 
         l, t, r, b = self.borders
         driver.clipstack.push(x+l, y+t, self.width-l-r, self.height-t-b)
 
-        for child in [x for x in self.children if x.allowlayout]:
-            child.draw(driver, x + child.x, y + child.y)
+        for child in [x for x in self.children if x.allow_layout]:
+            child.draw(driver,
+                x + self.offset.x + child.x,
+                y + self.offset.y + child.y)
 
         driver.clipstack.pop()
 
@@ -137,8 +141,8 @@
         driver.clipstack.pop()
 
 
-    def on_mousedown(self, ev):
-        super().on_mousedown(ev)
+    def _handle_mousedown(self, ev):
+        super()._handle_mousedown(ev)
         handled = False
         for child in reversed(self.children):
             if child.enclose(ev.wx, ev.wy):
@@ -149,23 +153,23 @@
                 break
         return handled
 
-    def on_mouseup(self, ev):
-        super().on_mouseup(ev)
+    def _handle_mouseup(self, ev):
+        super()._handle_mouseup(ev)
         if self.mousechild:
             childev = ev.childevent(self.mousechild)
             self.mousechild.emit('mouseup', childev)
             self.mousechild = None
             return True
 
-    def on_mousemove(self, ev):
-        super().on_mousemove(ev)
+    def _handle_mousemove(self, ev):
+        super()._handle_mousemove(ev)
         if self.mousechild:
             childev = ev.childevent(self.mousechild)
             self.mousechild.emit('mousemove', childev)
             return True
 
-    def on_mousewheel(self, ev):
-        super().on_mousewheel(ev)
+    def _handle_mousewheel(self, ev):
+        super()._handle_mousewheel(ev)
         handled = False
         for child in reversed(self.children):
             if child.enclose(ev.wx, ev.wy):
--- a/tuikit/editbox.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/editbox.py	Wed Dec 26 01:00:31 2012 +0100
@@ -25,8 +25,8 @@
         self.set_text(text)
 
 
-    def on_draw(self, screen, x, y):
-        super().on_draw(screen, x, y)
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         for j in range(self.height):
             if self.yofs + j >= len(self.lines):
                 break
@@ -40,8 +40,8 @@
         self.cursor = (self.get_cpos() - self.xofs, self.cline - self.yofs)
 
 
-    def on_keypress(self, keyname, char):
-        super().on_keypress(keyname, char)
+    def _handle_keypress(self, keyname, char):
+        super()._handle_keypress(keyname, char)
         if keyname:
             if keyname == 'left':
                 self.move_left()
@@ -86,8 +86,8 @@
         self.redraw()
 
 
-    def on_mousewheel(self, ev):
-        super().on_mousewheel(ev)
+    def _handle_mousewheel(self, ev):
+        super()._handle_mousewheel(ev)
         # up
         if ev.button == 4:
             self.move_up()
--- a/tuikit/editfield.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/editfield.py	Wed Dec 26 01:00:31 2012 +0100
@@ -22,16 +22,12 @@
         self.pos = len(value)      # position of cursor in value
         self.ofs = 0      # position of value beginning on screen
 
-        self.connect('resize', self.on_resize)
-        self.connect('draw', self.on_draw)
-        self.connect('keypress', self.on_keypress)
-
-
-    def on_resize(self):
+    def _handle_resize(self):
+        super()._handle_resize()
         self.tw = self.width - 2
 
-
-    def on_draw(self, screen, x, y):
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         screen.pushcolor('normal')
         # draw value
         val = self.value + ' ' * self.tw         # add spaces to fill rest of field
@@ -52,7 +48,8 @@
         self.cursor = (1 + self.pos - self.ofs, 0)
         screen.popcolor()
 
-    def on_keypress(self, keyname, char):
+    def _handle_keypress(self, keyname, char):
+        super()._handle_keypress(keyname, char)
         handled = False
         if keyname:
             handled = True
@@ -82,7 +79,6 @@
 
         return handled
 
-
     def move_left(self):
         if self.pos - self.ofs > 1 or (self.ofs == 0 and self.pos == 1):
             # move cursor
@@ -93,7 +89,6 @@
                 self.pos -= 1
                 self.ofs -= 1
 
-
     def move_right(self):
         if self.pos < len(self.value):
             if self.pos - self.ofs < self.tw - 2 \
@@ -105,11 +100,9 @@
                 self.pos += 1
                 self.ofs += 1
 
-
     def add_char(self, c):
         self.value = self.value[:self.pos] + c + self.value[self.pos:]
 
-
     def del_char(self):
         self.value = self.value[:self.pos] + self.value[self.pos+1:]
 
--- a/tuikit/emitter.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/emitter.py	Wed Dec 26 01:00:31 2012 +0100
@@ -19,7 +19,10 @@
             self._event_handlers = dict()
         for event_name in event_names:
             self._event_handlers[event_name] = []
-            setattr(Emitter, 'on_' + event_name, lambda *args, **kwargs: False)
+            # 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 *args, **kwargs: False)
 
     def connect(self, event_name, handler):
         """Connect event handler to event name.
@@ -72,11 +75,11 @@
             event_name,
             self.__class__.__name__,
             getattr(self, 'name', None) or id(self))
-        handled = getattr(self, 'on_' + event_name)(*args, **kwargs)
+        handled = getattr(self, '_handle_' + event_name)(*args, **kwargs)
         if handled:
             return True
         for handler in self._event_handlers[event_name]:
-            handled = handler(*args, **kwargs)
+            handled = handler(self, *args, **kwargs)
             if handled:
                 return True
 
--- a/tuikit/label.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/label.py	Wed Dec 26 01:00:31 2012 +0100
@@ -8,12 +8,10 @@
         Widget.__init__(self, len(label), 1)
 
         self.label = label
-        self.connect('draw', self.on_draw)
 
-
-    def on_draw(self, screen, x, y):
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         screen.pushcolor('normal')
         screen.puts(x, y, self.label)
         screen.popcolor()
 
-
--- a/tuikit/layout.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/layout.py	Wed Dec 26 01:00:31 2012 +0100
@@ -13,9 +13,24 @@
 
 
 class Layout:
+    def __init__(self):
+        self._container = None
+
+    @property
+    def container(self):
+        return self._container
+
+    @container.setter
+    def container(self, value):
+        self._container = value
+        self._container.connect('resize', self._on_container_resize)
+
+    def _on_container_resize(self, eo):
+        self.resize()
+
     def _getchildren(self):
         return [child for child in self.container.children
-            if child.allowlayout and not child.hidden]
+            if child.allow_layout and not child.hidden]
 
     def _getregion(self):
         c = self.container
@@ -25,6 +40,7 @@
 
 class LinearLayout(Layout):
     def __init__(self, homogeneous=False, spacing=0):
+        super().__init__()
         self.homogeneous = homogeneous
         self.spacing = spacing
 
@@ -93,6 +109,7 @@
 
 class GridLayout(Layout):
     def __init__(self, numcols=2):
+        super().__init__()
         self.numcols = numcols
 
 
--- a/tuikit/menu.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/menu.py	Wed Dec 26 01:00:31 2012 +0100
@@ -17,15 +17,10 @@
         self.selected = items[0]
         self.menubar = None
 
-        self.connect('draw', self.on_draw)
-        self.connect('keypress', self.on_keypress)
-        self.connect('mousedown', self.on_mousedown)
-        self.connect('mousemove', self.on_mousemove)
-        self.connect('mouseup', self.on_mouseup)
-
         self.add_events('activate')
 
-    def on_draw(self, screen, x, y):
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         screen.pushcolor(self.bg)
         screen.frame(x, y, self.width, self.height)
         i = 1
@@ -43,7 +38,8 @@
             i += 1
         screen.popcolor()
 
-    def on_keypress(self, keyname, char):
+    def _handle_keypress(self, keyname, char):
+        super()._handle_keypress(keyname, char)
         if keyname == 'up':
             self.move_selected(-1)
         if keyname == 'down':
@@ -52,13 +48,16 @@
             self.run_selected()
         self.redraw()
 
-    def on_mousedown(self, ev):
+    def _handle_mousedown(self, ev):
+        super()._handle_mousedown(ev)
         self.select_at_pos(ev.wy - 1)
 
-    def on_mousemove(self, ev):
+    def _handle_mousemove(self, ev):
+        super()._handle_mousemove(ev)
         self.select_at_pos(ev.wy - 1)
 
-    def on_mouseup(self, ev):
+    def _handle_mouseup(self, ev):
+        super()._handle_mousemove(ev)
         ok = self.select_at_pos(ev.wy - 1)
         if ok:
             self.run_selected()
--- a/tuikit/menubar.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/menubar.py	Wed Dec 26 01:00:31 2012 +0100
@@ -15,13 +15,6 @@
         self.setitems(items)
         self.selected = None
 
-        self.connect('draw', self.on_draw)
-        self.connect('keypress', self.on_keypress)
-        self.connect('mousedown', self.on_mousedown)
-        self.connect('mousemove', self.on_mousemove)
-        self.connect('unfocus', self.on_unfocus)
-
-
     def setitems(self, items):
         self.items = items
 
@@ -30,14 +23,14 @@
             if isinstance(item[1], Widget):
                 item[1].x = i
                 item[1].y = self.y + 1
-                item[1].allowlayout = False
+                item[1].allow_layout = False
                 item[1].hidden = True
                 item[1].connect('focus', self.on_submenu_focus)
                 item[1].menubar = self
             i += len(item[0]) + 4
 
-
-    def on_draw(self, screen, x, y):
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         screen.pushcolor(self.bg)
         i = 0
         for item in self.items:
@@ -53,7 +46,8 @@
         screen.popcolor()
 
 
-    def on_keypress(self, keyname, char):
+    def _handle_keypress(self, keyname, char):
+        super()._handle_draw(keyname, char)
         if keyname == 'left':
             self.move_selected(-1)
         elif keyname == 'right':
@@ -72,8 +66,8 @@
             self.select(item)
         self.redraw()
 
-
-    def on_mousedown(self, ev):
+    def _handle_mousedown(self, ev):
+        super()._handle_mousedown(ev)
         i = 0
         self.unselect()
         for item in self.items:
@@ -82,28 +76,25 @@
                 self.select(item)
             i += w
 
-
-    def on_mousemove(self, ev):
+    def _handle_mousemove(self, ev):
+        super()._handle_mousemove(ev)
         self.on_mousedown(ev)
 
-
-    def on_unfocus(self, newfocus):
+    def _handle_unfocus(self, newfocus):
+        super()._handle_unfocus()
         if self.selected and newfocus == self.selected[1]:
             return
         self.unselect()
 
-
     def on_submenu_focus(self):
         self.set_focus()
 
-
     def select(self, item):
         self.selected = item
         if isinstance(item[1], Widget):
             item[1].hidden = False
             item[1].redraw()
 
-
     def unselect(self):
         if self.selected:
             if isinstance(self.selected[1], Widget):
--- a/tuikit/pager.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/pager.py	Wed Dec 26 01:00:31 2012 +0100
@@ -17,6 +17,7 @@
     pager.select(page_one)
 
     '''
+
     def __init__(self, width=20, height=20):
         Container.__init__(self, width, height)
 
--- a/tuikit/scrollbar.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/scrollbar.py	Wed Dec 26 01:00:31 2012 +0100
@@ -7,9 +7,9 @@
     def __init__(self, height=10):
         Widget.__init__(self, 1, height)
 
-        self.max = height - 3
-        self.pos = 0
-        self.thumbpos = 0
+        self._max = height - 3
+        self._pos = 0
+        self._thumbpos = 0
 
         self.interval = 0.1
 
@@ -18,25 +18,47 @@
 
         self.add_events('change')
 
+    @property
+    def max(self):
+        """Maximum for scrolling position."""
+        return self._max
 
-    def setpos(self, pos):
-        self.pos = pos
-        self.thumbpos = 0
-        if self.max and self.pos <= self.max:
-            self.thumbpos = int(round(self.pos / self.max * (self.height - 3)))
+    @max.setter
+    def max(self, value):
+        self._max = value
+        self._update_thumbpos()
+
+    @property
+    def pos(self):
+        """Scrolling position.
+
+        Integer number between 0 and 'max'.
+
+        """
+        return self._pos
 
+    @pos.setter
+    def pos(self, value):
+        self._pos = value
+        self._update_thumbpos()
+        self.emit('change')
 
-    def on_draw(self, screen, x, y):
-        super().on_draw(screen, x, y)
+    def _update_thumbpos(self):
+        self._thumbpos = 0
+        if self._max and self._pos <= self._max:
+            self._thumbpos = int(round(self._pos / self._max * (self.height - 3)))
+        self.redraw()
+
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         screen.putch(x, y, screen.unigraph.UP_ARROW)
         for i in range(y + 1, y + self.height - 1):
             screen.putch(x, i, screen.unigraph.LIGHT_SHADE)
-        screen.putch(x, y + 1 + self.thumbpos, screen.unigraph.BLOCK)
+        screen.putch(x, y + 1 + self._thumbpos, screen.unigraph.BLOCK)
         screen.putch(x, y + self.height - 1, screen.unigraph.DOWN_ARROW)
 
-
-    def on_mousedown(self, ev):
-        super().on_mousedown(ev)
+    def _handle_mousedown(self, ev):
+        super()._handle_mousedown(ev)
         self.dragging = False
         self.move = None
         # arrow buttons
@@ -48,13 +70,12 @@
             self.top.add_timeout(self.interval * 2, self._timeout)
             return
         # thumb bar
-        if ev.wy == 1 + self.thumbpos:
+        if ev.wy == 1 + self._thumbpos:
             self.dragging = True
             return
 
-
-    def on_mouseup(self, ev):
-        super().on_mouseup(ev)
+    def _handle_mouseup(self, ev):
+        super()._handle_mouseup(ev)
         if self.dragging:
             self.drag(ev.wy)
             self.dragging = False
@@ -64,13 +85,11 @@
             self.move = None
             return
 
-
-    def on_mousemove(self, ev):
-        super().on_mousemove(ev)
+    def _handle_mousemove(self, ev):
+        super()._handle_mousemove(ev)
         if self.dragging:
             self.drag(ev.wy)
 
-
     def _timeout(self):
         if self.move == 'up':
             self.move_up()
@@ -78,31 +97,22 @@
             self.move_down()
         self.top.add_timeout(self.interval, self._timeout)
 
-
     def move_up(self):
-        if self.pos > 0:
-            self.setpos(self.pos - 1)
+        if self._pos > 0:
+            self.pos = self._pos - 1
         self.move = 'up'
-        self.redraw()
-        self.emit('change')
-
 
     def move_down(self):
-        if self.pos < self.max:
-            self.setpos(self.pos + 1)
+        if self._pos < self._max:
+            self.pos = self._pos + 1
         self.move = 'down'
-        self.redraw()
-        self.emit('change')
-
 
     def drag(self, wy):
-        newpos = int(round((wy - 1) / (self.height - 3) * self.max))
+        newpos = int(round((wy - 1) / (self.height - 3) * self._max))
         if newpos < 0:
             newpos = 0
-        if newpos > self.max:
-            newpos = self.max
-        if self.pos != newpos:
-            self.setpos(newpos)
-            self.redraw()
-            self.emit('change')
+        if newpos > self._max:
+            newpos = self._max
+        if self._pos != newpos:
+            self.pos = newpos
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tuikit/scrollview.py	Wed Dec 26 01:00:31 2012 +0100
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+
+from tuikit.container import Container
+from tuikit.scrollbar import VScrollbar
+
+
+class ScrollView(Container):
+    """Scrolling view
+
+    Shows scrollbars when needed.
+    Scrolling area is determined based on child widgets
+    size request.
+
+    """
+
+    def __init__(self, width=20, height=20):
+        Container.__init__(self, width, height)
+
+        self.vscroll = VScrollbar(height)
+        self.vscroll.connect('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)
+
+    def _handle_resize(self):
+        super()._handle_resize()
+        self.vscroll.x = self.size.w - 1
+        self.vscroll.height = self.height
+        self._update_vscroll_max()
+
+    def _on_vscroll_change(self, eo):
+        self.offset.y = - self.vscroll.pos
+
+    def _on_child_sizereq(self, eo):
+        self._update_vscroll_max()
+
+    def _update_vscroll_max(self):
+        max_height = 0
+        for child in self.children:
+            if child == self.vscroll:
+                continue
+            child_height = child.x + child.sizereq.h
+            if child_height > max_height:
+                max_height = child_height
+        if max_height < self.size.h:
+            self.vscroll.hide()
+        else:
+            self.vscroll.max = max_height - self.size.h
+            self.vscroll.show()
+
--- a/tuikit/tableview.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/tableview.py	Wed Dec 26 01:00:31 2012 +0100
@@ -156,8 +156,8 @@
             screen.popcolor()
             x += col.size + self.spacing
 
-    def on_draw(self, screen, x, y):
-        super().on_draw(screen, x, y)
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         screen.pushcolor('normal')
         self.rowcount = self.model.getcount()
         numrows = min(self.rowcount - self.offset.y, self.size.h - self.headsize)
@@ -173,8 +173,8 @@
             y += 1
         screen.popcolor()
 
-    def on_keypress(self, keyname, char):
-        super().on_keypress(keyname, char)
+    def _handle_keypress(self, keyname, char):
+        super()._handle_keypress(keyname, char)
         if keyname:
             if keyname == 'up':  self.move_up()
             if keyname == 'down':  self.move_down()
--- a/tuikit/textedit.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/textedit.py	Wed Dec 26 01:00:31 2012 +0100
@@ -32,8 +32,8 @@
         self.editbox.move_pagelast()
 
 
-    def on_draw(self, screen, x, y):
-        super().on_draw(screen, x, y)
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         screen.frame(x, y, self.width, self.height)
 
 
--- a/tuikit/treeview.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/treeview.py	Wed Dec 26 01:00:31 2012 +0100
@@ -181,8 +181,6 @@
 
         # model
         self._model = None
-        if model:
-            self.model = model
 
         self.collapsed = []
 
@@ -190,6 +188,9 @@
             'expand',   # node expanded, the affected node is given in args
             'collapse') # node collapsed, the affected node is given in args
 
+        if model:
+            self.model = model
+
     def __iter__(self):
         return TreeIter(self._model.root, self.collapsed)
 
@@ -209,10 +210,12 @@
                 self.cnode = self._model.root.children[0]
             except IndexError:
                 pass
+            self._update_sizereq()
 
     def on_model_node_added(self, node):
         if self.cnode is None:
             self.cnode = node
+        self._update_sizereq()
         self.redraw()
 
     def collapse(self, path, collapse=True):
@@ -230,9 +233,10 @@
                 self.emit('expand', node)
             except ValueError:
                 pass
+        self._update_sizereq()
 
-    def on_draw(self, driver, x, y):
-        super().on_draw(driver, x, y)
+    def _handle_draw(self, driver, x, y):
+        super()._handle_draw(driver, x, y)
         driver.pushcolor('normal')
 
         lines = 0  # bit array, bit 0 - draw vertical line on first column, etc.
@@ -267,8 +271,8 @@
 
         driver.popcolor()
 
-    def on_keypress(self, keyname, char):
-        super().on_keypress(keyname, char)
+    def _handle_keypress(self, keyname, char):
+        super()._handle_keypress(keyname, char)
         if keyname:
             if keyname == 'up':    self.move_up()
             if keyname == 'down':  self.move_down()
@@ -326,3 +330,9 @@
     def move_right(self):
         self.collapse_node(self.cnode, False)
 
+    def _update_sizereq(self):
+        height = 0
+        for num, _ in enumerate(self, start=1):
+            height = num
+        self.sizereq.h = height
+
--- a/tuikit/widget.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/widget.py	Wed Dec 26 01:00:31 2012 +0100
@@ -27,7 +27,7 @@
         #: 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)
+        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).
@@ -35,10 +35,11 @@
 
         #: 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)
+        self._sizereq = Size(10, 10)
+        self._sizereq.connect('change', lambda eo: self.emit('sizereq'))
 
         #: When false, the widget is not considered in layout.
-        self.allowlayout = True
+        self.allow_layout = True
 
         #: Allow keyboard focus for this widget.
         self.allow_focus = False
@@ -58,6 +59,7 @@
         # event handlers
         self.add_events(
             'resize',
+            'sizereq',
             'draw',
             'focus',
             'unfocus',
@@ -103,6 +105,9 @@
         self.size.h = value
         self.emit('resize')
 
+    @property
+    def sizereq(self):
+        return self._sizereq
 
     def settop(self, top):
         self.top = top
@@ -134,8 +139,7 @@
             else:
                 driver.hidecursor()
 
-    def on_mousedown(self, ev):
-        super().on_mousedown(ev)
+    def _handle_mousedown(self, ev):
         self.set_focus()
 
 
--- a/tuikit/window.py	Mon Dec 17 23:27:37 2012 +0100
+++ b/tuikit/window.py	Wed Dec 26 01:00:31 2012 +0100
@@ -32,10 +32,10 @@
         self.moving = False
 
         self.closebtn = Button('x')
-        self.closebtn.allowlayout = False
+        self.closebtn.allow_layout = False
         self.closebtn.x = self.width - 5
         self.closebtn.width = 3
-        self.closebtn.connect('click', self.on_closebtn)
+        self.closebtn.connect('click', self.on_closebtn_click)
         self.closebtn.bg = 'controls'
         self.closebtn.bghi = 'controls-active'
         self.add(self.closebtn)
@@ -55,8 +55,8 @@
         self.closebtn.hidden = not value
 
 
-    def on_draw(self, screen, x, y):
-        super().on_draw(screen, x, y)
+    def _handle_draw(self, screen, x, y):
+        super()._handle_draw(screen, x, y)
         screen.pushcolor('normal')
         screen.frame(x, y, self.width, self.height)
 
@@ -75,8 +75,8 @@
         screen.popcolor()
 
 
-    def on_mousedown(self, ev):
-        handled = super().on_mousedown(ev)
+    def _handle_mousedown(self, ev):
+        handled = super()._handle_mousedown(ev)
         if handled:
             return True
 
@@ -90,8 +90,8 @@
         self.redraw(True)
 
 
-    def on_mouseup(self, ev):
-        handled = super().on_mouseup(ev)
+    def _handle_mouseup(self, ev):
+        handled = super()._handle_mouseup(ev)
         if handled:
             return True
 
@@ -108,8 +108,8 @@
         self.redraw(True)
 
 
-    def on_mousemove(self, ev):
-        handled = super().on_mousemove(ev)
+    def _handle_mousemove(self, ev):
+        handled = super()._handle_mousemove(ev)
         if handled:
             return True
 
@@ -131,12 +131,12 @@
         self.redraw(True)
 
 
-    def on_resize(self):
-        super().on_resize()
+    def _handle_resize(self):
+        super()._handle_resize()
         self.closebtn.x = self.width - 5
 
 
-    def on_closebtn(self):
+    def on_closebtn_click(self):
         self.hide()