###########################################################################
# cgkit - Python Computer Graphics Kit
# Copyright (C) 2004 Matthias Baas (baas@ira.uka.de)
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# cgkit homepage: http://cgkit.sourceforge.net
###########################################################################
# $Id: panels.py,v 1.2 2006/01/27 07:52:40 mbaas Exp $

import wx
import cgkit.cmds
#from cgkit import getApp, pluginmanager
from cgkit import pluginmanager
import re
import panelicons

HORIZONTAL = 0x01
VERTICAL   = 0x02

# Exceptions:

class DuplicateNames(Exception):
    """Exception.

    Raised when there's a name clash between two nodes."""
    pass

class LayoutError(Exception):
    """Exception.

    Raises when a widget is added to a layout that already manages this
    widget."""
    pass

######################################################################

# LayoutRepository
class LayoutRepository(object):
    def __init__(self):
        self._layouts = []


# PanelWidgetRepository
class PanelWidgetRepository(object):
    """Stores all panel widgets that have been created.
    """
    
    def __init__(self):
        """Constructor."""
        self._widgets = []

    def __iter__(self):
        """Return an iterator that iterates over all widgets.
        """
        return iter(self._widgets)

    def insert(self, widget):
        """Inserts a widget into the repository.

        Nothing happens if the widget is already present.
        
        \param widget (\c PanelWidget) Widget that should be inserted into the repository
        """

        if widget not in self._widgets:
            self._widgets.append(widget)

    

# PanelWidget
class PanelWidget(object):
    """Container for a wx widget that serves as a panel widget.

    It's not allowed to have several %PanelWidget object share the same
    wx object.

    Attributes:

    - wx (\c wx \c widget)
    - name (\c str) The name of the widget (this mirrors the name of the wx widget)
    """
    def __init__(self, wx=None):
        """Constructor.

        \param wx (\c wx \c widget) The managed wx widget
        """
        self._wx = wx
        self._active = False
        self._usedby = []

    def __str__(self):
        return '<%s "%s">'%(self.__class__.__name__, self.name)

    # isActive
    def isActive(self):
        """Check if the widget is currently active.

        \return True if active
        """
        return self._active

    # activate
    def activate(self, root):
        """Activate the node.

        This method marks the widget as active.

        Derived classes may use this method to create the wx widget. In
        this case they have to use root.wx as wx parent.        

        \param root (\c Panels) The panels object where the widget is located.
        """
        self._active = True

    # deactivate
    def deactivate(self):
        """Deactivate the node.

        This method marks the widget as inactive.
        """
        self._active = False

    # isInUse
    def isInUse(self):
        """Returns whether the widget is used by any layout or not.

        \return True if the widget is used by a layout (be it active or not).
        """
        return self._usedby!=[]

    # isUsedBy
    def isUsedBy(self, layoutroot):
        """Checks if the widget is used by the given layout.

        \param layoutroot (\c ILayoutNode) The root node of the layout in question
        """
        for n in self._usedby:
            if layoutroot==n._layoutRoot():
                return True
        return False

    # acquire
    def acquire(self, panelnode):
        """Mark the widget as being used by the given node.

        \param panelnode (\c PanelNode) The panel node that controls the position and size of the widget
        """
        self._usedby.append(panelnode)

    # release
    def release(self, panelnode):
        """Mark the widget as being no longer in use by the given node.

        \param panelnode (\c PanelNode) The panel node that was controlling the position and size of the widget
        """        
        self._usedby.remove(panelnode)

    # hide
    def hide(self):
        if self._wx!=None:
            self._wx.Hide()

    # show
    def show(self):
        if self._wx!=None:
            self._wx.Show()

    def setGeom(self, x, y, w, h):
        if self._wx!=None:
            self._wx.SetDimensions(x,y,w,h)
        

    ######################################################################
    ## protected:
    
    # "wx" property...
    
    def _getWx(self):
        """Return the encapsulated wx widget.

        This method is used for retrieving the \a wx property.

        \return wxPython object.
        """
        return self._wx

    def _setWx(self, widget):
        """Set the wx widget.

        This method is used for setting the \a wx property.

        \param widget (\c wx object) wx Widget
        """
        self._wx = widget

    wx = property(_getWx, _setWx, None, "Encapsulated wx widget")

    # "name" property...
    
    def _getName(self):
        """Return the name of the widget.

        This method is used for retrieving the \a name property.

        \return Name (\c str)
        """
        if self._wx==None:
            return ""
        else:
            return str(self._wx.GetName())

    def _setName(self, name):
        """Set the name of the widget.

        This method is used for setting the \a name property.

        \param name (\c str) New name
        """
        if self._wx!=None:
            self._wx.SetName(name)

    name = property(_getName, _setName, None, "Widget name")

######################################################################
    
# Panels
class Panels(object):
    """%Panels area.

    Attributes:

    - \a layout (\c ILayoutNode): Panel layout tree
    - \a wx (\c wx widget): Corresponding wx widget
    - \a repository (\c PanelWidgetRepository) Widget repository

    Panel nodes can be accessed in one of the following ways:

    - The attribute \a mousepanel contains the panel that's currently
      underneath the mouse pointer (or None if the mouse is outside)
    - The attribute \a activepanel contains the currently active panel
      (i.e. the one that's activatable and that received the last mouse
      click)
    - Via attribute access you can address the panels by their name
    - You can iterate over all panel nodes
    

    """
        
    def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.CLIP_CHILDREN):
        """Constructor.
        """
#        wx.Window.__init__(self, parent, id, pos, size, style)

        self._wx = wx.Window(parent, id, pos, size, style)

        self._repository = PanelWidgetRepository()

        self._layout = LayoutNode(root=self)

        self.mousepanel = None
        self.activepanel = None

        self._xclick = None
        self._yclick = None
        self._xsplitter = None
        self._ysplitter = None
        self._splitter_flags = None
        self._layout_node = None

        self._current_cursor = None
        self._hcrsr = wx.StockCursor(wx.CURSOR_SIZEWE)
        self._vcrsr = wx.StockCursor(wx.CURSOR_SIZENS)
        self._hvcrsr = wx.StockCursor(wx.CURSOR_SIZING)

        wx.EVT_SIZE(self._wx, self.onSize)
        wx.EVT_LEFT_DOWN(self._wx, self.onLeftDown)
        wx.EVT_LEFT_UP(self._wx, self.onLeftUp)
        wx.EVT_MOTION(self._wx, self.onMouseMove)
        wx.EVT_PAINT(self._wx, self.onPaint)

#    def __repr__(self):
#        res = "<Panellayout>"
#        return res

    def __str__(self):
        return str(self._layout)

    def updateLayout(self):
        w,h = self._wx.GetClientSizeTuple()
        self._layout.setGeom(0,0,w,h)
        self._wx.Refresh(False)

    ######################################################################
    ## protected:
        
    def __iter__(self):
        if self._layout!=None:
            return iter(self._layout.panelNodes())
        else:
            return iter([])

    def __getitem__(self, key):
        if self._layout!=None:
            return self._layout.findNodeByName(key)
        else:
            return None

    def __getattr__(self, name):
        if self._layout!=None:
            return self._layout.findNodeByName(name)
        else:
            return None
        
    def onSize(self, event):
        w,h = event.GetSize()
        self._layout.setGeom(0,0,w,h)

    def onEnterPanel(self, panel):
        self.mousepanel = panel
        self._wx.SetCursor(wx.NullCursor)
