# ***** BEGIN LICENSE BLOCK *****
# Version: MPL 1.1/GPL 2.0/LGPL 2.1
#
# The contents of this file are subject to the Mozilla Public License Version
# 1.1 (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
# http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
# for the specific language governing rights and limitations under the
# License.
#
# The Original Code is the Python Computer Graphics Kit.
#
# The Initial Developer of the Original Code is Matthias Baas.
# Portions created by the Initial Developer are Copyright (C) 2004
# the Initial Developer. All Rights Reserved.
#
# Contributor(s):
#
# Alternatively, the contents of this file may be used under the terms of
# either the GNU General Public License Version 2 or later (the "GPL"), or
# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
# in which case the provisions of the GPL or the LGPL are applicable instead
# of those above. If you wish to allow use of your version of this file only
# under the terms of either the GPL or the LGPL, and not to allow others to
# use your version of this file under the terms of the MPL, indicate your
# decision by deleting the provisions above and replace them with the notice
# and other provisions required by the GPL or the LGPL. If you do not delete
# the provisions above, a recipient may use your version of this file under
# the terms of any one of the MPL, the GPL or the LGPL.
#
# ***** END LICENSE BLOCK *****
# $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)
|