tuikit/core/container.py
author Radek Brich <radek.brich@devl.cz>
Sun, 22 Feb 2015 09:53:13 +0100
changeset 119 dd91747504dd
parent 118 8c7970520632
permissions -rw-r--r--
Redraw widgets on request. Add scrollbar demo.

from tuikit.core.widget import Widget
from tuikit.core.coords import Point, Rect
from tuikit.layouts.fixed import FixedLayout


class Container(Widget):

    """Container widget.

    Can contain other widgets to create hierarchical structure.

    """

    def __init__(self, layout_class=FixedLayout):
        Widget.__init__(self)
        #: List of child widgets.
        self._widgets = []
        #: Widget with keyboard focus
        self._focus_widget = None
        #: Widget on last mouse position
        self.mouse_widget = None
        #: If True, tab cycles inside container
        self.trap_focus = False
        self.layout = layout_class()

    def add(self, widget, *args, **kwargs):
        """Add widget into container."""
        self._widgets.append(widget)
        widget.parent = self
        widget.window = self.window
        widget.set_theme(self.theme)
        widget.timer = self.timer
        self.layout.add(widget, *args, **kwargs)
        if self._focus_widget is None and widget.can_focus():
            self._focus_widget = widget
        widget.redraw()

    def resize(self, w, h):
        Widget.resize(self, w, h)
        self.layout.update(w, h)

    # -------------------------------------------------------------------------
    #   Presentation

    def draw_if_needed(self, buffer):
        """Draw this and child widgets, whichever was requested by redraw."""
        Widget.draw_if_needed(self, buffer)
        for child in self._widgets:
            with buffer.moved_origin(child.x, child.y):
                with buffer.clip(buffer.origin.x, buffer.origin.y,
                                 child.width, child.height):
                    child.draw_if_needed(buffer)

    def redraw(self):
        """Request redraw of all widgets in container."""
        Widget.redraw(self)
        for child in self._widgets:
            child.redraw()

    def set_theme(self, theme):
        Widget.set_theme(self, theme)
        for child in self._widgets:
            child.set_theme(theme)

    @property
    def cursor(self):
        """Return cursor coordinates or None if cursor is not set
        or is set outside of widget boundaries.

        If this container has child with focus,
        return its cursor position instead.

        """
        if self.focus_widget:
            cursor = self.focus_widget.cursor
            if not cursor:
                return None
            cursor = cursor.moved(*self.focus_widget.pos)
        else:
            cursor = self._cursor.immutable()
        if cursor in Rect(0, 0, *self._size):
            return cursor

    @property
    def cursor_visible(self):
        if self.focus_widget:
            return self.focus_widget.cursor_visible
        else:
            return self._cursor_visible

    # -------------------------------------------------------------------------
    #   Events

    def keypress_event(self, ev):
        # First, handle the keypress event to focused child widget
        if self.focus_widget is not None:
            if self.focus_widget.keypress_event(ev):
                return True
        # Next, handle default key behaviour by Container
        if ev.keyname == 'tab':
            return self.focus_next(-1 if 'shift' in ev.mods else 1)
        # Finally, handle default keys by Widget
        # and send keypress signal
        if Widget.keypress_event(self, ev):
            return True

    def mousedown_event(self, ev):
        self.mouse_widget = None
        for child in reversed(self._widgets):
            if ev.pos in child.boundaries:
                child.mousedown_event(ev.rebase(child.pos))
                self.mouse_widget = child

    def mouseup_event(self, ev):
        if self.mouse_widget:
            self.mouse_widget.mouseup_event(ev.rebase(self.mouse_widget.pos))

    def mousemove_event(self, ev):
        if self.mouse_widget:
            self.mouse_widget.mousemove_event(ev.rebase(self.mouse_widget.pos))

    # -------------------------------------------------------------------------
    #   Focus

    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.focus_widget is None:
            idx_current = 0
        else:
            idx_current = self._widgets.index(self.focus_widget)
        idx_new = idx_current
        cycled = False
        while True:
            idx_new += step
            if idx_new >= len(self._widgets):
                idx_new = 0
                cycled = True
            if idx_new < 0:  # for focus_previous
                idx_new = len(self._widgets) - 1
                cycled = True
            if idx_current == idx_new:
                return False
            if self._widgets[idx_new].can_focus():
                self.focus_widget = self._widgets[idx_new]
                return self.trap_focus or not cycled

    def focus_previous(self):
        """Focus previous child."""
        self.focus_next(-1)

    @property
    def focus_widget(self):
        return self._focus_widget

    @focus_widget.setter
    def focus_widget(self, widget):
        orig_focus_widget = self._focus_widget
        self._focus_widget = widget
        widget.redraw()
        if orig_focus_widget:
            orig_focus_widget.redraw()