tuikit/container.py
author Radek Brich <radek.brich@devl.cz>
Sun, 22 Feb 2015 09:53:13 +0100
changeset 119 dd91747504dd
parent 77 fc1989059e19
child 98 dcfb185ac866
permissions -rw-r--r--
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()