#        print "enter", panel.name

    def onLeavePanel(self, panel):
        pass
#        print "leave", panel.name

    def onClickPanel(self, panel):
        self.activepanel = panel
        self._wx.Refresh(False)

    def onLeftDown(self, event):
        x = event.GetX()
        y = event.GetY()
        flags, node = self._layout.findPanel(x,y)
        if flags!=0:
            self._splitter_flags = flags
            self._layout_node = node
            self._xclick = x
            self._yclick = y
            self._xsplitter, self._ysplitter = node.getPixelPos()
            self._wx.CaptureMouse()
            
    def onLeftUp(self, event):
        if self._xclick!=None:
            self._xclick = None
            self._yclick = None
            self._xsplitter = None
            self._ysplitter = None
            self._splitter_flags = None
            self._layout_node = None
            self._wx.ReleaseMouse()
            self._wx.SetCursor(wx.NullCursor)
#            self.Refresh(False)
        
    def onMouseMove(self, event):
        x = event.GetX()
        y = event.GetY()
#        print x,y
        if self._xclick!=None:
            dx = x-self._xclick
            dy = y-self._yclick
            if self._splitter_flags & HORIZONTAL==0:
                dx = 0
            if self._splitter_flags & VERTICAL==0:
                dy = 0
            self._layout_node.setPixelPos(self._xsplitter+dx, self._ysplitter+dy, True)
            self._wx.Refresh(False)
        else:
            flags, node = self._layout.findPanel(x,y)
            if flags==HORIZONTAL:
                self._wx.SetCursor(self._hcrsr)
            elif flags==VERTICAL:
                self._wx.SetCursor(self._vcrsr)
            elif flags==HORIZONTAL|VERTICAL:
                self._wx.SetCursor(self._hvcrsr)
            else:
                self._wx.SetCursor(wx.NullCursor)
            
    def onPaint(self, event):
        dc = wx.PaintDC(self._wx)

        dc.BeginDrawing()

        # Paint empty panels black
        dc.SetPen(wx.TRANSPARENT_PEN)
        dc.SetBrush(wx.BLACK_BRUSH)
        for n in self:
            if n.widget==None:
                dc.DrawRectangle(n._x0, n._y0, n._width, n._height)

        # Draw a border around the current panel
        activepen = wx.Pen(wx.Color(0,0,255), 3, wx.SOLID)
        dc.SetPen(activepen)
        panel = self.activepanel
        if panel!=None:
            dc.DrawRectangle(panel._x0, panel._y0, panel._width, panel._height)

        dc.EndDrawing()

    # "layout" property...
    
    def _getLayout(self):
        """Return the layout tree.

        This method is used for retrieving the \a layout property.

        \return Layout tree (\c ILayoutNode).
        """
        return self._layout

    def _setLayout(self, layout):
        """Set the layout tree.

        This method is used for setting the \a layout property.

        \param layout (\c ILayoutNode) Layout tree.
        """
        if self._layout!=None:
            self._layout.deactivate()
        self._layout = layout
        self.updateLayout()
        self._layout.activate(self)

    layout = property(_getLayout, _setLayout, None, "Panel layout tree")

    # "wx" property...
    
    def _getWx(self):
        """Return the encapsulated wx widget.

        This method is used for retrieving the \a wx property.

        \return wxPython object.
        """
        return self._wx

    wx = property(_getWx, None, None, "Encapsulated wx widget")

    # "repository" property...
    
    def _getRepository(self):
        """Return the widget repository.

        This method is used for retrieving the \a repository property.

        \return Widget repository (\c PanelWidgetRepository)
        """
        return self._repository

    repository = property(_getRepository, None, None, "Panel widget repository")
        


######################################################################
######################################################################

# ILayoutNode
class ILayoutNode(object):
    """This is the base class for a node in the layout tree.

    This class maintains the two attributes \em _parent (parent node)
    and \em _root (corresponding Panels object). The root attribute of
    every node in a tree must always point to the same %Panels object.
    If the layout is not active, then the %Panels object is None.

    The root object is set when the layout gets activated (i.e attached
    to a %Panels object). If a new node is created inside an active layout,
    the root has to be provided in the constructor.

    Attributes:

    - \b name (\c str): Node name
    """
    
    def __init__(self, root=None, name=None):
        """Constructor.

        \param root (\c Panels) Panels object which belongs to this layout or None.
        """
        self._parent = None
        self._root = root

        # Node name
        self._name = name
        

    # activate
    def activate(self, root):
        """Activate the node.

        Attaches the layout to the Panels object \a root. If the node has
        children this method has to call their %activate() method as well.
        If there are already PanelWidgets attached to this layout this
        method has to make them visible and do the layout.

        This method is called by the %Panels object whenever the layout
        is switched.

        \pre \a root must not have another layout activated.
        \pre self must be inactive
        \param root (\c Panels) The panels object to which the layout is attached.
        \see deactivate()
        """
        pass

    # deactivate
    def deactivate(self):
        """Deactivates this node.

        Detaches a layout from its root Panels object. All the widgets
        have to be hidden during deactivation.

        This method is called by the %Panels object whenever the layout
        is switched.

        \pre The layout has to be previously activated.
        \see activate()
        """
        pass

    # isActive
    def isActive(self):
        """Checks if this layout is active or not.

        \return True if the layout is active
        """
        return self._root!=None

    # setRoot
    def setRoot(self, root):
        """Set a new root.

        If the node contains children this method has to be overwritten
        and it has to set the new root on the children as well.

        This method is for internal use to propagate a new root through
        the entire tree when the layout is activated or deactivated.

        \param root (\c Panels) New root object.
        """
        self._root = root

    # setGeom
    def setGeom(self, x, y, width, height):
        """Set the position and size of the managed area.

        \param x (\c int) X position in pixel
        \param y (\c int) Y position in pixel
        \param width (\c int) Width in pixel
        \param height (\c int) Height in pixel
        """
        pass

    # applyConstraint
    def applyConstraint(self, width, height, interactive=False):
        """Check if any active constraints accept the given width and height.

        This method checks if \a width and \a height are acceptable for
        this node. If they are, the method has to return the width and
        height unmodified, otherwise an adjusted value has to be returned
        that's as close as possible to the input values.

        The parameter \a interactive specifies if the resizing was
        directly initiated by the user (i.e. a splitter was dragged).
        The usual policy is to allow resizing in this case. Otherwise
        the size is kept fixed (for example, if you only resize the entire
        application window).

        \param width (\c int)  Width to check
        \param height (\c int)  Height to check
        \param interactive (\c bool)  Flag that specifies if the resizing
               stems from a user interaction (the user drags a splitter)
        \return Tuple (width, height) with adjusted size values
        """
        pass

    # isResizable
    def isResizable(self, direction):
        """Check if the size of the node may be changed by the user.

        \param direction (\c int) A combination of HORIZONTAL and VERTICAL
        \return True if resizable.
        """
        pass

    # panelNodes
    def panelNodes(self):
        """Return a list of all panel nodes in this subtree.

        This method only has to return the panel nodes, i.e. the leafes
        of the tree. LayoutNode objects are skipped.

        This method enables the Panels object to iterate over all
        panel nodes.

        \return A list of PanelNode objects.
        """
        pass

    # findNodeByName
    def findNodeByName(self, name):
        """Returns the node in this subtree with the given name.

        \param name (\c str) Node name
        \return Node or None.
        """
        pass

    # makeNameUnique
    def makeNameUnique(self, name):
        """Makes a given name unique by appending an appropriate number.

        The argument \a name is checked if it's already used somewhere
        in the tree. If it's unused it is returned unchanged.
        Otherwise a number is appended that makes the name unique (if
        the original name aready contained a number, this number is
        increased).

        \param name (\c str) Input name
        """
        while self.findNodeByName(name)!=None:
            m = re.search("[0-9]+$", name)
            if m==None:
                name = name+"1"
            else:
                n = int(name[m.start():])
                name = name[:m.start()]+str(n+1)
        return name
            

    # findPanel
    def findPanel(self, x, y):
        return (0, self)

    # showConfigPanel
    def showConfigPanel(self):
        """Show the configuration panel.

        A LayoutNode passes this method call forward to its children
        which finally create the panel.
        """
        pass

    # hideConfigPanel
    def hideConfigPanel(self):
        """Hide the configuration panel.

        A LayoutNode passes this method call forward to its children
        which finally remove the panel.
        """
        pass

    ######################################################################
    ## protected:

    def _layoutRoot(self):
        """Return the root layout node.

        \param Root layout node (\c ILayoutNode).
        """
        p = self
        while p._parent!=None:
            p = p._parent
        return p

    # "name" property...
    
    def _getName(self):
        """Return the node name.

        This method is used for retrieving the \a name property.

        \return Name (\c str)
        """
        return self._name

    def _setName(self, name):
        """Set the node name.

        This method is used for setting the \a name property.

        \param name (\c str) New name
        """
        # Check if the new name exists already....
        prevname = self._name
        self._name = None
        if self._layoutRoot().findNodeByName(name)!=None:
            self._name = prevname
            raise DuplicateNames, 'There is already a node with the name "%s"'%name

        self._name = name
        
    name = property(_getName, _setName, None, "Name")


