# $Id: Container.py,v 1.22.2.1 2006/08/17 17:06:33 marcusva Exp $
#
# Copyright (c) 2004-2006, Marcus von Appen
# All rights reserved.
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#  * Redistributions of source code must retain the above copyright notice,
#    this list of conditions and the following disclaimer.
#  * Redistributions in binary form must reproduce the above copyright notice,
#    this list of conditions and the following disclaimer in the documentation
#    and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

"""An abstract widget, which can hold other widgets."""

from BaseWidget import BaseWidget

class Container (BaseWidget):
    """Container () -> Container

    A container widget class, which can hold multiple other widgets.

    The Container class is an abstract class, which can hold multiple
    widgets. It is usable to serve as container for various types of
    widgets and allows inheritors to use their own look.

    The 'children' attribute is a list of the widgets added to the
    Container.

    children = container.children           # get the list of children
    container.add_child (widget)            # add a widget
    container.add_child (widget1, widget2)  # add multiple widgets at once
    container.remove_child (widget)         # remove a widget
    container.children = my_list_of_widgets # Set multiple widgets at once.

    The 'padding' attribute and set_padding() method are used to place a
    certain amount of pixels between the children and the outer edges of
    the Container.

    container.padding = 10
    container.set_padding (10)

    An additional amount of pixels can be placed between the widgets
    using the 'spacing' attribute or set_spacing() method. Dependant on
    the inherited Container class, this places the given amount of
    pixels between the children.

    container.spacing = 10
    container.set_spacing (10)

    Default action (invoked by activate()):
    None
    
    Mnemonic action (invoked by activate_mnemonic()):
    None

    Attributes:
    children - List of widgets packed into the Container.
    spacing  - Spacing in pixels between the children.
    padding  - Padding between the borders and the children of the
               Container.
    """
    def __init__ (self):
        BaseWidget.__init__ (self)
        self._children = []
        self._spacing = 2
        self._padding = 2

    def set_depth (self, depth):
        """C.set_depth (...) -> None

        Sets the depth of the Container.

        Sets the depth of the Container and its children to the given
        value.
        """
        self.lock ()
        BaseWidget.set_depth (self, depth)
        for child in self.children:
            child.set_depth (depth)
        self.unlock ()

    def set_spacing (self, spacing):
        """C.set_spacing (...) -> None

        Sets the spacing between the children of the Container.

        The spacing value is the amount of pixels to place between the
        children of the Container.

        Raises a TypeError, if the passed argument is not a positive
        integer.
        """
        if (type (spacing) != int) or (spacing < 0):
            raise TypeError ("spacing must be a positive integer")
        self._spacing = spacing

    def set_padding (self, padding):
        """C.set_padding (...) -> None

        Sets the padding between the edges and children of the Container.
        
        The padding value is the amount of pixels to place between the
        edges of the Container and its child widgets.

        Raises a TypeError, if the passed argument is not a positive
        integer.
        """
        if (type (padding) != int) or (padding < 0):
            raise TypeError ("padding must be a positive integer")
        self._padding = padding

    def set_indexable (self, indexable):
        """C.set_indexable (...) -> None

        Sets the indexable of the Container.

        Adds the Container to an IIndexable implementation and causes its
        children to be added to the same, too.
        """
        BaseWidget.set_indexable (self, indexable)
        for child in self.children:
            child.set_indexable (indexable)

    def set_event_manager (self, manager):
        """C.set_event_manager (...) -> None

        Sets the event manager of the Container.

        Adds the Container to an event manager and causes its children
        to be added to the same, too.
        """
        BaseWidget.set_event_manager (self, manager)
        for child in self.children:
            child.set_event_manager (manager)
    
    def set_sensitive (self, sensitive=True):
        """C.set_sensitive (...) -> None

        Sets the sensitivity of the Container and its children.
        """
        BaseWidget.set_sensitive (self, sensitive)
        for child in self.children:
            child.set_sensitive (sensitive)

    def _add (self, **kwargs):
        """C._add (...) -> None

        Internal add method for child additions to the container.
        """
        children = kwargs.get ("children", [])
        insert = kwargs.get ("insert", False)
        pos = kwargs.get ("pos", 0)
        
        for child in children:
            if not isinstance (child, BaseWidget):
                raise TypeError ("Widget %s must inherit from BaseWidget"
                                 % child)
            if child.parent:
                raise Exception ("Widget %s already packed into another"
                                 "Container" % child)
            child.parent = self

            if (child.depth != self.depth):
                child.set_depth (self.depth)

            if not self.sensitive:
                child.set_sensitive (self.sensitive)

            if insert:
                self.children.insert (pos, child)
            else:
                self.children.append (child)

            if (self.manager != None) and (child.manager == None):
                child.manager = self.manager

            if (self.indexable != None) and (child.indexable == None):
                child.indexable = self.indexable
        
        self.dirty = True
        
    def add_child (self, *children):
        """C.add_child (...) -> None

        Adds one or more children to the Container.
        
        Adds one or more children to the Container and updates the
        parent-child relationships.
        
        Raises a TypeError, if one of the passed arguments does not
        inherit from the BaseWidget class.
        Raises an Exception, if one of the passed arguments is already
        attached to another parent.
        """
        self._add (children=children)
        
    def remove_child (self, child):
        """C.remove_child (...) -> None

        Removes a child from the Container.
        
        Removes the child from the Container and updates the
        parent-child relationship of the child.

        Raises a TypeError, if the passed argument does not inherit from
        the BaseWidget class.
        """
        if not isinstance (child, BaseWidget):
            raise TypeError ("child must inherit from BaseWidget")
        self.children.remove (child)
        child.parent = None
        self.dirty = True

    def insert_child (self, pos, *children):
        """C.insert_child (...) -> None

        Inserts one or more children at the desired position.

        Inserts one or more children at the desired position to the
        Container and updates the parent-child relationships.
        
        Raises a TypeError, if one of the passed arguments does not
        inherit from the BaseWidget class.
        Raises an Exception, if one of the passed arguments is already
        attached to another parent.
        """
        self._add (children=children, insert=True, pos=pos)

    def set_children (self, children):
        """C.set_children (...) -> None

        Sets the children of the Container.

        Sets the children of the Container to the passed list of
        widgets. If the Container already contains children, they will
        be removed first.

        Raises a TypeError, if one of the passed arguments does not
        inherit from the BaseWidget class.
        Raises an Exception, if one of the passed arguments is already
        attached to another parent.
        """
        while len (self._children) > 0:
            self.remove_child (self._children[0])
        if (children != None):
            self._add (children=children)

    def destroy (self):
        """C.destroy () -> None

        Destroys the Container and removes it from its event system.
        """
        _pop = self.children.pop
        while len (self.children) > 0:
            widget = _pop ()
            widget.parent = None
            widget.destroy ()
            del widget
        # del self.children
        BaseWidget.destroy (self)

    def update (self, **kwargs):
        """C.update (...) -> None

        Updates the Container and refreshes its image and rect content.

        Updates the Container and causes its parent to update itself on
        demand.
        """
        children = kwargs.get ("children", {})
        resize = kwargs.get ("resize", False)

        # We have to check for possible size changes here!
        if resize:
            self.dirty = True
        elif self.locked:
            return
        else:
            BaseWidget.update (self, children=children, resize=resize)

    spacing = property (lambda self: self._spacing,
                        lambda self, var: self.set_spacing (var),
                        doc = "The spacing between the children.")
    padding = property (lambda self: self._padding,
                        lambda self, var: self.set_padding (var),
                        doc = "The additional padding for the Container.")
    children = property (lambda self: self._children,
                         lambda self, var: self.set_children (var),
                         doc = "List of the children for the Container.")
