home *** CD-ROM | disk | FTP | other *** search
/ Hackers Magazine 57 / CdHackersMagazineNr57.iso / Software / Multimedia / k3d-setup-0.7.11.0.exe / lib / site-packages / cgkit / GUI / panels.py < prev    next >
Encoding:
Python Source  |  2007-01-11  |  65.1 KB  |  2,044 lines

  1. # ***** BEGIN LICENSE BLOCK *****
  2. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3. #
  4. # The contents of this file are subject to the Mozilla Public License Version
  5. # 1.1 (the "License"); you may not use this file except in compliance with
  6. # the License. You may obtain a copy of the License at
  7. # http://www.mozilla.org/MPL/
  8. #
  9. # Software distributed under the License is distributed on an "AS IS" basis,
  10. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11. # for the specific language governing rights and limitations under the
  12. # License.
  13. #
  14. # The Original Code is the Python Computer Graphics Kit.
  15. #
  16. # The Initial Developer of the Original Code is Matthias Baas.
  17. # Portions created by the Initial Developer are Copyright (C) 2004
  18. # the Initial Developer. All Rights Reserved.
  19. #
  20. # Contributor(s):
  21. #
  22. # Alternatively, the contents of this file may be used under the terms of
  23. # either the GNU General Public License Version 2 or later (the "GPL"), or
  24. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  25. # in which case the provisions of the GPL or the LGPL are applicable instead
  26. # of those above. If you wish to allow use of your version of this file only
  27. # under the terms of either the GPL or the LGPL, and not to allow others to
  28. # use your version of this file under the terms of the MPL, indicate your
  29. # decision by deleting the provisions above and replace them with the notice
  30. # and other provisions required by the GPL or the LGPL. If you do not delete
  31. # the provisions above, a recipient may use your version of this file under
  32. # the terms of any one of the MPL, the GPL or the LGPL.
  33. #
  34. # ***** END LICENSE BLOCK *****
  35. # $Id: panels.py,v 1.2 2006/01/27 07:52:40 mbaas Exp $
  36.  
  37. import wx
  38. import cgkit.cmds
  39. #from cgkit import getApp, pluginmanager
  40. from cgkit import pluginmanager
  41. import re
  42. import panelicons
  43.  
  44. HORIZONTAL = 0x01
  45. VERTICAL   = 0x02
  46.  
  47. # Exceptions:
  48.  
  49. class DuplicateNames(Exception):
  50.     """Exception.
  51.  
  52.     Raised when there's a name clash between two nodes."""
  53.     pass
  54.  
  55. class LayoutError(Exception):
  56.     """Exception.
  57.  
  58.     Raises when a widget is added to a layout that already manages this
  59.     widget."""
  60.     pass
  61.  
  62. ######################################################################
  63.  
  64. # LayoutRepository
  65. class LayoutRepository(object):
  66.     def __init__(self):
  67.         self._layouts = []
  68.  
  69.  
  70. # PanelWidgetRepository
  71. class PanelWidgetRepository(object):
  72.     """Stores all panel widgets that have been created.
  73.     """
  74.     
  75.     def __init__(self):
  76.         """Constructor."""
  77.         self._widgets = []
  78.  
  79.     def __iter__(self):
  80.         """Return an iterator that iterates over all widgets.
  81.         """
  82.         return iter(self._widgets)
  83.  
  84.     def insert(self, widget):
  85.         """Inserts a widget into the repository.
  86.  
  87.         Nothing happens if the widget is already present.
  88.         
  89.         \param widget (\c PanelWidget) Widget that should be inserted into the repository
  90.         """
  91.  
  92.         if widget not in self._widgets:
  93.             self._widgets.append(widget)
  94.  
  95.     
  96.  
  97. # PanelWidget
  98. class PanelWidget(object):
  99.     """Container for a wx widget that serves as a panel widget.
  100.  
  101.     It's not allowed to have several %PanelWidget object share the same
  102.     wx object.
  103.  
  104.     Attributes:
  105.  
  106.     - wx (\c wx \c widget)
  107.     - name (\c str) The name of the widget (this mirrors the name of the wx widget)
  108.     """
  109.     def __init__(self, wx=None):
  110.         """Constructor.
  111.  
  112.         \param wx (\c wx \c widget) The managed wx widget
  113.         """
  114.         self._wx = wx
  115.         self._active = False
  116.         self._usedby = []
  117.  
  118.     def __str__(self):
  119.         return '<%s "%s">'%(self.__class__.__name__, self.name)
  120.  
  121.     # isActive
  122.     def isActive(self):
  123.         """Check if the widget is currently active.
  124.  
  125.         \return True if active
  126.         """
  127.         return self._active
  128.  
  129.     # activate
  130.     def activate(self, root):
  131.         """Activate the node.
  132.  
  133.         This method marks the widget as active.
  134.  
  135.         Derived classes may use this method to create the wx widget. In
  136.         this case they have to use root.wx as wx parent.        
  137.  
  138.         \param root (\c Panels) The panels object where the widget is located.
  139.         """
  140.         self._active = True
  141.  
  142.     # deactivate
  143.     def deactivate(self):
  144.         """Deactivate the node.
  145.  
  146.         This method marks the widget as inactive.
  147.         """
  148.         self._active = False
  149.  
  150.     # isInUse
  151.     def isInUse(self):
  152.         """Returns whether the widget is used by any layout or not.
  153.  
  154.         \return True if the widget is used by a layout (be it active or not).
  155.         """
  156.         return self._usedby!=[]
  157.  
  158.     # isUsedBy
  159.     def isUsedBy(self, layoutroot):
  160.         """Checks if the widget is used by the given layout.
  161.  
  162.         \param layoutroot (\c ILayoutNode) The root node of the layout in question
  163.         """
  164.         for n in self._usedby:
  165.             if layoutroot==n._layoutRoot():
  166.                 return True
  167.         return False
  168.  
  169.     # acquire
  170.     def acquire(self, panelnode):
  171.         """Mark the widget as being used by the given node.
  172.  
  173.         \param panelnode (\c PanelNode) The panel node that controls the position and size of the widget
  174.         """
  175.         self._usedby.append(panelnode)
  176.  
  177.     # release
  178.     def release(self, panelnode):
  179.         """Mark the widget as being no longer in use by the given node.
  180.  
  181.         \param panelnode (\c PanelNode) The panel node that was controlling the position and size of the widget
  182.         """        
  183.         self._usedby.remove(panelnode)
  184.  
  185.     # hide
  186.     def hide(self):
  187.         if self._wx!=None:
  188.             self._wx.Hide()
  189.  
  190.     # show
  191.     def show(self):
  192.         if self._wx!=None:
  193.             self._wx.Show()
  194.  
  195.     def setGeom(self, x, y, w, h):
  196.         if self._wx!=None:
  197.             self._wx.SetDimensions(x,y,w,h)
  198.         
  199.  
  200.     ######################################################################
  201.     ## protected:
  202.     
  203.     # "wx" property...
  204.     
  205.     def _getWx(self):
  206.         """Return the encapsulated wx widget.
  207.  
  208.         This method is used for retrieving the \a wx property.
  209.  
  210.         \return wxPython object.
  211.         """
  212.         return self._wx
  213.  
  214.     def _setWx(self, widget):
  215.         """Set the wx widget.
  216.  
  217.         This method is used for setting the \a wx property.
  218.  
  219.         \param widget (\c wx object) wx Widget
  220.         """
  221.         self._wx = widget
  222.  
  223.     wx = property(_getWx, _setWx, None, "Encapsulated wx widget")
  224.  
  225.     # "name" property...
  226.     
  227.     def _getName(self):
  228.         """Return the name of the widget.
  229.  
  230.         This method is used for retrieving the \a name property.
  231.  
  232.         \return Name (\c str)
  233.         """
  234.         if self._wx==None:
  235.             return ""
  236.         else:
  237.             return str(self._wx.GetName())
  238.  
  239.     def _setName(self, name):
  240.         """Set the name of the widget.
  241.  
  242.         This method is used for setting the \a name property.
  243.  
  244.         \param name (\c str) New name
  245.         """
  246.         if self._wx!=None:
  247.             self._wx.SetName(name)
  248.  
  249.     name = property(_getName, _setName, None, "Widget name")
  250.  
  251. ######################################################################
  252.     
  253. # Panels
  254. class Panels(object):
  255.     """%Panels area.
  256.  
  257.     Attributes:
  258.  
  259.     - \a layout (\c ILayoutNode): Panel layout tree
  260.     - \a wx (\c wx widget): Corresponding wx widget
  261.     - \a repository (\c PanelWidgetRepository) Widget repository
  262.  
  263.     Panel nodes can be accessed in one of the following ways:
  264.  
  265.     - The attribute \a mousepanel contains the panel that's currently
  266.       underneath the mouse pointer (or None if the mouse is outside)
  267.     - The attribute \a activepanel contains the currently active panel
  268.       (i.e. the one that's activatable and that received the last mouse
  269.       click)
  270.     - Via attribute access you can address the panels by their name
  271.     - You can iterate over all panel nodes
  272.     
  273.  
  274.     """
  275.         
  276.     def __init__(self, parent, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.CLIP_CHILDREN):
  277.         """Constructor.
  278.         """
  279. #        wx.Window.__init__(self, parent, id, pos, size, style)
  280.  
  281.         self._wx = wx.Window(parent, id, pos, size, style)
  282.  
  283.         self._repository = PanelWidgetRepository()
  284.  
  285.         self._layout = LayoutNode(root=self)
  286.  
  287.         self.mousepanel = None
  288.         self.activepanel = None
  289.  
  290.         self._xclick = None
  291.         self._yclick = None
  292.         self._xsplitter = None
  293.         self._ysplitter = None
  294.         self._splitter_flags = None
  295.         self._layout_node = None
  296.  
  297.         self._current_cursor = None
  298.         self._hcrsr = wx.StockCursor(wx.CURSOR_SIZEWE)
  299.         self._vcrsr = wx.StockCursor(wx.CURSOR_SIZENS)
  300.         self._hvcrsr = wx.StockCursor(wx.CURSOR_SIZING)
  301.  
  302.         wx.EVT_SIZE(self._wx, self.onSize)
  303.         wx.EVT_LEFT_DOWN(self._wx, self.onLeftDown)
  304.         wx.EVT_LEFT_UP(self._wx, self.onLeftUp)
  305.         wx.EVT_MOTION(self._wx, self.onMouseMove)
  306.         wx.EVT_PAINT(self._wx, self.onPaint)
  307.  
  308. #    def __repr__(self):
  309. #        res = "<Panellayout>"
  310. #        return res
  311.  
  312.     def __str__(self):
  313.         return str(self._layout)
  314.  
  315.     def updateLayout(self):
  316.         w,h = self._wx.GetClientSizeTuple()
  317.         self._layout.setGeom(0,0,w,h)
  318.         self._wx.Refresh(False)
  319.  
  320.     ######################################################################
  321.     ## protected:
  322.         
  323.     def __iter__(self):
  324.         if self._layout!=None:
  325.             return iter(self._layout.panelNodes())
  326.         else:
  327.             return iter([])
  328.  
  329.     def __getitem__(self, key):
  330.         if self._layout!=None:
  331.             return self._layout.findNodeByName(key)
  332.         else:
  333.             return None
  334.  
  335.     def __getattr__(self, name):
  336.         if self._layout!=None:
  337.             return self._layout.findNodeByName(name)
  338.         else:
  339.             return None
  340.         
  341.     def onSize(self, event):
  342.         w,h = event.GetSize()
  343.         self._layout.setGeom(0,0,w,h)
  344.  
  345.     def onEnterPanel(self, panel):
  346.         self.mousepanel = panel
  347.         self._wx.SetCursor(wx.NullCursor)
  348. #        print "enter", panel.name
  349.  
  350.     def onLeavePanel(self, panel):
  351.         pass
  352. #        print "leave", panel.name
  353.  
  354.     def onClickPanel(self, panel):
  355.         self.activepanel = panel
  356.         self._wx.Refresh(False)
  357.  
  358.     def onLeftDown(self, event):
  359.         x = event.GetX()
  360.         y = event.GetY()
  361.         flags, node = self._layout.findPanel(x,y)
  362.         if flags!=0:
  363.             self._splitter_flags = flags
  364.             self._layout_node = node
  365.             self._xclick = x
  366.             self._yclick = y
  367.             self._xsplitter, self._ysplitter = node.getPixelPos()
  368.             self._wx.CaptureMouse()
  369.             
  370.     def onLeftUp(self, event):
  371.         if self._xclick!=None:
  372.             self._xclick = None
  373.             self._yclick = None
  374.             self._xsplitter = None
  375.             self._ysplitter = None
  376.             self._splitter_flags = None
  377.             self._layout_node = None
  378.             self._wx.ReleaseMouse()
  379.             self._wx.SetCursor(wx.NullCursor)
  380. #            self.Refresh(False)
  381.         
  382.     def onMouseMove(self, event):
  383.         x = event.GetX()
  384.         y = event.GetY()
  385. #        print x,y
  386.         if self._xclick!=None:
  387.             dx = x-self._xclick
  388.             dy = y-self._yclick
  389.             if self._splitter_flags & HORIZONTAL==0:
  390.                 dx = 0
  391.             if self._splitter_flags & VERTICAL==0:
  392.                 dy = 0
  393.             self._layout_node.setPixelPos(self._xsplitter+dx, self._ysplitter+dy, True)
  394.             self._wx.Refresh(False)
  395.         else:
  396.             flags, node = self._layout.findPanel(x,y)
  397.             if flags==HORIZONTAL:
  398.                 self._wx.SetCursor(self._hcrsr)
  399.             elif flags==VERTICAL:
  400.                 self._wx.SetCursor(self._vcrsr)
  401.             elif flags==HORIZONTAL|VERTICAL:
  402.                 self._wx.SetCursor(self._hvcrsr)
  403.             else:
  404.                 self._wx.SetCursor(wx.NullCursor)
  405.             
  406.     def onPaint(self, event):
  407.         dc = wx.PaintDC(self._wx)
  408.  
  409.         dc.BeginDrawing()
  410.  
  411.         # Paint empty panels black
  412.         dc.SetPen(wx.TRANSPARENT_PEN)
  413.         dc.SetBrush(wx.BLACK_BRUSH)
  414.         for n in self:
  415.             if n.widget==None:
  416.                 dc.DrawRectangle(n._x0, n._y0, n._width, n._height)
  417.  
  418.         # Draw a border around the current panel
  419.         activepen = wx.Pen(wx.Color(0,0,255), 3, wx.SOLID)
  420.         dc.SetPen(activepen)
  421.         panel = self.activepanel
  422.         if panel!=None:
  423.             dc.DrawRectangle(panel._x0, panel._y0, panel._width, panel._height)
  424.  
  425.         dc.EndDrawing()
  426.  
  427.     # "layout" property...
  428.     
  429.     def _getLayout(self):
  430.         """Return the layout tree.
  431.  
  432.         This method is used for retrieving the \a layout property.
  433.  
  434.         \return Layout tree (\c ILayoutNode).
  435.         """
  436.         return self._layout
  437.  
  438.     def _setLayout(self, layout):
  439.         """Set the layout tree.
  440.  
  441.         This method is used for setting the \a layout property.
  442.  
  443.         \param layout (\c ILayoutNode) Layout tree.
  444.         """
  445.         if self._layout!=None:
  446.             self._layout.deactivate()
  447.         self._layout = layout
  448.         self.updateLayout()
  449.         self._layout.activate(self)
  450.  
  451.     layout = property(_getLayout, _setLayout, None, "Panel layout tree")
  452.  
  453.     # "wx" property...
  454.     
  455.     def _getWx(self):
  456.         """Return the encapsulated wx widget.
  457.  
  458.         This method is used for retrieving the \a wx property.
  459.  
  460.         \return wxPython object.
  461.         """
  462.         return self._wx
  463.  
  464.     wx = property(_getWx, None, None, "Encapsulated wx widget")
  465.  
  466.     # "repository" property...
  467.     
  468.     def _getRepository(self):
  469.         """Return the widget repository.
  470.  
  471.         This method is used for retrieving the \a repository property.
  472.  
  473.         \return Widget repository (\c PanelWidgetRepository)
  474.         """
  475.         return self._repository
  476.  
  477.     repository = property(_getRepository, None, None, "Panel widget repository")
  478.         
  479.  
  480.  
  481. ######################################################################
  482. ######################################################################
  483.  
  484. # ILayoutNode
  485. class ILayoutNode(object):
  486.     """This is the base class for a node in the layout tree.
  487.  
  488.     This class maintains the two attributes \em _parent (parent node)
  489.     and \em _root (corresponding Panels object). The root attribute of
  490.     every node in a tree must always point to the same %Panels object.
  491.     If the layout is not active, then the %Panels object is None.
  492.  
  493.     The root object is set when the layout gets activated (i.e attached
  494.     to a %Panels object). If a new node is created inside an active layout,
  495.     the root has to be provided in the constructor.
  496.  
  497.     Attributes:
  498.  
  499.     - \b name (\c str): Node name
  500.     """
  501.     
  502.     def __init__(self, root=None, name=None):
  503.         """Constructor.
  504.  
  505.         \param root (\c Panels) Panels object which belongs to this layout or None.
  506.         """
  507.         self._parent = None
  508.         self._root = root
  509.  
  510.         # Node name
  511.         self._name = name
  512.         
  513.  
  514.     # activate
  515.     def activate(self, root):
  516.         """Activate the node.
  517.  
  518.         Attaches the layout to the Panels object \a root. If the node has
  519.         children this method has to call their %activate() method as well.
  520.         If there are already PanelWidgets attached to this layout this
  521.         method has to make them visible and do the layout.
  522.  
  523.         This method is called by the %Panels object whenever the layout
  524.         is switched.
  525.  
  526.         \pre \a root must not have another layout activated.
  527.         \pre self must be inactive
  528.         \param root (\c Panels) The panels object to which the layout is attached.
  529.         \see deactivate()
  530.         """
  531.         pass
  532.  
  533.     # deactivate
  534.     def deactivate(self):
  535.         """Deactivates this node.
  536.  
  537.         Detaches a layout from its root Panels object. All the widgets
  538.         have to be hidden during deactivation.
  539.  
  540.         This method is called by the %Panels object whenever the layout
  541.         is switched.
  542.  
  543.         \pre The layout has to be previously activated.
  544.         \see activate()
  545.         """
  546.         pass
  547.  
  548.     # isActive
  549.     def isActive(self):
  550.         """Checks if this layout is active or not.
  551.  
  552.         \return True if the layout is active
  553.         """
  554.         return self._root!=None
  555.  
  556.     # setRoot
  557.     def setRoot(self, root):
  558.         """Set a new root.
  559.  
  560.         If the node contains children this method has to be overwritten
  561.         and it has to set the new root on the children as well.
  562.  
  563.         This method is for internal use to propagate a new root through
  564.         the entire tree when the layout is activated or deactivated.
  565.  
  566.         \param root (\c Panels) New root object.
  567.         """
  568.         self._root = root
  569.  
  570.     # setGeom
  571.     def setGeom(self, x, y, width, height):
  572.         """Set the position and size of the managed area.
  573.  
  574.         \param x (\c int) X position in pixel
  575.         \param y (\c int) Y position in pixel
  576.         \param width (\c int) Width in pixel
  577.         \param height (\c int) Height in pixel
  578.         """
  579.         pass
  580.  
  581.     # applyConstraint
  582.     def applyConstraint(self, width, height, interactive=False):
  583.         """Check if any active constraints accept the given width and height.
  584.  
  585.         This method checks if \a width and \a height are acceptable for
  586.         this node. If they are, the method has to return the width and
  587.         height unmodified, otherwise an adjusted value has to be returned
  588.         that's as close as possible to the input values.
  589.  
  590.         The parameter \a interactive specifies if the resizing was
  591.         directly initiated by the user (i.e. a splitter was dragged).
  592.         The usual policy is to allow resizing in this case. Otherwise
  593.         the size is kept fixed (for example, if you only resize the entire
  594.         application window).
  595.  
  596.         \param width (\c int)  Width to check
  597.         \param height (\c int)  Height to check
  598.         \param interactive (\c bool)  Flag that specifies if the resizing
  599.                stems from a user interaction (the user drags a splitter)
  600.         \return Tuple (width, height) with adjusted size values
  601.         """
  602.         pass
  603.  
  604.     # isResizable
  605.     def isResizable(self, direction):
  606.         """Check if the size of the node may be changed by the user.
  607.  
  608.         \param direction (\c int) A combination of HORIZONTAL and VERTICAL
  609.         \return True if resizable.
  610.         """
  611.         pass
  612.  
  613.     # panelNodes
  614.     def panelNodes(self):
  615.         """Return a list of all panel nodes in this subtree.
  616.  
  617.         This method only has to return the panel nodes, i.e. the leafes
  618.         of the tree. LayoutNode objects are skipped.
  619.  
  620.         This method enables the Panels object to iterate over all
  621.         panel nodes.
  622.  
  623.         \return A list of PanelNode objects.
  624.         """
  625.         pass
  626.  
  627.     # findNodeByName
  628.     def findNodeByName(self, name):
  629.         """Returns the node in this subtree with the given name.
  630.  
  631.         \param name (\c str) Node name
  632.         \return Node or None.
  633.         """
  634.         pass
  635.  
  636.     # makeNameUnique
  637.     def makeNameUnique(self, name):
  638.         """Makes a given name unique by appending an appropriate number.
  639.  
  640.         The argument \a name is checked if it's already used somewhere
  641.         in the tree. If it's unused it is returned unchanged.
  642.         Otherwise a number is appended that makes the name unique (if
  643.         the original name aready contained a number, this number is
  644.         increased).
  645.  
  646.         \param name (\c str) Input name
  647.         """
  648.         while self.findNodeByName(name)!=None:
  649.             m = re.search("[0-9]+$", name)
  650.             if m==None:
  651.                 name = name+"1"
  652.             else:
  653.                 n = int(name[m.start():])
  654.                 name = name[:m.start()]+str(n+1)
  655.         return name
  656.             
  657.  
  658.     # findPanel
  659.     def findPanel(self, x, y):
  660.         return (0, self)
  661.  
  662.     # showConfigPanel
  663.     def showConfigPanel(self):
  664.         """Show the configuration panel.
  665.  
  666.         A LayoutNode passes this method call forward to its children
  667.         which finally create the panel.
  668.         """
  669.         pass
  670.  
  671.     # hideConfigPanel
  672.     def hideConfigPanel(self):
  673.         """Hide the configuration panel.
  674.  
  675.         A LayoutNode passes this method call forward to its children
  676.         which finally remove the panel.
  677.         """
  678.         pass
  679.  
  680.     ######################################################################
  681.     ## protected:
  682.  
  683.     def _layoutRoot(self):
  684.         """Return the root layout node.
  685.  
  686.         \param Root layout node (\c ILayoutNode).
  687.         """
  688.         p = self
  689.         while p._parent!=None:
  690.             p = p._parent
  691.         return p
  692.  
  693.     # "name" property...
  694.     
  695.     def _getName(self):
  696.         """Return the node name.
  697.  
  698.         This method is used for retrieving the \a name property.
  699.  
  700.         \return Name (\c str)
  701.         """
  702.         return self._name
  703.  
  704.     def _setName(self, name):
  705.         """Set the node name.
  706.  
  707.         This method is used for setting the \a name property.
  708.  
  709.         \param name (\c str) New name
  710.         """
  711.         # Check if the new name exists already....
  712.         prevname = self._name
  713.         self._name = None
  714.         if self._layoutRoot().findNodeByName(name)!=None:
  715.             self._name = prevname
  716.             raise DuplicateNames, 'There is already a node with the name "%s"'%name
  717.  
  718.         self._name = name
  719.         
  720.     name = property(_getName, _setName, None, "Name")
  721.  
  722.  
  723. # LayoutNodeCoords
  724. class LayoutNodeCoords:
  725.     """Helper class for the LayoutNode class.
  726.     """
  727.     def __init__(self,x0=0,x1=0,x2=0,x3=0,y0=0,y1=0,y2=0,y3=0):
  728.         self.x0 = x0
  729.         self.y0 = y0
  730.         self.x1 = x1
  731.         self.y1 = y1
  732.         self.x2 = x2
  733.         self.y2 = y2
  734.         self.x3 = x3
  735.         self.y3 = y3
  736.  
  737.  
  738. # LayoutNode
  739. class LayoutNode(ILayoutNode):
  740.     """Layout node.
  741.  
  742.     This class represents a layout scheme that can manage areas
  743.     that are laid out as depicted in the following image:
  744.     
  745.     \image html "panel_layout_node.png" "Layout"
  746.  
  747.     This class is also used if the region should only be split in
  748.     horizontal or vertical direction. If the region is split horizontally
  749.     then x1 = x2 = x3, if it's split vertically then y1 = y2 = y3.
  750.     The following constraints are always enforced on the coordinates:
  751.     x0 <= x1 <= x2 <= x3 and y0 <= y1 <= y2 <= y3. If a vertical splitter
  752.     is present then x1 < x2, if a horizontal splitter is present then
  753.     y1 < y2. All coordinates of all layout nodes are always relative to
  754.     the widget that's associated with the layout hierarchy.
  755.  
  756.     There are two coordinates associated with each splitter. One is
  757.     the \em logical coordinate that lies within [0,1] (0=left/top,
  758.     1=right/bottom) and the other is the true \em pixel \em coordinate
  759.     relative to the parent widget. In the presence of size constraints
  760.     among the children panels/layouts the logical coordinates remain
  761.     unchanged whereas the true pixel position changes. The pixel coordinates
  762.     always try to represent the logical coordinates as close as possible.
  763.     """
  764.     def  __init__(self, x=0, y=0, width=0, height=0, root=None, name=None,
  765.                   splittertype=HORIZONTAL|VERTICAL, x0=0.5, y0=0.5,
  766.                   children=None):
  767.         """Constructor.
  768.  
  769.         The arguments should be given as keyword arguments.
  770.  
  771.         \param x (\c int) Initial X position
  772.         \param y (\c int) Initial Y position
  773.         \param width (\c int) Initial width
  774.         \param height (\c int) Initial height
  775.         \param root (\c Panels) Root object
  776.         \param name (\c str) Node name
  777.         \param splittertype (\c int) A combination of HORIZONTAL and VERTICAL
  778.         \param x0 (\c float) Initial logical splitter x position
  779.         \param y0 (\c float) Initial logical splitter y position
  780.         \param children  A tuple with children nodes (either LayoutNode or PanelNode). The number of children is determined by the splitter type.
  781.         """
  782.         
  783.         ILayoutNode.__init__(self, root=root, name=name)
  784.  
  785.         # This is the region that's managed by this node
  786.         # This coordinates are relative to the root widget
  787.         # x1/y1 is the true pixel coordinate of the splitter 
  788.         self._x0 = 0
  789.         self._y0 = 0
  790.         self._x1 = 0
  791.         self._y1 = 0
  792.         self._x2 = 0
  793.         self._y2 = 0
  794.         self._x3 = 0
  795.         self._y3 = 0
  796.  
  797.         # Logical splitter coordinate (0-1)
  798.         self._splitter_x = x0
  799.         self._splitter_y = y0
  800.  
  801.         # Splitter width in pixel
  802.         self._splitter_width = 6
  803.  
  804.         # Splitter type
  805.         self._splitter_type = splittertype
  806.  
  807.         # Children nodes (may not be None)
  808.         # Upper left
  809.         self._child00 = None
  810.         # Upper right
  811.         self._child01 = None
  812.         # Lower left
  813.         self._child10 = None
  814.         # Lower right
  815.         self._child11 = None
  816.         
  817.         if children==None:
  818.             if splittertype==HORIZONTAL|VERTICAL:
  819.                 children = (None,None,None,None)
  820.             else:
  821.                 children = (None, None)
  822.         self.setChildren(children)
  823.  
  824.         self.setGeom(x, y, width, height)
  825.  
  826.     def __str__(self):
  827.         return self._toStr(self, "", 0)
  828.  
  829.     def _toStr(self, node, res, d):
  830.         """Helper method for the __str__ operator."""
  831.  
  832.         if isinstance(node, LayoutNode):
  833.             s="?"
  834.             if node._splitter_type==HORIZONTAL:
  835.                 s = "horizontal"
  836.             elif node._splitter_type==VERTICAL:
  837.                 s = "vertical"
  838.             elif node._splitter_type==HORIZONTAL | VERTICAL:
  839.                 s = "horizontal & vertical"
  840.             if node.name!=None:
  841.                 name = '"%s"'%node.name
  842.             else:
  843.                 name = "<None>"
  844.             s = 'LayoutNode %s active:%d type:%s'%(name, node.isActive(), s)
  845.             res += d*" "+s+"\n"
  846.             childs = node.getChildren()
  847.             for c in childs:
  848.                 res = self._toStr(c, res, d+2)
  849.             return res
  850.         elif isinstance(node, PanelNode):
  851.             return res + d*" "+str(node)+"\n"
  852.         else:
  853.             return res + d*" "+"???\n"
  854.  
  855.     def activate(self, root):
  856.         self.setRoot(root)
  857.         self._child00.activate(root)
  858.         self._child01.activate(root)
  859.         self._child10.activate(root)
  860.         self._child11.activate(root)
  861.  
  862.     def deactivate(self):
  863.         self.setRoot(None)
  864.         self._child00.deactivate()
  865.         self._child01.deactivate()
  866.         self._child10.deactivate()
  867.         self._child11.deactivate()
  868.  
  869.     def setRoot(self, root):
  870.         ILayoutNode.setRoot(self, root)
  871.         self._child00.setRoot(root)
  872.         self._child01.setRoot(root)
  873.         self._child10.setRoot(root)
  874.         self._child11.setRoot(root)
  875.  
  876.     def getSplitterType(self):
  877.         """Return the type of layout node.
  878.  
  879.         Returns the direction that are split by a splitter. A horizontal
  880.         splitter splits in X direction, a vertical splitter in Y direction.
  881.  
  882.         \return A combination of HORIZONTAL and VERTICAL
  883.         \see setSplitterType()
  884.         """
  885.         return self._splitter_type
  886.  
  887.     def setSplitterType(self, stype):
  888.         """Set the layout node type.
  889.  
  890.         \param stype (\c int) A combination of HORIZONTAL and VERTICAL
  891.         \see getSplitterType()
  892.         """
  893.         self._splitter_type = stype
  894.         
  895.  
  896.     # setGeom
  897.     def setGeom(self, x, y, width, height):
  898.         # Initialize coordinates...
  899.         self._x0 = int(x)
  900.         self._y0 = int(y)
  901.         self._x3 = int(x+width)
  902.         self._y3 = int(y+height)
  903.         # The following call updates the pixel position of the splitter
  904.         self.setLogicalPos(self._splitter_x, self._splitter_y)
  905.  
  906.     def findNodeByName(self, name):
  907.         if self._name==name:
  908.             return self
  909.         
  910.         res = self._child00.findNodeByName(name)
  911.         if res==None:
  912.             res = self._child01.findNodeByName(name)
  913.         if res==None:
  914.             res = self._child10.findNodeByName(name)
  915.         if res==None:
  916.             res = self._child11.findNodeByName(name)
  917.         return res
  918.  
  919.     def showConfigPanel(self):
  920.         self._child00.showConfigPanel()
  921.         if self._splitter_type==HORIZONTAL:
  922.             self._child01.showConfigPanel()
  923.         elif self._splitter_type==VERTICAL:
  924.             self._child10.showConfigPanel()
  925.         else:
  926.             self._child01.showConfigPanel()
  927.             self._child10.showConfigPanel()
  928.             self._child11.showConfigPanel()
  929.  
  930.     def hideConfigPanel(self):
  931.         self._child00.hideConfigPanel()
  932.         if self._splitter_type==HORIZONTAL:
  933.             self._child01.hideConfigPanel()
  934.         elif self._splitter_type==VERTICAL:
  935.             self._child10.hideConfigPanel()
  936.         else:
  937.             self._child01.hideConfigPanel()
  938.             self._child10.hideConfigPanel()
  939.             self._child11.hideConfigPanel()    
  940.         
  941.     def getChildren(self):
  942.         """Return the children nodes.
  943.  
  944.         The number of children returned depends on the splitter type
  945.         (either 2 or 4).
  946.  
  947.         \return A tuple with two or four children nodes (\c ILayoutNode).
  948.         """
  949.         if self._splitter_type==HORIZONTAL:
  950.             return self._child00, self._child01
  951.         elif self._splitter_type==VERTICAL:
  952.             return self._child00, self._child10
  953.         else:
  954.             return self._child00, self._child10, self._child01, self._child11
  955.  
  956.  
  957.     def setChildren(self, childs):
  958.         """Set the children of this node.
  959.  
  960.         \a childs is a tuple of nodes which should be set as children.
  961.         A node may also be None in which case an empty PanelNode is set.
  962.         The children nodes must not be part of another tree (if they are
  963.         an exception is thrown).
  964.         
  965.         \param childs A sequence containing the children. The number of children depends on the splitter type (2 or 4).
  966.         """
  967.  
  968.         if self._splitter_type==HORIZONTAL:
  969.             self.setChild(childs[0], 0)
  970.             self.setChild(childs[1], 1)
  971.             self.setChild(None, 2)
  972.             self.setChild(None, 3)
  973.         elif self._splitter_type==VERTICAL:
  974.             self.setChild(childs[0], 0)
  975.             self.setChild(None, 1)
  976.             self.setChild(childs[1], 2)
  977.             self.setChild(None, 3)
  978.         else:
  979.             self.setChild(childs[0], 0)
  980.             self.setChild(childs[1], 1)
  981.             self.setChild(childs[2], 2)
  982.             self.setChild(childs[3], 3)
  983.  
  984.  
  985.     def childIndex(self, child):
  986.         """Return the index of the children node.
  987.  
  988.         \a child must actually be a child of self, otherwise a ValueError
  989.         exception is thrown.
  990.  
  991.         \param child (\c ILayoutNode) Children node
  992.         \return Index (0-3)
  993.         """
  994.         
  995.         lst = [self._child00, self._child01, self._child10, self._child11]
  996.         return lst.index(child)
  997.  
  998.     def getChild(self, idx):
  999.         """Return a single children node.
  1000.  
  1001.         \param idx (\c int) Children index (0-3)
  1002.         \return Children node (\c ILayoutNode).
  1003.         """
  1004.         if idx<0 or idx>3:
  1005.             raise IndexError, "Children index out of range (%d)"%idx
  1006.  
  1007.         return [self._child00, self._child01, self._child10, self._child11][idx]
  1008.  
  1009.     def setChild(self, child, idx):
  1010.         """Replace a single children node.
  1011.  
  1012.         \param child (\c ILayoutNode) New children or None
  1013.         \param idx (\c int) Children index (0-3)
  1014.         \todo Pruefen, ob Child tree ok ist (keine doppelten Widgets oder Namen)
  1015.         """
  1016.         if idx<0 or idx>3:
  1017.             raise IndexError, "Children index out of range (%d)"%idx
  1018.         if child==None:
  1019. #            if self._root!=None:
  1020. #                black = wx.Window(self._root.wx, -1)
  1021. #                black.SetBackgroundColour(wx.Colour())
  1022. #            else:
  1023. #                black = None
  1024. #            child = PanelNode(widget=black)
  1025.             child = PanelNode()
  1026.         if child._parent!=None:
  1027.             raise ValueError, 'The panel layout node is already part of a layout tree.'
  1028.             
  1029.         if idx==0:
  1030.             self.removeChild(self._child00)
  1031.             self._child00 = child
  1032.         elif idx==1:
  1033.             self.removeChild(self._child01)
  1034.             self._child01 = child
  1035.         elif idx==2:
  1036.             self.removeChild(self._child10)
  1037.             self._child10 = child
  1038.         elif idx==3:
  1039.             self.removeChild(self._child11)
  1040.             self._child11 = child
  1041.  
  1042.         child._parent = self
  1043.         if self.isActive():
  1044.             child.activate(self._root)
  1045.  
  1046.  
  1047.     def removeChild(self, child):
  1048.         """Remove a child node.
  1049.  
  1050.         \a child may be None in which case the method returns immediately.
  1051.  
  1052.         \param child (\c ILayoutNode) Children which is to be removed or None
  1053.         """
  1054.         if child==None:
  1055.             return
  1056.         
  1057.         if child==self._child00:
  1058.             self._child00 = None
  1059.         elif child==self._child01:
  1060.             self._child01 = None
  1061.         elif child==self._child10:
  1062.             self._child10 = None
  1063.         elif child==self._child11:
  1064.             self._child11 = None
  1065.         else:
  1066.             raise ValueError, "Layout node is not a children node."
  1067.  
  1068.         child._parent = None
  1069.         if self.isActive():
  1070.             child.deactivate()
  1071.  
  1072.         self._fillEmptyChildren()
  1073.  
  1074.     def remove(self, idx=0):
  1075.         """Remove this layout node and replace it with a children node.
  1076.         """
  1077.  
  1078.         child = self.getChild(idx)
  1079.         self.setChildren((None,None,None,None))
  1080.  
  1081.         root = self._root
  1082.  
  1083.         parent = self._parent
  1084.         if parent!=None:
  1085.             i = parent.childIndex(self)
  1086.             parent.removeChild(self)
  1087.             parent.setChild(child, i)
  1088.         else:
  1089.             if root!=None:
  1090.                 root.layout = child
  1091.  
  1092.         if root!=None:
  1093.             root.updateLayout()
  1094.         
  1095.         
  1096.     # isResizable
  1097.     def isResizable(self, direction):
  1098.         res = False
  1099.         if direction&HORIZONTAL:
  1100.             res |= ( (self._child00.isResizable(HORIZONTAL) and
  1101.                       self._child10.isResizable(HORIZONTAL) )
  1102.                      or
  1103.                      (self._child01.isResizable(HORIZONTAL) and
  1104.                       self._child11.isResizable(HORIZONTAL)) )
  1105.         if direction&VERTICAL:
  1106.             res |= ( (self._child00.isResizable(VERTICAL) and
  1107.                       self._child01.isResizable(VERTICAL) )
  1108.                      or
  1109.                      (self._child10.isResizable(VERTICAL) and
  1110.                       self._child11.isResizable(VERTICAL)) )
  1111.  
  1112.         return res
  1113.  
  1114.     # applyConstraint
  1115.     def applyConstraint(self, width, height, interactive=False):
  1116.         coords = LayoutNodeCoords(self._x0, 0, 0, self._x0+width,
  1117.                                   self._y0, 0, 0, self._y0+height)
  1118.         self._calcSplitterCoords(self._splitter_x, self._splitter_y,
  1119.                                  coords, interactive)
  1120.         return (coords.x3-coords.x0, coords.y3-coords.y0)
  1121.  
  1122.     # panelNodes
  1123.     def panelNodes(self):
  1124.         res = []
  1125.         for c in self.getChildren():
  1126.             res += c.panelNodes()
  1127.         return res
  1128.  
  1129.     # isInside
  1130.     def isInside(self, x, y):
  1131.         """Check if the mouse coordinate (x,y) is inside the managed region.
  1132.  
  1133.         The coordinate must be given relative to the root widget.
  1134.  
  1135.         \param x (\c int) X coordinate
  1136.         \param y (\c int) Y coordinate
  1137.         """
  1138.         return (x>=self._x0 and x<self._x3 and y>=self._y0 and y<self._y3)
  1139.  
  1140.     # findPanel
  1141.     def findPanel(self, x, y):
  1142.         """Find a panel by mouse position.
  1143.  
  1144.         The method assumes that x,y lies somehwere in the managed region.
  1145.  
  1146.         The return value contains a flag that has the following values:
  1147.  
  1148.         - 0: A panel was hit
  1149.         - 0x1: A horizontal splitter was hit
  1150.         - 0x2: A vertical splitter was hit
  1151.         - 0x3: The middle of a splitter was hit
  1152.  
  1153.         \return Tuple (Flag, LayoutNode)
  1154.         """
  1155.         # Left column?
  1156.         if x<self._x1:
  1157.             # Upper left panel?
  1158.             if y<self._y1:
  1159.                 return self._child00.findPanel(x,y)
  1160.             # Vertical splitter?
  1161.             elif y<self._y2:
  1162.                 return (VERTICAL, self)
  1163.             # Lower left panel
  1164.             else:
  1165.                 return self._child10.findPanel(x,y)
  1166.         # Horizontal splitter? (or middle)
  1167.         elif x<self._x2:
  1168.             if y>=self._y1 and y<self._y2:
  1169.                 return (VERTICAL | HORIZONTAL, self)
  1170.             else:
  1171.                 return (HORIZONTAL, self)
  1172.         # Right column
  1173.         else:
  1174.             # Upper right panel?
  1175.             if y<self._y1:
  1176.                 return self._child01.findPanel(x,y)
  1177.             # Vertical splitter?
  1178.             elif y<self._y2:
  1179.                 return (VERTICAL, self)
  1180.             # Lower right panel
  1181.             else:
  1182.                 return self._child11.findPanel(x,y)
  1183.  
  1184.     # setLogicalPos
  1185.     def setLogicalPos(self, x0=0.5, y0=0.5, interactive=False):
  1186.         """Set the logical position of the splitters.
  1187.  
  1188.         The pixel position is updated as well.
  1189.         
  1190.         \param x0 (\c float) Logical X position 
  1191.         \param y0 (\c float) Logical Y position 
  1192.         """
  1193.  
  1194.         self._splitter_x = x0
  1195.         self._splitter_y = y0
  1196.  
  1197.         coords = LayoutNodeCoords(self._x0, 0, 0, self._x3,
  1198.                                   self._y0, 0, 0, self._y3)
  1199.         self._calcSplitterCoords(x0, y0, coords, interactive)
  1200.         self._x1 = coords.x1
  1201.         self._x2 = coords.x2
  1202.         self._y1 = coords.y1
  1203.         self._y2 = coords.y2
  1204.         self._update()
  1205.  
  1206.     # getLogicalPos
  1207.     def getLogicalPos(self):
  1208.         """Return the logical position of the splitter.
  1209.  
  1210.         \return Tuple (x0, y0) containing the logical position.
  1211.         """
  1212.         return self._splitter_x, self._splitter_y
  1213.  
  1214.     # setPixelPos
  1215.     def setPixelPos(self, x, y, interactive=False):
  1216.         """Set the pixel position of the splitter.
  1217.  
  1218.         This method doesn't set the pixel position directly as the
  1219.         position might break some size constraints. Instead the
  1220.         position is converted to logical coordinates and setLogicalPos()
  1221.         is called instead (so the logical position is updated as well).
  1222.  
  1223.         \param x (\c int) X coordinate of the destination pixel position
  1224.         \param y (\c int) Y coordinate of the destination pixel position
  1225.         """
  1226.         x0, y0 = self._pixel2logical(x, y)
  1227.         self.setLogicalPos(x0, y0, interactive)
  1228.  
  1229.     # getPixelPos
  1230.     def getPixelPos(self):
  1231.         """Return the pixel position of the splitter.
  1232.  
  1233.         \param Tuple (x, y) containing the pixel position.
  1234.         """
  1235.         return self._x1, self._y1
  1236.         
  1237.  
  1238.     ######################################################################
  1239.     ## protected:
  1240.  
  1241.     def _logical2pixel(self, x0, y0):
  1242.         """Convert logical coordinates to pixel coordinates.
  1243.  
  1244.         \param x0 (\c float) Logical X coordinate
  1245.         \param y0 (\c float) Logical Y coordinate
  1246.         \return Tuple (x,y) with pixel coordinates (\c int, \c int)
  1247.         """
  1248.         xmax = self._x3 - self._splitter_width
  1249.         ymax = self._y3 - self._splitter_width
  1250.         return (int((1.0-x0)*self._x0 + x0*xmax),
  1251.                 int((1.0-y0)*self._y0 + y0*ymax))
  1252.  
  1253.     def _pixel2logical(self, x, y):
  1254.         """Convert pixel coordinates to logical coordinates.
  1255.  
  1256.         \param x (\c int) X coordinate
  1257.         \param y (\c int) Y coordinate
  1258.         \return Tuple (x0, y0) with logical coordinates (\c float, \c float)
  1259.         """
  1260.         w = self._x3 - self._x0 - self._splitter_width
  1261.         h = self._y3 - self._y0 - self._splitter_width
  1262.         
  1263.         if w==0:
  1264.             x0 = 0.0
  1265.         else:
  1266.             x0 = float(x-self._x0)/w
  1267.             
  1268.         if h==0:
  1269.             y0 = 0.0
  1270.         else:
  1271.             y0 = float(y-self._y0)/h
  1272.             
  1273.         return x0,y0
  1274.  
  1275.     def _update(self):
  1276.         """Update the children areas.
  1277.  
  1278.         \pre x0-x3/y0-y3 contain valid values
  1279.         """
  1280.         x0,y0 = self._x0, self._y0
  1281.         x1,y1 = self._x1, self._y1
  1282.         x2,y2 = self._x2, self._y2
  1283.         x3,y3 = self._x3, self._y3
  1284.         
  1285.         self._child00.setGeom(x0, y0, x1-x0, y1-y0)
  1286.         self._child01.setGeom(x2, y0, x3-x2, y1-y0)
  1287.         self._child10.setGeom(x0, y2, x1-x0, y3-y2)
  1288.         self._child11.setGeom(x2, y2, x3-x2, y3-y2)        
  1289.  
  1290.     def _calcSplitterCoords(self, x0, y0, coords, interactive):
  1291.         """Calculate new splitter positions.
  1292.       
  1293.         \param x0 (\c float) Logical X coordinate of the splitter
  1294.         \param y0 (\c float) Logical Y coordinate of the splitter
  1295.         \param coords (\c LayoutNodeCoords) Coordinates container
  1296.         \param interactive (\c bool) Interactive flag
  1297.         \pre x0,x3,y0,y3 are set in coords and x0 <= x3 and y0 <= y3
  1298.         """
  1299.         
  1300.         x,y = self._logical2pixel(x0,y0)
  1301.         coords.x1 = x
  1302.         coords.x2 = x + self._splitter_width
  1303.         coords.y1 = y
  1304.         coords.y2 = y + self._splitter_width
  1305.         self._enforceSplitterConstraints(coords)
  1306.         self._enforceSizeConstraints(coords, interactive)
  1307.         return coords
  1308.         
  1309.  
  1310.     def _enforceSizeConstraints(self, coords, interactive):
  1311.         """Change the splitter pixel coordinates to enforce size constraints.
  1312.  
  1313.         This method changes pixel coordinates of the splitter so that
  1314.         the size constraints on the children will be met. If there are
  1315.         no constraints then x0-x3/y0-y3 won't be modified.
  1316.         The logical coordinates remain unchanged in any case.
  1317.         """
  1318.  
  1319.         # Is the left column resizable?
  1320.         if (self._child00.isResizable(HORIZONTAL) and self._child10.isResizable(HORIZONTAL)):
  1321.             # Right column has priority:
  1322.             # Determine suggested width of the right column by asking
  1323.             # child01 and child11 in both orders and taking the maximum
  1324.             w = coords.x3-coords.x2
  1325.             w1,h = self._child01.applyConstraint(w,1, interactive)
  1326.             w1,h = self._child11.applyConstraint(w1,1, interactive)
  1327.             w2,h = self._child11.applyConstraint(w,1, interactive)
  1328.             w2,h = self._child01.applyConstraint(w2,1, interactive)
  1329.             w = max(w1,w2)
  1330.             # Set the final splitter x position 
  1331.             coords.x2 = coords.x3 - w
  1332.             coords.x1 = coords.x2 - self._splitter_width
  1333.             self._enforceSplitterConstraints(coords)
  1334.         else:
  1335.             # Left column has priority:
  1336.             # Determine suggested width of the left column by asking
  1337.             # child00 and child10 in both orders and taking the maximum
  1338.             w = coords.x1-coords.x0
  1339.             w1,h = self._child00.applyConstraint(w,1, interactive)
  1340.             w1,h = self._child10.applyConstraint(w1,1, interactive)
  1341.             w2,h = self._child10.applyConstraint(w,1, interactive)
  1342.             w2,h = self._child00.applyConstraint(w2,1, interactive)
  1343.             w = max(w1,w2)
  1344.             # Set the final splitter x position 
  1345.             coords.x1 = coords.x0 + w
  1346.             coords.x2 = coords.x1 + self._splitter_width
  1347.             self._enforceSplitterConstraints(coords)
  1348.  
  1349.         # Is the top row resizable?
  1350.         if (self._child00.isResizable(VERTICAL) and self._child01.isResizable(VERTICAL)):
  1351.             # Bottom row has priority:
  1352.             # Determine suggested width of the bottom row by asking
  1353.             # child10 and child11 in both orders and taking the maximum
  1354.             h = coords.y3-coords.y2
  1355.             w,h1 = self._child10.applyConstraint(1,h, interactive)
  1356.             w,h1 = self._child11.applyConstraint(1,h1, interactive)
  1357.             w,h2 = self._child11.applyConstraint(1,h, interactive)
  1358.             w,h2 = self._child10.applyConstraint(1,h2, interactive)
  1359.             h = max(h1,h2)
  1360.             # Set the final splitter y position 
  1361.             coords.y2 = coords.y3 - h
  1362.             coords.y1 = coords.y2 - self._splitter_width
  1363.             self._enforceSplitterConstraints(coords)
  1364.         else:
  1365.             # Top row has priority:
  1366.             # Determine suggested height of the top row by asking
  1367.             # child00 and child01 in both orders and taking the maximum
  1368.             h = coords.y1-coords.y0
  1369.             w,h1 = self._child00.applyConstraint(1,h, interactive)
  1370.             w,h1 = self._child01.applyConstraint(1,h1, interactive)
  1371.             w,h2 = self._child01.applyConstraint(1,h, interactive)
  1372.             w,h2 = self._child00.applyConstraint(1,h2, interactive)
  1373.             h = max(h1,h2)
  1374.             # Set the final splitter y position 
  1375.             coords.y1 = coords.y0 + h
  1376.             coords.y2 = coords.y1 + self._splitter_width
  1377.             self._enforceSplitterConstraints(coords)
  1378.         
  1379.  
  1380.     def _enforceSplitterConstraints(self, coords):
  1381.         """Enforces the pixel coordinate constraints on both of the splitter.
  1382.  
  1383.         Enforces the following constraints x0 <= x1 <= x2 <= x3 and
  1384.         y0 <= y1 <= y2 <= y3 provided that x0 <= x3 and y0 <= y3 already
  1385.         hold. The distance x2-x1 resp. y2-y1 is kept intact, unless x2<x1
  1386.         or y2<y1 in which case both values will be the same. If a splitter
  1387.         is not present in a particular direction, the corresponding splitter
  1388.         coordinates will be fixed at x3 resp. y3.
  1389.  
  1390.         The logical coordinate remains unchanged.
  1391.  
  1392.         This method is called whenever the splitter position changes.
  1393.  
  1394.         \pre x0 <= x3 and y0 <= y3
  1395.         """
  1396.  
  1397.         if self._splitter_type & HORIZONTAL == 0:
  1398.             coords.x1 = coords.x2 = coords.x3
  1399.         if self._splitter_type & VERTICAL == 0:
  1400.             coords.y1 = coords.y2 = coords.y3
  1401.             
  1402.         # d is the current width of the splitter
  1403.         d = coords.x2-coords.x1
  1404.         if d<0:
  1405.             coords.x2 = coords.x1
  1406.             d = 0
  1407.             
  1408.         if coords.x1<coords.x0:
  1409.             coords.x1 = coords.x0
  1410.             coords.x2 = coords.x1 + d
  1411.         if coords.x2>coords.x3:
  1412.             coords.x2 = coords.x3
  1413.             coords.x1 = max(coords.x0, coords.x2-d)
  1414.  
  1415.         # d is the current width of the splitter
  1416.         d = coords.y2-coords.y1
  1417.         if d<0:
  1418.             coords.y2 = coords.y1
  1419.             d = 0
  1420.             
  1421.         if coords.y1<coords.y0:
  1422.             coords.y1 = coords.y0
  1423.             coords.y2 = coords.y1 + d
  1424.         if coords.y2>coords.y3:
  1425.             coords.y2 = coords.y3
  1426.             coords.y1 = max(coords.y0, coords.y2-d)
  1427.  
  1428.     def _fillEmptyChildren(self):
  1429.         if self._child00==None:
  1430.             self.setChild(PanelNode(), 0)
  1431.         if self._child01==None:
  1432.             self.setChild(PanelNode(), 1)
  1433.         if self._child10==None:
  1434.             self.setChild(PanelNode(), 2)
  1435.         if self._child11==None:
  1436.             self.setChild(PanelNode(), 3)
  1437.  
  1438. # PanelNode
  1439. class PanelNode(ILayoutNode):
  1440.     """This class represents a leaf node in the layout tree.
  1441.  
  1442.     Each node in the layout tree must be a %PanelNode object. This object
  1443.     represents one panel is associated with a wx widget. The class
  1444.     has the following attributes:
  1445.  
  1446.     - \a constraint (\c SizeConstraint): A constraint on the size of the panel
  1447.     - \a widget (\c PanelWidget): The associated widget
  1448.     - [\a wx (\c wx \c widget): The associated wx widget]
  1449.     """
  1450.     
  1451.     def __init__(self, name="", activatable=False, constraint=None, widget=None):
  1452.         """Constructor.
  1453.  
  1454.         \param name (\c str) Node name
  1455.         \param activatable (\c bool) A flag that determines if the panel can
  1456.                be activated or not (only 3D views should be made activatable)
  1457.         \param constraint (\c SizeConstraint) Size constraint
  1458.         \param widget (\c PanelWidget) The panel widget or a sequence of widgets or None
  1459.         """
  1460.         ILayoutNode.__init__(self, name=name)
  1461.  
  1462.         # Position and size of the panel
  1463.         self._x0 = 0
  1464.         self._y0 = 0
  1465.         self._width = 0
  1466.         self._height = 0
  1467.  
  1468.         # Activatable flag
  1469.         self._activatable = activatable
  1470.  
  1471.         # Panel widgets. This is actually a stack where the last widget
  1472.         # is on top (all other widgets are hidden).
  1473.         self._widgets = []
  1474.  
  1475.         # Hide all widgets
  1476.         for w in self._widgets:
  1477.             w.hide()
  1478.  
  1479.         # Push the widgets...
  1480.         if widget!=None:
  1481.             try:
  1482.                 wlst = list(widget)
  1483.             except TypeError:
  1484.                 wlst = [widget]
  1485.             
  1486.             for w in wlst:
  1487.                 self.pushWidget(w)
  1488.  
  1489.         self.showConfigPanel()
  1490.  
  1491.         # Constraint
  1492.         if constraint==None:
  1493.             constraint = NoConstraint()
  1494.         self._constraint = constraint
  1495.  
  1496.     def __str__(self):
  1497.         if self.name!=None:
  1498.             name = '"%s"'%self.name
  1499.         else:
  1500.             name = "<None>"
  1501.         return 'PanelNode %s active:%d widgets:%s'%(name, self.isActive(), map(lambda x: str(x), self._widgets))
  1502.  
  1503.     def setGeom(self, x, y, width, height):
  1504. #        print "Panel '%s', setGeom(%d, %d, %d, %d)"%(self.name, x, y, width, height)
  1505.         self._x0 = x
  1506.         self._y0 = y
  1507.         self._width = width
  1508.         self._height = height
  1509.  
  1510.         x+=1
  1511.         y+=1
  1512.         width-=2
  1513.         height-=2
  1514.         self._constraint.setSize(width, height)
  1515.         if len(self._widgets)>0:
  1516.             self._widgets[-1].setGeom(x,y,width,height)
  1517.  
  1518.     def activate(self, root):
  1519.         # Store root (Panels object)
  1520.         self.setRoot(root)
  1521.  
  1522.         # Mark all widgets as activated
  1523.         for w in self._widgets:
  1524.             if not isinstance(w, PanelNodeDlg):
  1525.                 self._root.repository.insert(w)
  1526.             w.activate(root)
  1527.  
  1528.         # Set the position and size of the panel widget
  1529.         self.setGeom(self._x0, self._y0, self._width, self._height)
  1530.         # Connect and show the top widget
  1531.         if len(self._widgets)>0:
  1532.             w = self._widgets[-1]
  1533.             self._connectPanelEvents(w.wx)
  1534.             w.show()
  1535.  
  1536.     def deactivate(self):
  1537.         # Disconnect and hide the top widget
  1538.         if len(self._widgets)>0:
  1539.             w = self._widgets[-1]
  1540.             w.hide()
  1541.             self._disconnectPanelEvents(w.wx)
  1542.         
  1543.         # Mark all widgets as inactivated
  1544.         for w in self._widgets:
  1545.             w.deactivate()
  1546.  
  1547.         self.setRoot(None)
  1548.  
  1549.  
  1550.     def pushWidget(self, widget):
  1551.         """Add a widget on top of the stack.
  1552.  
  1553.         Adds a widget on top of the stack. If the layout is active the
  1554.         new widget will be displayed.
  1555.  
  1556.         \param widget (\c PanelWidget) The new panel widget
  1557.         """
  1558.         if widget.isUsedBy(self._layoutRoot()):
  1559.             raise LayoutError, 'The widget "%s" is already managed by this layout.'%widget.name
  1560.  
  1561.         # Hide and disconnect the previous top widget...
  1562.         if self.isActive():
  1563.             if len(self._widgets)>0:
  1564.                 w = self._widgets[-1]
  1565.                 w.hide()
  1566.                 self._disconnectPanelEvents(w.wx)
  1567.  
  1568.         # Store the widget...
  1569.         widget.acquire(self)
  1570.         self._widgets.append(widget)
  1571.  
  1572.         if self.isActive():
  1573.             if not isinstance(widget, PanelNodeDlg):
  1574.                 self._root.repository.insert(widget)
  1575.             # activate the widget...
  1576.             widget.activate(self._root)
  1577.             # Set the position and size of the panel widget
  1578.             self.setGeom(self._x0, self._y0, self._width, self._height)
  1579.             # Connect and show the top widget
  1580.             if len(self._widgets)>0:
  1581.                 w = self._widgets[-1]
  1582.                 self._connectPanelEvents(w.wx)
  1583.                 w.show()
  1584.  
  1585.     def popWidget(self):
  1586.         """Remove the top widget from the stack.
  1587.  
  1588.         \pre The stack must not be empty
  1589.         """
  1590.         w = self._widgets[-1]
  1591.         self._widgets.pop()
  1592.         w.release(self)
  1593.         # Disconnect and hide the top widget and show and connect the
  1594.         # previous one
  1595.         if self.isActive():
  1596.             w.deactivate()
  1597.             w.hide()
  1598.             self._disconnectPanelEvents(w.wx)
  1599.             if len(self._widgets)>0:
  1600.                 w2 = self._widgets[-1]
  1601.                 self._connectPanelEvents(w2.wx)
  1602.                 w2.show()
  1603.                 self.setGeom(self._x0, self._y0, self._width, self._height)
  1604.         
  1605.  
  1606.     def findNodeByName(self, name):
  1607.         if self._name==name:
  1608.             return self
  1609.         else:
  1610.             return None
  1611.  
  1612.     def showConfigPanel(self):
  1613.         # Is a config panel already on top?
  1614.         if len(self._widgets)>0 and isinstance(self._widgets[-1], PanelNodeDlg):
  1615.             return
  1616.         # Remove any config panel that's somewhere inside the stack
  1617.         self._widgets = filter(lambda w: not isinstance(w, PanelNodeDlg), self._widgets)
  1618.         # Create a new config panel and push it onto the stack
  1619.         dlg = PanelNodeDlg(self)
  1620.         self.pushWidget(dlg)
  1621.  
  1622.     def hideConfigPanel(self):
  1623.         # If there's only the config panel present then leave it there
  1624.         if len(self._widgets)==1 and isinstance(self._widgets[0], PanelNodeDlg):
  1625.             return
  1626.         
  1627.         # Is a config panel on top?
  1628.         if len(self._widgets)>0 and isinstance(self._widgets[-1], PanelNodeDlg):
  1629.             # pop the panel
  1630.             self.popWidget()
  1631.         else:
  1632.             # Remove any config panel that's somewhere inside the stack
  1633.             self._widgets = filter(lambda w: not isinstance(w, PanelNodeDlg), self._widgets)
  1634.             
  1635.     def isResizable(self, direction):
  1636.         return self._constraint.isResizable(direction)
  1637.  
  1638.     def applyConstraint(self, width, height, interactive=False):
  1639.         w,h = self._constraint(width-2, height-2, interactive)
  1640.         w+=2
  1641.         h+=2
  1642.         return w,h
  1643.  
  1644.     def panelNodes(self):
  1645.         return [self]
  1646.  
  1647. #    def findPanel(self, x, y):
  1648. #        return (0, self)
  1649.  
  1650.     def makeCurrent(self):
  1651.         """Make this panel the current panel.
  1652.         """
  1653.         if self._root!=None and self._activatable:
  1654.             self._root.onClickPanel(self)
  1655.  
  1656.     def split(self, x=None, y=None):
  1657.         """Split this panel in two or four new panels.
  1658.  
  1659.         \param x (\c float) Split coordinate in x direction (0-1) or None
  1660.         \param y (\c float) Split coordinate in y direction (0-1) or None
  1661.         \return Newly created layout node (\c LayoutNode) that contains
  1662.                the original panel and the new empty ones.
  1663.         """
  1664.         if x==None and y==None:
  1665.             return
  1666.         elif x!=None and y==None:
  1667.             stype = HORIZONTAL
  1668.             childs = (self, None)
  1669.         elif x==None and y!=None:
  1670.             stype = VERTICAL
  1671.             childs = (self, None)
  1672.         else:
  1673.             stype = HORIZONTAL | VERTICAL
  1674.             childs = (self, None, None, None)
  1675.  
  1676.         node = LayoutNode(splittertype=stype)
  1677.  
  1678.         parent = self._parent
  1679.         if parent!=None:
  1680.             idx = parent.childIndex(self)
  1681.             parent.removeChild(self)
  1682.             parent.setChild(node, idx)
  1683.         else:
  1684.             if self._root!=None:
  1685.                 self._root.layout = node
  1686.  
  1687.         node.setChildren(childs)
  1688.         if self._root!=None:
  1689.             self._root.updateLayout()
  1690.  
  1691.         return node
  1692.  
  1693.  
  1694.     ######################################################################
  1695.     ## protected:
  1696.  
  1697.     def _connectPanelEvents(self, wxwin):
  1698.         """Connect the given wx window to the Panels object.
  1699.  
  1700.         This method sets the connections so that the Panels object will
  1701.         get notified when the mouse enters or leaves this panel and
  1702.         when it gets clicked.
  1703.  
  1704.         \param wxwin (\c wx \c window) A wx.Window instance
  1705.         """
  1706. #        print "connect",wxwin
  1707.         wx.EVT_ENTER_WINDOW(wxwin, self._onEnter)
  1708.         wx.EVT_LEAVE_WINDOW(wxwin, self._onLeave)
  1709.         wx.EVT_LEFT_DOWN(wxwin, self._onClick)
  1710.         wx.EVT_MIDDLE_DOWN(wxwin, self._onClick)
  1711.         wx.EVT_RIGHT_DOWN(wxwin, self._onClick)
  1712.  
  1713.     def _disconnectPanelEvents(self, wxwin):
  1714.         """Disconnect the given wx window from the Panels object.
  1715.  
  1716.         \param wxwin (\c wx \c window) A wx.Window instance
  1717.         """
  1718. #        print "disconnect",wxwin
  1719.         wx.EVT_ENTER_WINDOW(wxwin, None)
  1720.         wx.EVT_LEAVE_WINDOW(wxwin, None)
  1721.         wx.EVT_LEFT_DOWN(wxwin, None)
  1722.         wx.EVT_MIDDLE_DOWN(wxwin, None)
  1723.         wx.EVT_RIGHT_DOWN(wxwin, None)
  1724.  
  1725.     def _onEnter(self, event):
  1726.         self._root.onEnterPanel(self)
  1727. #        print "onEnter", self.name, self._root
  1728.         event.Skip()
  1729.  
  1730.     def _onLeave(self, event):
  1731.         self._root.onLeavePanel(self)
  1732. #        print "onLeave", self.name, self._root
  1733.         event.Skip()
  1734.  
  1735.     def _onClick(self, event):
  1736.         if self._activatable:
  1737.             self._root.onClickPanel(self)
  1738.         event.Skip()
  1739.  
  1740.     # "widget" property...
  1741.     
  1742.     def _getWidget(self):
  1743.         """Return the top panel widget.
  1744.  
  1745.         This method is used for retrieving the \a widget property.
  1746.  
  1747.         \return PanelWidget object or None.
  1748.         """
  1749.         if len(self._widgets)==0:
  1750.             return None
  1751.         return self._widgets[-1]
  1752.  
  1753.     def _setWidget(self, widget):
  1754.         """Set the top panel widget.
  1755.  
  1756.         This method is used for setting the \a widget property.
  1757.  
  1758.         \param widget (\c PanelWidget) Widget
  1759.         """
  1760.         if len(self._widgets)>0:
  1761.             self.popWidget()
  1762.         self.pushWidget(widget)
  1763.  
  1764.     widget = property(_getWidget, _setWidget, None, "Associated panel widget")
  1765.  
  1766.     # "constraint" property...
  1767.     
  1768.     def _getConstraint(self):
  1769.         """Return the constraint object.
  1770.  
  1771.         This method is used for retrieving the \a constraint property.
  1772.  
  1773.         \return Constraint object (\c SizeConstraint).
  1774.         """
  1775.         return self._constraint
  1776.  
  1777.     def _setConstraint(self, constraint):
  1778.         """Set the constraint object.
  1779.  
  1780.         This method is used for setting the \a constraint property.
  1781.  
  1782.         \param constraint (\c SizeConstraint) Constraint object
  1783.         """
  1784.         self._constraint = constraint
  1785.  
  1786.     constraint = property(_getConstraint, _setConstraint, None, "Size constraint")
  1787.     
  1788.         
  1789.         
  1790. # NoConstraint
  1791. class NoConstraint:
  1792.     """An unconstrained size constraint class."""
  1793.     
  1794.     def __call__(self, width, height, interactive=False):
  1795.         return width, height
  1796.  
  1797.     def setSize(self, width, height):
  1798.         pass
  1799.  
  1800.     def isResizable(self, direction):
  1801.         return True
  1802.  
  1803.  
  1804. # SizeConstraint
  1805. class SizeConstraint:
  1806.     """This class is used to enforce size constraints on widgets/panels.
  1807.  
  1808.     This class computes acceptable width/height values that conform
  1809.     to the current constraints. The constraints can either keep the
  1810.     width or height constant or at a multiple of a base width/height.
  1811.     The constraint class is a callable object that can be called
  1812.     with an input width and height and it returns a new width/height
  1813.     tuple that conforms to the constraint and is close to the input
  1814.     values.
  1815.  
  1816.     Examples:
  1817.  
  1818.     \code
  1819.     # A constraint that keeps the width fixed at 270 pixel, the height is arbitrary
  1820.     >>> c = SizeConstraint(width=270, wfixed=True)
  1821.     >>> c(100,100)
  1822.     (270, 100)
  1823.  
  1824.     # Now keep the height at a multiple of 32
  1825.     >>> c = SizeConstraint(height=32)
  1826.     >>> c(100,100)
  1827.     (100, 96)    
  1828.     \endcode
  1829.     
  1830.     """
  1831.     
  1832.     def __init__(self, width=None, height=None):
  1833.         """Constructor.
  1834.  
  1835.         \param width  A tuple (width, step, resizable) describing the width constraint
  1836.         \param height  A tuple (height, step, resizable) describing the height constraint
  1837.         """
  1838.         if width==None:
  1839.             width = None, None, False
  1840.         if height==None:
  1841.             height = None, None, False
  1842.             
  1843.         self.width, self.wstep, self.wresizable = width
  1844.         self.height, self.hstep, self.hresizable = height
  1845.  
  1846.     def __call__(self, width, height, interactive=False):
  1847.         """Check if the constraint accepts the given size.
  1848.  
  1849.         This method has to check if the given width and height
  1850.         are acceptable for the constraint. If they are the method
  1851.         just has to return those values again, if they are not
  1852.         it has to return a suggested size that would be acceptable.
  1853.  
  1854.         \param width (\c int) A width value
  1855.         \param height (\c int) A height value
  1856.         \param interactive (\c bool) If True the constraints also accepts multiples of its width (default: False)
  1857.         \return Tuple (width, height) specifying an acceptable size that's
  1858.                as close as possible to the input size
  1859.         """
  1860.         if self.width!=None:
  1861.             w = self.width
  1862.         else:
  1863.             w = width
  1864.             
  1865.         if self.height!=None:
  1866.             h = self.height
  1867.         else:
  1868.             h = height
  1869.         
  1870.         if self.wresizable and interactive:
  1871.             dw = width-self.width
  1872.             if dw%self.wstep==0:
  1873.                 w = width
  1874.             else:
  1875.                 margin = int(0.15*self.wstep)
  1876.                 w = self.width + int((dw+margin)/self.wstep)*self.wstep
  1877.  
  1878.         if self.hresizable and interactive:
  1879.             dh = height-self.height
  1880.             if dh%self.hstep==0:
  1881.                 h = height
  1882.             else:
  1883.                 margin = int(0.15*self.hstep)
  1884.                 h = self.height + int((dh+margin)/self.hstep)*self.hstep
  1885.  
  1886.         return w,h
  1887.  
  1888.     def setSize(self, width, height):
  1889.         """Set a new width and height.
  1890.  
  1891.         The new size is not set directly but run through the
  1892.         constraint (in interactive mode). So the actual size that
  1893.         gets set is a size that's compliant to the constraint.
  1894.         """
  1895.         w, h = self(width, height, True)
  1896.         if self.width!=None:
  1897.             self.width = w
  1898.         if self.height!=None:
  1899.             self.height = h
  1900.  
  1901.     def isResizable(self, direction):
  1902.         """Check if the size of the constraint may be changed.
  1903.  
  1904.         \param direction (\int) A combination of HORIZONTAL and VERTICAL
  1905.         \return True if resizable.
  1906.         """
  1907.         res = False
  1908.         if direction&HORIZONTAL:
  1909.             res |= self.wresizable
  1910.         if direction&VERTICAL:
  1911.             res |= self.hresizable
  1912.     
  1913. ######################################################################
  1914.  
  1915.  
  1916. class PanelNodeDlg(PanelWidget):
  1917.     def __init__(self, panelnode):
  1918.         """Constructor.
  1919.  
  1920.         \param parentwin (\c MainWindow) Parent window (must have a "panels.wx" attribute)
  1921.         \param name (\c str) Widget name
  1922.         """
  1923.         PanelWidget.__init__(self)
  1924.         self.panelnode = panelnode
  1925.     
  1926.     def activate(self, root):
  1927.         PanelWidget.activate(self, root)
  1928.         self.wx = wx.Panel(root.wx, -1, style=wx.SIMPLE_BORDER)
  1929.         
  1930.         self.editlabel = wx.StaticText(self.wx, -1, "Name:")
  1931.         self.nameedit = wx.TextCtrl(self.wx, -1, value=self.panelnode.name)
  1932.  
  1933.         self.splitbox = wx.StaticBox(self.wx, -1, "Split")
  1934.         self.hsplitbtn = wx.BitmapButton(self.wx, -1, panelicons.getHSplitBitmap())
  1935.         self.hsplitbtn.SetToolTip(wx.ToolTip("Split this panel horizontally"))
  1936.         self.vsplitbtn = wx.BitmapButton(self.wx, -1, panelicons.getVSplitBitmap())
  1937.         self.vsplitbtn.SetToolTip(wx.ToolTip("Split this panel vertically"))
  1938.         self.hvsplitbtn = wx.BitmapButton(self.wx, -1, panelicons.getHVSplitBitmap())
  1939.         self.hvsplitbtn.SetToolTip(wx.ToolTip("Split this panel in both directions"))
  1940.  
  1941.         self.toplayout = wx.BoxSizer(wx.VERTICAL)
  1942.  
  1943.         self.s1 = wx.BoxSizer(wx.HORIZONTAL)
  1944.         self.s1.Add(self.editlabel, 0, wx.ALL | wx.ALIGN_CENTRE, 4)
  1945.         self.s1.Add(self.nameedit, 1, wx.ALL | wx.ALIGN_CENTRE, 4)
  1946.  
  1947.         self.s2 = wx.StaticBoxSizer(self.splitbox, wx.HORIZONTAL)
  1948.         self.s2.Add(self.hsplitbtn, 0, wx.ALL, 2)
  1949.         self.s2.Add(self.vsplitbtn, 0, wx.ALL, 2)
  1950.         self.s2.Add(self.hvsplitbtn, 0, wx.ALL, 2)
  1951.  
  1952.         self.toplayout.Add((0,1), 1, 0,0)
  1953.         self.toplayout.Add(self.s1, 0, wx.ALIGN_CENTRE, 0)
  1954.         self.toplayout.Add(self.s2, 0, wx.ALIGN_CENTRE, 0)
  1955.         self.toplayout.Add((0,1), 1, 0,0)
  1956.  
  1957.         self.wx.SetSizer(self.toplayout)
  1958.  
  1959.         wx.EVT_TEXT_ENTER(self.wx, self.nameedit.GetId(), self.onNameEntered)
  1960.         wx.EVT_KILL_FOCUS(self.nameedit, self.onNameEntered)
  1961.         wx.EVT_BUTTON(self.wx, self.hsplitbtn.GetId(), self.onHSplit)
  1962.         wx.EVT_BUTTON(self.wx, self.vsplitbtn.GetId(), self.onVSplit)
  1963.         wx.EVT_BUTTON(self.wx, self.hvsplitbtn.GetId(), self.onHVSplit)
  1964.  
  1965.     def onNameEntered(self, event):
  1966.         newname = self.nameedit.GetValue()
  1967.         try:
  1968.             self.panelnode.name = newname
  1969.         except DuplicateNames:
  1970. #            dlg = wx.MessageDialog(self.wx,
  1971. #                                   'There is already a layout node called "%s".\nPlease choose another name.'%newname, "Duplicate names", wx.OK)
  1972. #            dlg.ShowModal()
  1973.             newname = self.panelnode._layoutRoot().makeNameUnique(newname)
  1974.             self.panelnode.name = newname
  1975.             self.nameedit.SetValue(newname)
  1976.             self.nameedit.SetSelection(-1,-1)
  1977.  #           self.nameedit.SetFocus()
  1978.         
  1979.     def onHSplit(self, event):
  1980.         self.panelnode.split(x=0.5)
  1981.  
  1982.     def onVSplit(self, event):
  1983.         self.panelnode.split(y=0.5)
  1984.  
  1985.     def onHVSplit(self, event):
  1986.         self.panelnode.split(x=0.5, y=0.5)
  1987.  
  1988.  
  1989. ######################################################################
  1990.  
  1991. class WidgetDlg(wx.Frame):
  1992.     def __init__(self, parent, id):
  1993.         wx.Frame.__init__(self, parent, id, "Panel widgets", style=wx.DEFAULT_FRAME_STYLE | wx.FRAME_FLOAT_ON_PARENT)
  1994.  
  1995.         self.lst = wx.ListCtrl(self, -1, style=wx.LC_REPORT | wx.LC_SINGLE_SEL | wx.LC_HRULES | wx.LC_VRULES)
  1996.         self.lst.InsertColumn(0, "Panel")
  1997.         self.lst.InsertColumn(1, "Name")
  1998.         self.lst.InsertColumn(2, "Ref.count")
  1999.         self.lst.InsertStringItem(0, "Shell")
  2000.         self.lst.SetStringItem(0,1,"shell")
  2001.         self.lst.SetStringItem(0,2,"1")
  2002.         self.lst.InsertStringItem(1, "Shader Editor")
  2003.         self.lst.SetStringItem(1,1,"shadereditor")
  2004.         self.lst.SetStringItem(1,2,"1")
  2005.         self.lst.InsertStringItem(2, "3D Viewport")
  2006.         self.lst.SetStringItem(2,1,"front")
  2007.         self.lst.SetStringItem(2,2,"1")
  2008.         self.lst.InsertStringItem(3, "3D Viewport")
  2009.         self.lst.SetStringItem(3,1,"perspective")
  2010.         self.lst.SetStringItem(3,2,"1")
  2011.  
  2012. def widgetDlg():
  2013.     w = WidgetDlg(getApp().window.wx, -1)
  2014.     w.Show()
  2015.  
  2016. cgkit.cmds.widgetDlg = widgetDlg
  2017.  
  2018. ######################################################################
  2019.  
  2020. # createPanelWidget
  2021. def createPanelWidget(name, modname=None):
  2022.     """Looks for a panel widget plugin and creates an instance of it.
  2023.  
  2024.     \param name (\c str) Widget name
  2025.     \param modname (\c str) Module name or None (which matches every module)
  2026.     """
  2027.  
  2028.     for odesc in pluginmanager.iterProtoObjects(PanelWidget):
  2029.         if odesc.name==name and (modname==None or odesc.plugindesc.modulename==modname):
  2030.             ClassName = odesc.object
  2031.             widget = ClassName()
  2032.             return widget
  2033.         
  2034.     return None
  2035.  
  2036. cgkit.cmds.createPanelWidget = createPanelWidget
  2037.  
  2038. ######################################################################
  2039.  
  2040. if __name__=="__main__":
  2041.  
  2042.     c = SizeConstraint(5,5, wfixed=True)
  2043.     print c(172,22)
  2044.