# LayoutNodeCoords
class LayoutNodeCoords:
    """Helper class for the LayoutNode class.
    """
    def __init__(self,x0=0,x1=0,x2=0,x3=0,y0=0,y1=0,y2=0,y3=0):
        self.x0 = x0
        self.y0 = y0
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2
        self.x3 = x3
        self.y3 = y3


# LayoutNode
class LayoutNode(ILayoutNode):
    """Layout node.

    This class represents a layout scheme that can manage areas
    that are laid out as depicted in the following image:
    
    \image html "panel_layout_node.png" "Layout"

    This class is also used if the region should only be split in
    horizontal or vertical direction. If the region is split horizontally
    then x1 = x2 = x3, if it's split vertically then y1 = y2 = y3.
    The following constraints are always enforced on the coordinates:
    x0 <= x1 <= x2 <= x3 and y0 <= y1 <= y2 <= y3. If a vertical splitter
    is present then x1 < x2, if a horizontal splitter is present then
    y1 < y2. All coordinates of all layout nodes are always relative to
    the widget that's associated with the layout hierarchy.

    There are two coordinates associated with each splitter. One is
    the \em logical coordinate that lies within [0,1] (0=left/top,
    1=right/bottom) and the other is the true \em pixel \em coordinate
    relative to the parent widget. In the presence of size constraints
    among the children panels/layouts the logical coordinates remain
    unchanged whereas the true pixel position changes. The pixel coordinates
    always try to represent the logical coordinates as close as possible.
    """
    def  __init__(self, x=0, y=0, width=0, height=0, root=None, name=None,
                  splittertype=HORIZONTAL|VERTICAL, x0=0.5, y0=0.5,
                  children=None):
        """Constructor.

        The arguments should be given as keyword arguments.

        \param x (\c int) Initial X position
        \param y (\c int) Initial Y position
        \param width (\c int) Initial width
        \param height (\c int) Initial height
        \param root (\c Panels) Root object
        \param name (\c str) Node name
        \param splittertype (\c int) A combination of HORIZONTAL and VERTICAL
        \param x0 (\c float) Initial logical splitter x position
        \param y0 (\c float) Initial logical splitter y position
        \param children  A tuple with children nodes (either LayoutNode or PanelNode). The number of children is determined by the splitter type.
        """
        
        ILayoutNode.__init__(self, root=root, name=name)

        # This is the region that's managed by this node
        # This coordinates are relative to the root widget
        # x1/y1 is the true pixel coordinate of the splitter 
        self._x0 = 0
        self._y0 = 0
        self._x1 = 0
        self._y1 = 0
        self._x2 = 0
        self._y2 = 0
        self._x3 = 0
        self._y3 = 0

        # Logical splitter coordinate (0-1)
        self._splitter_x = x0
        self._splitter_y = y0

        # Splitter width in pixel
        self._splitter_width = 6

        # Splitter type
        self._splitter_type = splittertype

        # Children nodes (may not be None)
        # Upper left
        self._child00 = None
        # Upper right
        self._child01 = None
        # Lower left
        self._child10 = None
        # Lower right
        self._child11 = None
        
        if children==None:
            if splittertype==HORIZONTAL|VERTICAL:
                children = (None,None,None,None)
            else:
                children = (None, None)
        self.setChildren(children)

        self.setGeom(x, y, width, height)

    def __str__(self):
        return self._toStr(self, "", 0)

    def _toStr(self, node, res, d):
        """Helper method for the __str__ operator."""

        if isinstance(node, LayoutNode):
            s="?"
            if node._splitter_type==HORIZONTAL:
                s = "horizontal"
            elif node._splitter_type==VERTICAL:
                s = "vertical"
            elif node._splitter_type==HORIZONTAL | VERTICAL:
                s = "horizontal & vertical"
            if node.name!=None:
                name = '"%s"'%node.name
            else:
                name = "<None>"
            s = 'LayoutNode %s active:%d type:%s'%(name, node.isActive(), s)
            res += d*" "+s+"\n"
            childs = node.getChildren()
            for c in childs:
                res = self._toStr(c, res, d+2)
            return res
        elif isinstance(node, PanelNode):
            return res + d*" "+str(node)+"\n"
        else:
            return res + d*" "+"???\n"

    def activate(self, root):
        self.setRoot(root)
        self._child00.activate(root)
        self._child01.activate(root)
        self._child10.activate(root)
        self._child11.activate(root)

    def deactivate(self):
        self.setRoot(None)
        self._child00.deactivate()
        self._child01.deactivate()
        self._child10.deactivate()
        self._child11.deactivate()

    def setRoot(self, root):
        ILayoutNode.setRoot(self, root)
        self._child00.setRoot(root)
        self._child01.setRoot(root)
        self._child10.setRoot(root)
        self._child11.setRoot(root)

    def getSplitterType(self):
        """Return the type of layout node.

        Returns the direction that are split by a splitter. A horizontal
        splitter splits in X direction, a vertical splitter in Y direction.

        \return A combination of HORIZONTAL and VERTICAL
        \see setSplitterType()
        """
        return self._splitter_type

    def setSplitterType(self, stype):
        """Set the layout node type.

        \param stype (\c int) A combination of HORIZONTAL and VERTICAL
        \see getSplitterType()
        """
        self._splitter_type = stype
        

    # setGeom
    def setGeom(self, x, y, width, height):
        # Initialize coordinates...
        self._x0 = int(x)
        self._y0 = int(y)
        self._x3 = int(x+width)
        self._y3 = int(y+height)
        # The following call updates the pixel position of the splitter
        self.setLogicalPos(self._splitter_x, self._splitter_y)

    def findNodeByName(self, name):
        if self._name==name:
            return self
        
        res = self._child00.findNodeByName(name)
        if res==None:
            res = self._child01.findNodeByName(name)
        if res==None:
            res = self._child10.findNodeByName(name)
        if res==None:
            res = self._child11.findNodeByName(name)
        return res

    def showConfigPanel(self):
        self._child00.showConfigPanel()
        if self._splitter_type==HORIZONTAL:
            self._child01.showConfigPanel()
        elif self._splitter_type==VERTICAL:
            self._child10.showConfigPanel()
        else:
            self._child01.showConfigPanel()
            self._child10.showConfigPanel()
            self._child11.showConfigPanel()

    def hideConfigPanel(self):
        self._child00.hideConfigPanel()
        if self._splitter_type==HORIZONTAL:
            self._child01.hideConfigPanel()
        elif self._splitter_type==VERTICAL:
            self._child10.hideConfigPanel()
        else:
            self._child01.hideConfigPanel()
            self._child10.hideConfigPanel()
            self._child11.hideConfigPanel()    
        
    def getChildren(self):
        """Return the children nodes.

        The number of children returned depends on the splitter type
        (either 2 or 4).

        \return A tuple with two or four children nodes (\c ILayoutNode).
        """
        if self._splitter_type==HORIZONTAL:
            return self._child00, self._child01
        elif self._splitter_type==VERTICAL:
            return self._child00, self._child10
        else:
            return self._child00, self._child10, self._child01, self._child11


    def setChildren(self, childs):
        """Set the children of this node.

        \a childs is a tuple of nodes which should be set as children.
        A node may also be None in which case an empty PanelNode is set.
        The children nodes must not be part of another tree (if they are
        an exception is thrown).
        
        \param childs A sequence containing the children. The number of children depends on the splitter type (2 or 4).
        """

        if self._splitter_type==HORIZONTAL:
            self.setChild(childs[0], 0)
            self.setChild(childs[1], 1)
            self.setChild(None, 2)
            self.setChild(None, 3)
        elif self._splitter_type==VERTICAL:
            self.setChild(childs[0], 0)
            self.setChild(None, 1)
            self.setChild(childs[1], 2)
            self.setChild(None, 3)
        else:
            self.setChild(childs[0], 0)
            self.setChild(childs[1], 1)
            self.setChild(childs[2], 2)
            self.setChild(childs[3], 3)


    def childIndex(self, child):
        """Return the index of the children node.

        \a child must actually be a child of self, otherwise a ValueError
        exception is thrown.

        \param child (\c ILayoutNode) Children node
        \return Index (0-3)
        """
        
        lst = [self._child00, self._child01, self._child10, self._child11]
        return lst.index(child)

    def getChild(self, idx):
        """Return a single children node.

        \param idx (\c int) Children index (0-3)
        \return Children node (\c ILayoutNode).
        """
        if idx<0 or idx>3:
            raise IndexError, "Children index out of range (%d)"%idx

        return [self._child00, self._child01, self._child10, self._child11][idx]

    def setChild(self, child, idx):
        """Replace a single children node.

        \param child (\c ILayoutNode) New children or None
        \param idx (\c int) Children index (0-3)
        \todo Pruefen, ob Child tree ok ist (keine doppelten Widgets oder Namen)
        """
        if idx<0 or idx>3:
            raise IndexError, "Children index out of range (%d)"%idx
        if child==None:
