Redraw widgets on request. Add scrollbar demo.
# -*- coding: utf-8 -*-
from tuikit.widget import Widget
from tuikit.common import Borders, Coords
import logging
class Container(Widget):
'''Container widget. Base for any widget which can contain other widgets.'''
def __init__(self):
'''Create container of requested size.'''
Widget.__init__(self)
#: List of child widgets.
self.children = []
self._hint_class = {}
self.focuschild = None
self.mousechild = None
self.allow_focus = True
#: Width of borders (left, top, right, bottom).
#: Child widgets are placed within borders.
self.borders = Borders()
self.widthrequest = (None, None)
self.heightrequest = (None, None)
self.colorprefix = None
self.trap_focus = False # if True, tab cycles inside container
def add(self, widget, **kwargs):
'''Add widget into this container.'''
self.children.append(widget)
widget.parent = self
widget.top = self.top
widget.reset_hints()
for key in kwargs:
widget.update_hint(key, kwargs[key])
if self.focuschild is None and widget.can_focus():
widget.set_focus()
def add_floater(self, widget, **kwargs):
widget.floater = True
widget._size.update(widget._sizereq)
self.add(widget, **kwargs)
def register_hints(self, *hints):
for hint_name, hint_class in zip(hints[::2], hints[1::2]):
self._hint_class[hint_name] = hint_class
def bring_up_child(self, child):
if child in self.children:
self.children.remove(child)
self.children.append(child)
def move_child(self, child, x, y):
pass
def _set_top(self, value):
self._top = value
for child in self.children:
child.top = value
def focus_next(self, step=1):
"""Focus next child.
Sets focus to next child, if there is one
which can be focused. Cycles from last child
to first when needed. Return value depends on
this cycling:
* False means there wasn't any child to focus
before end of list. Focus was either not changed
or first child was focused.
* True when focus is set to next child in normal
way or when self.trap_focus is set.
Return value is supposed to be returned from keypress
event - in that case, True stops event propagation.
"""
if self.focuschild is None:
idx_current = 0
else:
idx_current = self.children.index(self.focuschild)
idx_new = idx_current
cycled = False
while True:
idx_new += step
if idx_new >= len(self.children):
idx_new = 0
cycled = True
if idx_new < 0: # for focus_previous
idx_new = len(self.children) - 1
cycled = True
if idx_current == idx_new:
return False
if self.children[idx_new].can_focus():
self.children[idx_new].set_focus()
return self.trap_focus or not cycled
def focus_previous(self):
"""Focus previous child."""
self.focus_next(-1)
def on_focus(self, ev):
if self.focuschild:
self.focuschild.emit('focus', ev)
def on_unfocus(self, ev):
if self.focuschild:
self.focuschild.emit('unfocus', ev)
def redraw(self):
Widget.redraw(self)
for child in self.children:
child.redraw()
def draw(self, driver, x, y):
"""Draw the container and its children.
This method should not be overriden by subclasses,
use on_draw instead.
"""
if self.hidden:
return True
if self.colorprefix:
driver.pushcolorprefix(self.colorprefix)
Widget.draw(self, driver, x, 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 [ch for ch in self.children if not ch.floater]:
child.draw(driver,
x + child.x,
y + child.y)
driver.clipstack.pop()
# draw floaters - no clipping, no colorprefix
if self.parent is None:
self.draw_floaters(driver, x, y)
if self.colorprefix:
driver.popcolorprefix()
def draw_floaters(self, driver, x, y):
#logging.getLogger('tuikit').info('draw_floaters %s %r %r', self, x ,y )
# draw our floaters
for child in [ch for ch in self.children if ch.floater]:
child.draw(driver,
x + child.x,
y + child.y)
# delve into child containers, draw their floaters
for child in [ch for ch in self.children if not ch.floater and isinstance(ch, Container)]:
child.draw_floaters(driver,
x + child.x,
y + child.y)
def on_resize(self, ev):
for child in self.children:
child.emit('resize')
def on_keypress(self, ev):
if self.focuschild is not None and not self.focuschild.hidden:
if self.focuschild.emit('keypress', ev):
return True
if ev.keyname == 'tab':
return self.focus_next()
def on_mousedown(self, ev):
self.cascade_mouse_event(ev)
def on_mouseup(self, ev):
if self.mousechild:
child_ev = ev.make_child_event(self, self.mousechild)
self.mousechild.emit('mouseup', child_ev)
self.mousechild = None
return True
def on_mousemove(self, ev):
if self.mousechild:
child_ev = ev.make_child_event(self, self.mousechild)
self.mousechild.emit('mousemove', child_ev)
return True
def on_mousehover(self, ev):
self.cascade_mouse_event(ev)
def on_mousewheel(self, ev):
self.cascade_mouse_event(ev)
def cascade_mouse_event(self, ev):
"""Resend mouse event to child under cursor.
Args:
ev: Original mouse event. Event type and cursor
position is extracted from this.
Returns:
Boolean value. True when event was handled by a child.
"""
if self.parent is None:
if self.floaters_mouse_event(ev):
return True
for child in reversed([ch for ch in self.children if not ch.floater]):
if child.enclose(ev.wx, ev.wy):
child_ev = ev.make_child_event(self, child)
child.emit(ev.event_name, child_ev)
if ev.event_name == 'mousedown':
self.mousechild = child
return True
return False
def floaters_mouse_event(self, ev):
"""Resend mouse event to our floaters and any floaters in child containers."""
# delve into child containers, test their floaters
for child in reversed([ch for ch in self.children \
if not ch.floater and isinstance(ch, Container)]):
child_ev = ev.make_child_event(self, child)
if child.floaters_mouse_event(child_ev):
if ev.event_name == 'mousedown':
self.mousechild = child
return True
# test our floaters
for child in reversed([ch for ch in self.children if ch.floater]):
if child.enclose(ev.wx, ev.wy):
child_ev = ev.make_child_event(self, child)
child.emit(ev.event_name, child_ev)
if ev.event_name == 'mousedown':
self.mousechild = child
return True
def complete_requests(self):
Widget.complete_requests(self)
for child in self.children:
child.complete_requests()