#            if self._root!=None:
#                black = wx.Window(self._root.wx, -1)
#                black.SetBackgroundColour(wx.Colour())
#            else:
#                black = None
#            child = PanelNode(widget=black)
            child = PanelNode()
        if child._parent!=None:
            raise ValueError, 'The panel layout node is already part of a layout tree.'
            
        if idx==0:
            self.removeChild(self._child00)
            self._child00 = child
        elif idx==1:
            self.removeChild(self._child01)
            self._child01 = child
        elif idx==2:
            self.removeChild(self._child10)
            self._child10 = child
        elif idx==3:
            self.removeChild(self._child11)
            self._child11 = child

        child._parent = self
        if self.isActive():
            child.activate(self._root)


    def removeChild(self, child):
        """Remove a child node.

        \a child may be None in which case the method returns immediately.

        \param child (\c ILayoutNode) Children which is to be removed or None
        """
        if child==None:
            return
        
        if child==self._child00:
            self._child00 = None
        elif child==self._child01:
            self._child01 = None
        elif child==self._child10:
            self._child10 = None
        elif child==self._child11:
            self._child11 = None
        else:
            raise ValueError, "Layout node is not a children node."

        child._parent = None
        if self.isActive():
            child.deactivate()

        self._fillEmptyChildren()

    def remove(self, idx=0):
        """Remove this layout node and replace it with a children node.
        """

        child = self.getChild(idx)
        self.setChildren((None,None,None,None))

        root = self._root

        parent = self._parent
        if parent!=None:
            i = parent.childIndex(self)
            parent.removeChild(self)
            parent.setChild(child, i)
        else:
            if root!=None:
                root.layout = child

        if root!=None:
            root.updateLayout()
        
        
    # isResizable
    def isResizable(self, direction):
        res = False
        if direction&HORIZONTAL:
            res |= ( (self._child00.isResizable(HORIZONTAL) and
                      self._child10.isResizable(HORIZONTAL) )
                     or
                     (self._child01.isResizable(HORIZONTAL) and
                      self._child11.isResizable(HORIZONTAL)) )
        if direction&VERTICAL:
            res |= ( (self._child00.isResizable(VERTICAL) and
                      self._child01.isResizable(VERTICAL) )
                     or
                     (self._child10.isResizable(VERTICAL) and
                      self._child11.isResizable(VERTICAL)) )

        return res

    # applyConstraint
    def applyConstraint(self, width, height, interactive=False):
        coords = LayoutNodeCoords(self._x0, 0, 0, self._x0+width,
                                  self._y0, 0, 0, self._y0+height)
        self._calcSplitterCoords(self._splitter_x, self._splitter_y,
                                 coords, interactive)
        return (coords.x3-coords.x0, coords.y3-coords.y0)

    # panelNodes
    def panelNodes(self):
        res = []
        for c in self.getChildren():
            res += c.panelNodes()
        return res

    # isInside
    def isInside(self, x, y):
        """Check if the mouse coordinate (x,y) is inside the managed region.

        The coordinate must be given relative to the root widget.

        \param x (\c int) X coordinate
        \param y (\c int) Y coordinate
        """
        return (x>=self._x0 and x<self._x3 and y>=self._y0 and y<self._y3)

    # findPanel
    def findPanel(self, x, y):
        """Find a panel by mouse position.

        The method assumes that x,y lies somehwere in the managed region.

        The return value contains a flag that has the following values:

        - 0: A panel was hit
        - 0x1: A horizontal splitter was hit
        - 0x2: A vertical splitter was hit
        - 0x3: The middle of a splitter was hit

        \return Tuple (Flag, LayoutNode)
        """
        # Left column?
        if x<self._x1:
            # Upper left panel?
            if y<self._y1:
                return self._child00.findPanel(x,y)
            # Vertical splitter?
            elif y<self._y2:
                return (VERTICAL, self)
            # Lower left panel
            else:
                return self._child10.findPanel(x,y)
        # Horizontal splitter? (or middle)
        elif x<self._x2:
            if y>=self._y1 and y<self._y2:
                return (VERTICAL | HORIZONTAL, self)
            else:
                return (HORIZONTAL, self)
        # Right column
        else:
            # Upper right panel?
            if y<self._y1:
                return self._child01.findPanel(x,y)
            # Vertical splitter?
            elif y<self._y2:
                return (VERTICAL, self)
            # Lower right panel
            else:
                return self._child11.findPanel(x,y)

    # setLogicalPos
    def setLogicalPos(self, x0=0.5, y0=0.5, interactive=False):
        """Set the logical position of the splitters.

        The pixel position is updated as well.
        
        \param x0 (\c float) Logical X position 
        \param y0 (\c float) Logical Y position 
        """

        self._splitter_x = x0
        self._splitter_y = y0

        coords = LayoutNodeCoords(self._x0, 0, 0, self._x3,
                                  self._y0, 0, 0, self._y3)
        self._calcSplitterCoords(x0, y0, coords, interactive)
        self._x1 = coords.x1
        self._x2 = coords.x2
        self._y1 = coords.y1
        self._y2 = coords.y2
        self._update()

    # getLogicalPos
    def getLogicalPos(self):
        """Return the logical position of the splitter.

        \return Tuple (x0, y0) containing the logical position.
        """
        return self._splitter_x, self._splitter_y

    # setPixelPos
    def setPixelPos(self, x, y, interactive=False):
        """Set the pixel position of the splitter.

        This method doesn't set the pixel position directly as the
        position might break some size constraints. Instead the
        position is converted to logical coordinates and setLogicalPos()
        is called instead (so the logical position is updated as well).

        \param x (\c int) X coordinate of the destination pixel position
        \param y (\c int) Y coordinate of the destination pixel position
        """
        x0, y0 = self._pixel2logical(x, y)
        self.setLogicalPos(x0, y0, interactive)

    # getPixelPos
    def getPixelPos(self):
        """Return the pixel position of the splitter.

        \param Tuple (x, y) containing the pixel position.
        """
        return self._x1, self._y1
        

    ######################################################################
    ## protected:

    def _logical2pixel(self, x0, y0):
        """Convert logical coordinates to pixel coordinates.

        \param x0 (\c float) Logical X coordinate
        \param y0 (\c float) Logical Y coordinate
        \return Tuple (x,y) with pixel coordinates (\c int, \c int)
        """
        xmax = self._x3 - self._splitter_width
        ymax = self._y3 - self._splitter_width
        return (int((1.0-x0)*self._x0 + x0*xmax),
                int((1.0-y0)*self._y0 + y0*ymax))

    def _pixel2logical(self, x, y):
        """Convert pixel coordinates to logical coordinates.

        \param x (\c int) X coordinate
        \param y (\c int) Y coordinate
        \return Tuple (x0, y0) with logical coordinates (\c float, \c float)
        """
        w = self._x3 - self._x0 - self._splitter_width
        h = self._y3 - self._y0 - self._splitter_width
        
        if w==0:
            x0 = 0.0
        else:
            x0 = float(x-self._x0)/w
            
        if h==0:
            y0 = 0.0
        else:
            y0 = float(y-self._y0)/h
            
        return x0,y0

    def _update(self):
        """Update the children areas.

        \pre x0-x3/y0-y3 contain valid values
        """
        x0,y0 = self._x0, self._y0
        x1,y1 = self._x1, self._y1
        x2,y2 = self._x2, self._y2
        x3,y3 = self._x3, self._y3
        
        self._child00.setGeom(x0, y0, x1-x0, y1-y0)
        self._child01.setGeom(x2, y0, x3-x2, y1-y0)
        self._child10.setGeom(x0, y2, x1-x0, y3-y2)
        self._child11.setGeom(x2, y2, x3-x2, y3-y2)        

    def _calcSplitterCoords(self, x0, y0, coords, interactive):
        """Calculate new splitter positions.
      
        \param x0 (\c float) Logical X coordinate of the splitter
        \param y0 (\c float) Logical Y coordinate of the splitter
        \param coords (\c LayoutNodeCoords) Coordinates container
        \param interactive (\c bool) Interactive flag
        \pre x0,x3,y0,y3 are set in coords and x0 <= x3 and y0 <= y3
        """
        
        x,y = self._logical2pixel(x0,y0)
        coords.x1 = x
        coords.x2 = x + self._splitter_width
        coords.y1 = y
        coords.y2 = y + self._splitter_width
        self._enforceSplitterConstraints(coords)
        self._enforceSizeConstraints(coords, interactive)
        return coords
        

    def _enforceSizeConstraints(self, coords, interactive):
        """Change the splitter pixel coordinates to enforce size constraints.

        This method changes pixel coordinates of the splitter so that
        the size constraints on the children will be met. If there are
        no constraints then x0-x3/y0-y3 won't be modified.
        The logical coordinates remain unchanged in any case.
        """

        # Is the left column resizable?
        if (self._child00.isResizable(HORIZONTAL) and self._child10.isResizable(HORIZONTAL)):
            # Right column has priority:
            # Determine suggested width of the right column by asking
            # child01 and child11 in both orders and taking the maximum
            w = coords.x3-coords.x2
            w1,h = self._child01.applyConstraint(w,1, interactive)
            w1,h = self._child11.applyConstraint(w1,1, interactive)
            w2,h = self._child11.applyConstraint(w,1, interactive)
            w2,h = self._child01.applyConstraint(w2,1, interactive)
            w = max(w1,w2)
            # Set the final splitter x position 
            coords.x2 = coords.x3 - w
            coords.x1 = coords.x2 - self._splitter_width
            self._enforceSplitterConstraints(coords)
        else:
            # Left column has priority:
            # Determine suggested width of the left column by asking
            # child00 and child10 in both orders and taking the maximum
            w = coords.x1-coords.x0
            w1,h = self._child00.applyConstraint(w,1, interactive)
            w1,h = self._child10.applyConstraint(w1,1, interactive)
            w2,h = self._child10.applyConstraint(w,1, interactive)
            w2,h = self._child00.applyConstraint(w2,1, interactive)
            w = max(w1,w2)
            # Set the final splitter x position 
            coords.x1 = coords.x0 + w
            coords.x2 = coords.x1 + self._splitter_width
            self._enforceSplitterConstraints(coords)

        # Is the top row resizable?
        if (self._child00.isResizable(VERTICAL) and self._child01.isResizable(VERTICAL)):
            # Bottom row has priority:
            # Determine suggested width of the bottom row by asking
            # child10 and child11 in both orders and taking the maximum
            h = coords.y3-coords.y2
            w,h1 = self._child10.applyConstraint(1,h, interactive)
            w,h1 = self._child11.applyConstraint(1,h1, interactive)
            w,h2 = self._child11.applyConstraint(1,h, interactive)
            w,h2 = self._child10.applyConstraint(1,h2, interactive)
            h = max(h1,h2)
            # Set the final splitter y position 
            coords.y2 = coords.y3 - h
            coords.y1 = coords.y2 - self._splitter_width
            self._enforceSplitterConstraints(coords)
        else:
            # Top row has priority:
            # Determine suggested height of the top row by asking
            # child00 and child01 in both orders and taking the maximum
            h = coords.y1-coords.y0
            w,h1 = self._child00.applyConstraint(1,h, interactive)
            w,h1 = self._child01.applyConstraint(1,h1, interactive)
            w,h2 = self._child01.applyConstraint(1,h, interactive)
            w,h2 = self._child00.applyConstraint(1,h2, interactive)
            h = max(h1,h2)
            # Set the final splitter y position 
            coords.y1 = coords.y0 + h
            coords.y2 = coords.y1 + self._splitter_width
            self._enforceSplitterConstraints(coords)
        

    def _enforceSplitterConstraints(self, coords):
        """Enforces the pixel coordinate constraints on both of the splitter.

        Enforces the following constraints x0 <= x1 <= x2 <= x3 and
        y0 <= y1 <= y2 <= y3 provided that x0 <= x3 and y0 <= y3 already
        hold. The distance x2-x1 resp. y2-y1 is kept intact, unless x2<x1
        or y2<y1 in which case both values will be the same. If a splitter
        is not present in a particular direction, the corresponding splitter
        coordinates will be fixed at x3 resp. y3.

        The logical coordinate remains unchanged.

        This method is called whenever the splitter position changes.

        \pre x0 <= x3 and y0 <= y3
        """

        if self._splitter_type & HORIZONTAL == 0:
            coords.x1 = coords.x2 = coords.x3
        if self._splitter_type & VERTICAL == 0:
            coords.y1 = coords.y2 = coords.y3
            
        # d is the current width of the splitter
        d = coords.x2-coords.x1
        if d<0:
            coords.x2 = coords.x1
            d = 0
            
        if coords.x1<coords.x0:
            coords.x1 = coords.x0
            coords.x2 = coords.x1 + d
        if coords.x2>coords.x3:
            coords.x2 = coords.x3
            coords.x1 = max(coords.x0, coords.x2-d)

        # d is the current width of the splitter
        d = coords.y2-coords.y1
        if d<0:
            coords.y2 = coords.y1
            d = 0
            
        if coords.y1<coords.y0:
            coords.y1 = coords.y0
            coords.y2 = coords.y1 + d
        if coords.y2>coords.y3:
            coords.y2 = coords.y3
            coords.y1 = max(coords.y0, coords.y2-d)

    def _fillEmptyChildren(self):
        if self._child00==None:
            self.setChild(PanelNode(), 0)
        if self._child01==None:
            self.setChild(PanelNode(), 1)
        if self._child10==None:
            self.setChild(PanelNode(), 2)
        if self._child11==None:
            self.setChild(PanelNode(), 3)

# PanelNode
class PanelNode(ILayoutNode):
    """This class represents a leaf node in the layout tree.

    Each node in the layout tree must be a %PanelNode object. This object
    represents one panel is associated with a wx widget. The class
    has the following attributes:

    - \a constraint (\c SizeConstraint): A constraint on the size of the panel
    - \a widget (\c PanelWidget): The associated widget
    - [\a wx (\c wx \c widget): The associated wx widget]
    """
    
    def __init__(self, name="", activatable=False, constraint=None, widget=None):
        """Constructor.

        \param name (\c str) Node name
        \param activatable (\c bool) A flag that determines if the panel can
               be activated or not (only 3D views should be made activatable)
        \param constraint (\c SizeConstraint) Size constraint
        \param widget (\c PanelWidget) The panel widget or a sequence of widgets or None
        """
        ILayoutNode.__init__(self, name=name)

        # Position and size of the panel
        self._x0 = 0
        self._y0 = 0
        self._width = 0
        self._height = 0

        # Activatable flag
        self._activatable = activatable

        # Panel widgets. This is actually a stack where the last widget
        # is on top (all other widgets are hidden).
        self._widgets = []

        # Hide all widgets
        for w in self._widgets:
            w.hide()

        # Push the widgets...
        if widget!=None:
            try:
                wlst = list(widget)
            except TypeError:
                wlst = [widget]
            
            for w in wlst:
                self.pushWidget(w)

        self.showConfigPanel()

        # Constraint
        if constraint==None:
            constraint = NoConstraint()
        self._constraint = constraint

    def __str__(self):
        if self.name!=None:
            name = '"%s"'%self.name
        else:
            name = "<None>"
        return 'PanelNode %s active:%d widgets:%s'%(name, self.isActive(), map(lambda x: str(x), self._widgets))

    def setGeom(self, x, y, width, height):
#        print "Panel '%s', setGeom(%d, %d, %d, %d)"%(self.name, x, y, width, height)
        self._x0 = x
        self._y0 = y
        self._width = width
        self._height = height

        x+=1
        y+=1
        width-=2
        height-=2
        self._constraint.setSize(width, height)
        if len(self._widgets)>0:
            self._widgets[-1].setGeom(x,y,width,height)

    def activate(self, root):
        # Store root (Panels object)
        self.setRoot(root)

        # Mark all widgets as activated
        for w in self._widgets:
            if not isinstance(w, PanelNodeDlg):
                self._root.repository.insert(w)
            w.activate(root)

        # Set the position and size of the panel widget
        self.setGeom(self._x0, self._y0, self._width, self._height)
        # Connect and show the top widget
        if len(self._widgets)>0:
            w = self._widgets[-1]
            self._connectPanelEvents(w.wx)
            w.show()

    def deactivate(self):
        # Disconnect and hide the top widget
        if len(self._widgets)>0:
            w = self._widgets[-1]
            w.hide()
            self._disconnectPanelEvents(w.wx)
        
        # Mark all widgets as inactivated
        for w in self._widgets:
            w.deactivate()

        self.setRoot(None)


    def pushWidget(self, widget):
        """Add a widget on top of the stack.

        Adds a widget on top of the stack. If the layout is active the
        new widget will be displayed.

        \param widget (\c PanelWidget) The new panel widget
        """
        if widget.isUsedBy(self._layoutRoot()):
            raise LayoutError, 'The widget "%s" is already managed by this layout.'%widget.name

        # Hide and disconnect the previous top widget...
        if self.isActive():
            if len(self._widgets)>0:
                w = self._widgets[-1]
                w.hide()
                self._disconnectPanelEvents(w.wx)

        # Store the widget...
        widget.acquire(self)
        self._widgets.append(widget)

        if self.isActive():
            if not isinstance(widget, PanelNodeDlg):
                self._root.repository.insert(widget)
            # activate the widget...
            widget.activate(self._root)
            # Set the position and size of the panel widget
            self.setGeom(self._x0, self._y0, self._width, self._height)
            # Connect and show the top widget
            if len(self._widgets)>0:
                w = self._widgets[-1]
                self._connectPanelEvents(w.wx)
                w.show()

    def popWidget(self):
        """Remove the top widget from the stack.

        \pre The stack must not be empty
        """
        w = self._widgets[-1]
        self._widgets.pop()
        w.release(self)
        # Disconnect and hide the top widget and show and connect the
        # previous one
        if self.isActive():
            w.deactivate()
            w.hide()
            self._disconnectPanelEvents(w.wx)
            if len(self._widgets)>0:
                w2 = self._widgets[-1]
                self._connectPanelEvents(w2.wx)
                w2.show()
                self.setGeom(self._x0, self._y0, self._width, self._height)
        

    def findNodeByName(self, name):
        if self._name==name:
            return self
        else:
            return None

    def showConfigPanel(self):
        # Is a config panel already on top?
        if len(self._widgets)>0 and isinstance(self._widgets[-1], PanelNodeDlg):
            return
        # Remove any config panel that's somewhere inside the stack
        self._widgets = filter(lambda w: not isinstance(w, PanelNodeDlg), self._widgets)
        # Create a new config panel and push it onto the stack
        dlg = PanelNodeDlg(self)
        self.pushWidget(dlg)

    def hideConfigPanel(self):
        # If there's only the config panel present then leave it there
        if len(self._widgets)==1 and isinstance(self._widgets[0], PanelNodeDlg):
            return
        
        # Is a config panel on top?
        if len(self._widgets)>0 and isinstance(self._widgets[-1], PanelNodeDlg):
            # pop the panel
            self.popWidget()
        else:
            # Remove any config panel that's somewhere inside the stack
            self._widgets = filter(lambda w: not isinstance(w, PanelNodeDlg), self._widgets)
            
    def isResizable(self, direction):
        return self._constraint.isResizable(direction)

    def applyConstraint(self, width, height, interactive=False):
        w,h = self._constraint(width-2, height-2, interactive)
        w+=2
        h+=2
        return w,h

    def panelNodes(self):
        return [self]

#    def findPanel(self, x, y):
#        return (0, self)

    def makeCurrent(self):
        """Make this panel the current panel.
        """
        if self._root!=None and self._activatable:
            self._root.onClickPanel(self)

    def split(self, x=None, y=None):
        """Split this panel in two or four new panels.

        \param x (\c float) Split coordinate in x direction (0-1) or None
        \param y (\c float) Split coordinate in y direction (0-1) or None
        \return Newly created layout node (\c LayoutNode) that contains
               the original panel and the new empty ones.
        """
        if x==None and y==None:
            return
        elif x!=None and y==None:
            stype = HORIZONTAL
            childs = (self, None)
        elif x==None and y!=None:
            stype = VERTICAL
            childs = (self, None)
        else:
            stype = HORIZONTAL | VERTICAL
            childs = (self, None, None, None)

        node = LayoutNode(splittertype=stype)

        parent = self._parent
        if parent!=None:
            idx = parent.childIndex(self)
            parent.removeChild(self)
            parent.setChild(node, idx)
        else:
            if self._root!=None:
                self._root.layout = node

        node.setChildren(childs)
        if self._root!=None:
            self._root.updateLayout()

        return node


    ######################################################################
    ## protected:

    def _connectPanelEvents(self, wxwin):
        """Connect the given wx window to the Panels object.

        This method sets the connections so that the Panels object will
        get notified when the mouse enters or leaves this panel and
        when it gets clicked.

        \param wxwin (\c wx \c window) A wx.Window instance
        """
#        print "connect",wxwin
        wx.EVT_ENTER_WINDOW(wxwin, self._onEnter)
        wx.EVT_LEAVE_WINDOW(wxwin, self._onLeave)
        wx.EVT_LEFT_DOWN(wxwin, self._onClick)
        wx.EVT_MIDDLE_DOWN(wxwin, self._onClick)
        wx.EVT_RIGHT_DOWN(wxwin, self._onClick)

    def _disconnectPanelEvents(self, wxwin):
        """Disconnect the given wx window from the Panels object.

        \param wxwin (\c wx \c window) A wx.Window instance
        """
#        print "disconnect",wxwin
        wx.EVT_ENTER_WINDOW(wxwin, None)
        wx.EVT_LEAVE_WINDOW(wxwin, None)
        wx.EVT_LEFT_DOWN(wxwin, None)
        wx.EVT_MIDDLE_DOWN(wxwin, None)
        wx.EVT_RIGHT_DOWN(wxwin, None)

    def _onEnter(self, event):
        self._root.onEnterPanel(self)
#        print "onEnter", self.name, self._root
        event.Skip()

    def _onLeave(self, event):
        self._root.onLeavePanel(self)
#        print "onLeave", self.name, self._root
        event.Skip()

    def _onClick(self, event):
        if self._activatable:
            self._root.onClickPanel(self)
        event.Skip()

    # "widget" property...
    
    def _getWidget(self):
        """Return the top panel widget.

        This method is used for retrieving the \a widget property.

        \return PanelWidget object or None.
        """
        if len(self._widgets)==0:
            return None
        return self._widgets[-1]

    def _setWidget(self, widget):
        """Set the top panel widget.

        This method is used for setting the \a widget property.

        \param widget (\c PanelWidget) Widget
        """
        if len(self._widgets)>0:
            self.popWidget()
        self.pushWidget(widget)

    widget = property(_getWidget, _setWidget, None, "Associated panel widget")

    # "constraint" property...
    
    def _getConstraint(self):
        """Return the constraint object.

        This method is used for retrieving the \a constraint property.

        \return Constraint object (\c SizeConstraint).
        """
        return self._constraint

    def _setConstraint(self, constraint):
        """Set the constraint object.

        This method is used for setting the \a constraint property.

        \param constraint (\c SizeConstraint) Constraint object
        """
        self._constraint = constraint

    constraint = property(_getConstraint, _setConstraint, None, "Size constraint")
    
        
        
# NoConstraint
class NoConstraint:
    """An unconstrained size constraint class."""
    
    def __call__(self, width, height, interactive=False):
        return width, height

    def setSize(self, width, height):
        pass

    def isResizable(self, direction):
        return True


# SizeConstraint
class SizeConstraint:
    """This class is used to enforce size constraints on widgets/panels.

    This class computes acceptable width/height values that conform
    to the current constraints. The constraints can either keep the
    width or height constant or at a multiple of a base width/height.
    The constraint class is a callable object that can be called
    with an input width and height and it returns a new width/height
    tuple that conforms to the constraint and is close to the input
    values.

    Examples:

    \code
    # A constraint that keeps the width fixed at 270 pixel, the height is arbitrary
    >>> c = SizeConstraint(width=270, wfixed=True)
    >>> c(100,100)
    (270, 100)

    # Now keep the height at a multiple of 32
    >>> c = SizeConstraint(height=32)
    >>> c(100,100)
    (100, 96)    
    \endcode
    
    """
    
    def __init__(self, width=None, height=None):
        """Constructor.

        \param width  A tuple (width, step, resizable) describing the width constraint
        \param height  A tuple (height, step, resizable) describing the height constraint
        """
        if width==None:
            width = None, None, False
        if height==None:
            height = None, None, False
            
        self.width, self.wstep, self.wresizable = width
        self.height, self.hstep, self.hresizable = height

    def __call__(self, width, height, interactive=False):
        """Check if the constraint accepts the given size.

        This method has to check if the given width and height
        are acceptable for the constraint. If they are the method
        just has to return those values again, if they are not
        it has to return a suggested size that would be acceptable.

        \param width (\c int) A width value
        \param height (\c int) A height value
        \param interactive (\c bool) If True the constraints also accepts multiples of its width (default: False)
        \return Tuple (width, height) specifying an acceptable size that's
               as close as possible to the input size
        """
        if self.width!=None:
            w = self.width
        else:
            w = width
            
        if self.height!=None:
            h = self.height
        else:
            h = height
        
        if self.wresizable and interactive:
            dw = width-self.width
            if dw%self.wstep==0:
                w = width
            else:
                margin = int(0.15*self.wstep)
                w = self.width + int((dw+margin)/self.wstep)*self.wstep

        if self.hresizable and interactive:
            dh = height-self.height
            if dh%self.hstep==0:
                h = height
            else:
                margin = int(0.15*self.hstep)
                h = self.height + int((dh+margin)/self.hstep)*self.hstep

        return w,h

    def setSize(self, width, height):
        """Set a new width and height.

        The new size is not set directly but run through the
        constraint (in interactive mode). So the actual size that
        gets set is a size that's compliant to the constraint.
        """
        w, h = self(width, height, True)
        if self.width!=None:
            self.width = w
        if self.height!=None:
            self.height = h

    def isResizable(self, direction):
        """Check if the size of the constraint may be changed.

        \param direction (\int) A combination of HORIZONTAL and VERTICAL
        \return True if resizable.
        """
        res = False
        if direction&HORIZONTAL:
            res |= self.wresizable
        if direction&VERTICAL:
            res |= self.hresizable
    
######################################################################


class PanelNodeDlg(PanelWidget):
    def __init__(self, panelnode):
        """Constructor.

        \param parentwin (\c MainWindow) Parent window (must have a "panels.wx" attribute)
        \param name (\c str) Widget name
        """
        PanelWidget.__init__(self)
        self.panelnode = panelnode
    
    def activate(self, root):
        PanelWidget.activate(self, root)
        self.wx = wx.Panel(root.wx, -1, style=wx.SIMPLE_BORDER)
        
        self.editlabel = wx.StaticText(self.wx, -1, "Name:")
        self.nameedit = wx.TextCtrl(self.wx, -1, value=self.panelnode.name)

        self.splitbox = wx.StaticBox(self.wx, -1, "Split")
        self.hsplitbtn = wx.BitmapButton(self.wx, -1, panelicons.getHSplitBitmap())
        self.hsplitbtn.SetToolTip(wx.ToolTip("Split this panel horizontally"))
        self.vsplitbtn = wx.BitmapButton(self.wx, -1, panelicons.getVSplitBitmap())
        self.vsplitbtn.SetToolTip(wx.ToolTip("Split this panel vertically"))
        self.hvsplitbtn = wx.BitmapButton(self.wx, -1, panelicons.getHVSplitBitmap())
        self.hvsplitbtn.SetToolTip(wx.ToolTip("Split this panel in both directions"))

        self.toplayout = wx.BoxSizer(wx.VERTICAL)

        self.s1 = wx.BoxSizer(wx.HORIZONTAL)
        self.s1.Add(self.editlabel, 0, wx.ALL | wx.ALIGN_CENTRE, 4)
        self.s1.Add(self.nameedit, 1, wx.ALL | wx.ALIGN_CENTRE, 4)

        self.s2 = wx.StaticBoxSizer(self.splitbox, wx.HORIZONTAL)
        self.s2.Add(self.hsplitbtn, 0, wx.ALL, 2)
        self.s2.Add(self.vsplitbtn, 0, wx.ALL, 2)
        self.s2.Add(self.hvsplitbtn, 0, wx.ALL, 2)

        self.toplayout.Add((0,1), 1, 0,0)
        self.toplayout.Add(self.s1, 0, wx.ALIGN_CENTRE, 0)
        self.toplayout.Add(self.s2, 0, wx.ALIGN_CENTRE, 0)
        self.toplayout.Add((0,1), 1, 0,0)

        self.wx.SetSizer(self.toplayout)

        wx.EVT_TEXT_ENTER(self.wx, self.nameedit.GetId(), self.onNameEntered)
        wx.EVT_KILL_FOCUS(self.nameedit, self.onNameEntered)
        wx.EVT_BUTTON(self.wx, self.hsplitbtn.GetId(), self.onHSplit)
        wx.EVT_BUTTON(self.wx, self.vsplitbtn.GetId(), self.onVSplit)
        wx.EVT_BUTTON(self.wx, self.hvsplitbtn.GetId(), self.onHVSplit)

    def onNameEntered(self, event):
        newname = self.nameedit.GetValue()
        try:
            self.panelnode.name = newname
        except DuplicateNames:
#            dlg = wx.MessageDialog(self.wx,
#                                   'There is already a layout node called "%s".\nPlease choose another name.'%newname, "Duplicate names", wx.OK)
#            dlg.ShowModal()
            newname = self.panelnode._layoutRoot().makeNameUnique(newname)
            self.panelnode.name = newname
            self.nameedit.SetValue(newname)
            self.nameedit.SetSelection(-1,-1)
 #           self.nameedit.SetFocus()
        
    def onHSplit(self, event):
        self.panelnode.split(x=0.5)

    def onVSplit(self, event):
        self.panelnode.split(y=0.5)

    def onHVSplit(self, event):
        self.panelnode.split(x=0.5, y=0.5)


######################################################################

class WidgetDlg(wx.Frame):
    def __init__(self, parent, id):
        wx.Frame.__init__(self, parent, id, "Panel widgets", style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT)

        self.lst = wx.ListCtrl(self, -1, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_HRULES | wx.LC_VRULES)
        self.lst.InsertColumn(0, "Panel")
        self.lst.InsertColumn(1, "Name")
        self.lst.InsertColumn(2, "Ref.count")
        self.lst.InsertStringItem(0, "Shell")
        self.lst.SetStringItem(0,1,"shell")
        self.lst.SetStringItem(0,2,"1")
        self.lst.InsertStringItem(1, "Shader Editor")
        self.lst.SetStringItem(1,1,"shadereditor")
        self.lst.SetStringItem(1,2,"1")
        self.lst.InsertStringItem(2, "3D Viewport")
        self.lst.SetStringItem(2,1,"front")
        self.lst.SetStringItem(2,2,"1")
        self.lst.InsertStringItem(3, "3D Viewport")
        self.lst.SetStringItem(3,1,"perspective")
        self.lst.SetStringItem(3,2,"1")

def widgetDlg():
    w = WidgetDlg(getApp().window.wx, -1)
    w.Show()

cgkit.cmds.widgetDlg = widgetDlg

######################################################################

# createPanelWidget
def createPanelWidget(name, modname=None):
    """Looks for a panel widget plugin and creates an instance of it.

    \param name (\c str) Widget name
    \param modname (\c str) Module name or None (which matches every module)
    """

    for odesc in pluginmanager.iterProtoObjects(PanelWidget):
        if odesc.name==name and (modname==None or odesc.plugindesc.modulename==modname):
            ClassName = odesc.object
            widget = ClassName()
            return widget
        
    return None

cgkit.cmds.createPanelWidget = createPanelWidget

######################################################################

if __name__=="__main__":

    c = SizeConstraint(5,5, wfixed=True)
    print c(172,22)
