]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/lib/ogl/composit.py
Added a Python port of the OGL library, deprecated the C++ wrapped version.
[wxWidgets.git] / wxPython / wx / lib / ogl / composit.py
diff --git a/wxPython/wx/lib/ogl/composit.py b/wxPython/wx/lib/ogl/composit.py
new file mode 100644 (file)
index 0000000..8d53b9d
--- /dev/null
@@ -0,0 +1,1410 @@
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name:         composit.py
+# Purpose:      Composite class
+#
+# Author:       Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created:      20040508
+# RCS-ID:       
+# Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# Licence:      wxWindows license
+#----------------------------------------------------------------------------
+
+from __future__ import division
+
+import sys
+import wx
+
+from basic import RectangleShape, Shape, ControlPoint
+from oglmisc import *
+
+KEY_SHIFT, KEY_CTRL = 1, 2
+
+objectStartX = 0.0
+objectStartY = 0.0
+
+CONSTRAINT_CENTRED_VERTICALLY   = 1
+CONSTRAINT_CENTRED_HORIZONTALLY = 2
+CONSTRAINT_CENTRED_BOTH         = 3
+CONSTRAINT_LEFT_OF              = 4
+CONSTRAINT_RIGHT_OF             = 5
+CONSTRAINT_ABOVE                = 6
+CONSTRAINT_BELOW                = 7
+CONSTRAINT_ALIGNED_TOP          = 8
+CONSTRAINT_ALIGNED_BOTTOM       = 9
+CONSTRAINT_ALIGNED_LEFT         = 10
+CONSTRAINT_ALIGNED_RIGHT        = 11
+
+# Like aligned, but with the objects centred on the respective edge
+# of the reference object.
+CONSTRAINT_MIDALIGNED_TOP       = 12
+CONSTRAINT_MIDALIGNED_BOTTOM    = 13
+CONSTRAINT_MIDALIGNED_LEFT      = 14
+CONSTRAINT_MIDALIGNED_RIGHT     = 15
+
+
+
+class ConstraintType(object):
+    def __init__(self, theType, theName, thePhrase):
+        self._type = theType
+        self._name = theName
+        self._phrase = thePhrase
+
+
+
+ConstraintTypes = [
+    [CONSTRAINT_CENTRED_VERTICALLY,
+        ConstraintType(CONSTRAINT_CENTRED_VERTICALLY, "Centre vertically", "centred vertically w.r.t.")],
+
+    [CONSTRAINT_CENTRED_HORIZONTALLY,
+        ConstraintType(CONSTRAINT_CENTRED_HORIZONTALLY, "Centre horizontally", "centred horizontally w.r.t.")],
+
+    [CONSTRAINT_CENTRED_BOTH,
+        ConstraintType(CONSTRAINT_CENTRED_BOTH, "Centre", "centred w.r.t.")],
+
+    [CONSTRAINT_LEFT_OF,
+        ConstraintType(CONSTRAINT_LEFT_OF, "Left of", "left of")],
+
+    [CONSTRAINT_RIGHT_OF,
+        ConstraintType(CONSTRAINT_RIGHT_OF, "Right of", "right of")],
+
+    [CONSTRAINT_ABOVE,
+        ConstraintType(CONSTRAINT_ABOVE, "Above", "above")],
+
+    [CONSTRAINT_BELOW,
+        ConstraintType(CONSTRAINT_BELOW, "Below", "below")],
+
+    # Alignment
+    [CONSTRAINT_ALIGNED_TOP,
+        ConstraintType(CONSTRAINT_ALIGNED_TOP, "Top-aligned", "aligned to the top of")],
+
+    [CONSTRAINT_ALIGNED_BOTTOM,
+        ConstraintType(CONSTRAINT_ALIGNED_BOTTOM, "Bottom-aligned", "aligned to the bottom of")],
+
+    [CONSTRAINT_ALIGNED_LEFT,
+        ConstraintType(CONSTRAINT_ALIGNED_LEFT, "Left-aligned", "aligned to the left of")],
+
+    [CONSTRAINT_ALIGNED_RIGHT,
+        ConstraintType(CONSTRAINT_ALIGNED_RIGHT, "Right-aligned", "aligned to the right of")],
+
+    # Mid-alignment
+    [CONSTRAINT_MIDALIGNED_TOP,
+        ConstraintType(CONSTRAINT_MIDALIGNED_TOP, "Top-midaligned", "centred on the top of")],
+
+    [CONSTRAINT_MIDALIGNED_BOTTOM,
+        ConstraintType(CONSTRAINT_MIDALIGNED_BOTTOM, "Bottom-midaligned", "centred on the bottom of")],
+
+    [CONSTRAINT_MIDALIGNED_LEFT,
+        ConstraintType(CONSTRAINT_MIDALIGNED_LEFT, "Left-midaligned", "centred on the left of")],
+
+    [CONSTRAINT_MIDALIGNED_RIGHT,
+        ConstraintType(CONSTRAINT_MIDALIGNED_RIGHT, "Right-midaligned", "centred on the right of")]
+    ]
+
+
+
+
+class Constraint(object):
+    """A Constraint object helps specify how child shapes are laid out with
+    respect to siblings and parents.
+
+    Derived from:
+      wxObject
+    """
+    def __init__(self, type, constraining, constrained):
+        self._xSpacing = 0.0
+        self._ySpacing = 0.0
+
+        self._constraintType = type
+        self._constraintingObject = constraining
+
+        self._constraintId = 0
+        self._constraintName="noname"
+
+        self._constrainedObjects = constrained[:]
+
+    def __repr__(self):
+        return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__)
+
+    def SetSpacing(self, x, y):
+        """Sets the horizontal and vertical spacing for the constraint."""
+        self._xSpacing = x
+        self._ySpacing = y
+        
+    def Equals(self, a, b):
+        """Return TRUE if x and y are approximately equal (for the purposes
+        of evaluating the constraint).
+        """
+        marg = 0.5
+
+        return b <= a + marg and b >= a-marg
+
+    def Evaluate(self):
+        """Evaluate this constraint and return TRUE if anything changed."""
+        maxWidth, maxHeight = self._constraintingObject.GetBoundingBoxMax()
+        minWidth, minHeight = self._constraintingObject.GetBoundingBoxMin()
+        x = self._constraintingObject.GetX()
+        y = self._constraintingObject.GetY()
+
+        dc = wx.ClientDC(self._constraintingObject.GetCanvas())
+        self._constraintingObject.GetCanvas().PrepareDC(dc)
+
+        if self._constraintType == CONSTRAINT_CENTRED_VERTICALLY:
+            n = len(self._constrainedObjects)
+            totalObjectHeight = 0.0
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                totalObjectHeight += height2
+
+            # Check if within the constraining object...
+            if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight:
+                spacingY = (minHeight-totalObjectHeight) / (n + 1)
+                startY = y-minHeight / 2
+            else: # Otherwise, use default spacing
+                spacingY = self._ySpacing
+                startY = y-(totalObjectHeight + (n + 1) * spacingY) / 2
+
+            # Now position the objects
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                startY += spacingY + height2 / 2
+                if not self.Equals(startY, constrainedObject.GetY()):
+                    constrainedObject.Move(dc, constrainedObject.GetX(), startY, False)
+                    changed = True
+                startY += height2 / 2
+            return changed
+        elif self._constraintType == CONSTRAINT_CENTRED_HORIZONTALLY:
+            n = len(self._constrainedObjects)
+            totalObjectWidth = 0.0
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                totalObjectWidth += width2
+
+            # Check if within the constraining object...
+            if totalObjectWidth + (n + 1) * self._xSpacing<minWidth:
+                spacingX = (minWidth-totalObjectWidth) / (n + 1)
+                startX = x-minWidth / 2
+            else: # Otherwise, use default spacing
+                spacingX = self._xSpacing
+                startX = x-(totalObjectWidth + (n + 1) * spacingX) / 2
+
+            # Now position the objects
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                startX += spacingX + width2 / 2
+                if not self.Equals(startX, constrainedObject.GetX()):
+                    constrainedObject.Move(dc, startX, constrainedObject.GetY(), False)
+                    changed = True
+                startX += width2 / 2
+            return changed
+        elif self._constraintType == CONSTRAINT_CENTRED_BOTH:
+            n = len(self._constrainedObjects)
+            totalObjectWidth = 0.0
+            totalObjectHeight = 0.0
+
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                totalObjectWidth += width2
+                totalObjectHeight += height2
+
+            # Check if within the constraining object...
+            if totalObjectHeight + (n + 1) * self._xSpacing <= minWidth:
+                spacingX = (minWidth-totalObjectWidth) / (n + 1)
+                startX = x-minWidth / 2
+            else: # Otherwise, use default spacing
+                spacingX = self._xSpacing
+                startX = x-(totalObjectWidth + (n + 1) * spacingX) / 2
+
+            # Check if within the constraining object...
+            if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight:
+                spacingY = (minHeight-totalObjectHeight) / (n + 1)
+                startY = y-minHeight / 2
+            else: # Otherwise, use default spacing
+                spacingY = self._ySpacing
+                startY = y-(totalObjectHeight + (n + 1) * spacingY) / 2
+
+            # Now position the objects
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                startX += spacingX + width2 / 2
+                startY += spacingY + height2 / 2
+
+                if not self.Equals(startX, constrainedObject.GetX()) or not self.Equals(startY, constrainedObject.GetY()):
+                    constrainedObject.Move(dc, startX, startY, False)
+                    changed = True
+
+                startX += width2 / 2
+                startY += height2 / 2
+            return changed
+        elif self._constraintType == CONSTRAINT_LEFT_OF:
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+
+                x3 = x-minWidth / 2-width2 / 2-self._xSpacing
+                if not self.Equals(x3, constrainedObject.GetX()):
+                    changed = True
+                    constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+            return changed
+        elif self._constraintType == CONSTRAINT_RIGHT_OF:
+            changed = False
+
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                x3 = x + minWidth / 2 + width2 / 2 + self._xSpacing
+                if not self.Equals(x3, constrainedObject.GetX()):
+                    constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+                    changed = True
+            return changed
+        elif self._constraintType == CONSTRAINT_ABOVE:
+            changed = False
+
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+
+                y3 = y-minHeight / 2-height2 / 2-self._ySpacing
+                if not self.Equals(y3, constrainedObject.GetY()):
+                    changed = True
+                    constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+            return changed
+        elif self._constraintType == CONSTRAINT_BELOW:
+            changed = False
+
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+
+                y3 = y + minHeight / 2 + height2 / 2 + self._ySpacing
+                if not self.Equals(y3, constrainedObject.GetY()):
+                    changed = True
+                    constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+            return changed
+        elif self._constraintType == CONSTRAINT_ALIGNED_LEFT:
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                x3 = x-minWidth / 2 + width2 / 2 + self._xSpacing
+                if not self.Equals(x3, constrainedObject.GetX()):
+                    changed = True
+                    constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+            return changed
+        elif self._constraintType == CONSTRAINT_ALIGNED_RIGHT:
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                x3 = x + minWidth / 2-width2 / 2-self._xSpacing
+                if not self.Equals(x3, constrainedObject.GetX()):
+                    changed = True
+                    constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+            return changed
+        elif self._constraintType == CONSTRAINT_ALIGNED_TOP:
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                y3 = y-minHeight / 2 + height2 / 2 + self._ySpacing
+                if not self.Equals(y3, constrainedObject.GetY()):
+                    changed = True
+                    constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+            return changed
+        elif self._constraintType == CONSTRAINT_ALIGNED_BOTTOM:
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                width2, height2 = constrainedObject.GetBoundingBoxMax()
+                y3 = y + minHeight / 2-height2 / 2-self._ySpacing
+                if not self.Equals(y3, constrainedObject.GetY()):
+                    changed = True
+                    constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+            return changed
+        elif self._constraintType == CONSTRAINT_MIDALIGNED_LEFT:
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                x3 = x-minWidth / 2
+                if not self.Equals(x3, constrainedObject.GetX()):
+                    changed = True
+                    constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+            return changed
+        elif self._constraintType == CONSTRAINT_MIDALIGNED_RIGHT:
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                x3 = x + minWidth / 2
+                if not self.Equals(x3, constrainedObject.GetX()):
+                    changed = True
+                    constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+            return changed
+        elif self._constraintType == CONSTRAINT_MIDALIGNED_TOP:
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                y3 = y-minHeight / 2
+                if not self.Equals(y3, constrainedObject.GetY()):
+                    changed = True
+                    constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+            return changed
+        elif self._constraintType == CONSTRAINT_MIDALIGNED_BOTTOM:
+            changed = False
+            for constrainedObject in self._constrainedObjects:
+                y3 = y + minHeight / 2
+                if not self.Equals(y3, constrainedObject.GetY()):
+                    changed = True
+                    constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+            return changed
+        
+        return False
+    
+
+
+class CompositeShape(RectangleShape):
+    """This is an object with a list of child objects, and a list of size
+    and positioning constraints between the children.
+
+    Derived from:
+      wxRectangleShape
+    """
+    def __init__(self):
+        RectangleShape.__init__(self, 100.0, 100.0)
+
+        self._oldX = self._xpos
+        self._oldY = self._ypos
+
+        self._constraints = [] 
+        self._divisions = [] # In case it's a container
+        
+    def OnDraw(self, dc):
+        x1 = self._xpos-self._width / 2
+        y1 = self._ypos-self._height / 2
+
+        if self._shadowMode != SHADOW_NONE:
+            if self._shadowBrush:
+                dc.SetBrush(self._shadowBrush)
+            dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT))
+
+            if self._cornerRadius:
+                dc.DrawRoundedRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height, self._cornerRadius)
+            else:
+                dc.DrawRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height)
+
+        # For debug purposes /pi
+        #dc.DrawRectangle(x1, y1, self._width, self._height)
+        
+    def OnDrawContents(self, dc):
+        for object in self._children:
+            object.Draw(dc)
+            object.DrawLinks(dc)
+
+        Shape.OnDrawContents(self, dc)
+
+    def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
+        diffX = x-old_x
+        diffY = y-old_y
+
+        for object in self._children:
+            object.Erase(dc)
+            object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display)
+
+        return True
+
+    def OnErase(self, dc):
+        RectangleShape.OnErase(self, dc)
+        for object in self._children:
+            object.Erase(dc)
+
+    def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+        xx, yy = self._canvas.Snap(x, y)
+        offsetX = xx-objectStartX
+        offsetY = yy-objectStartY
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        dc.SetLogicalFunction(OGLRBLF)
+        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
+        dc.SetPen(dottedPen)
+        dc.SetBrush(wx.TRANSPARENT_BRUSH)
+
+        self.GetEventHandler().OnDrawOutline(dc, self.GetX() + offsetX, self.GetY() + offsetY, self.GetWidth(), self.GetHeight())
+
+    def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+        global objectStartX, objectStartY
+
+        objectStartX = x
+        objectStartY = y
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        #self.Erase(dc)
+        
+        dc.SetLogicalFunction(OGLRBLF)
+        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
+        dc.SetPen(dottedPen)
+        dc.SetBrush(wx.TRANSPARENT_BRUSH)
+        self._canvas.CaptureMouse()
+
+        xx, yy = self._canvas.Snap(x, y)
+        offsetX = xx-objectStartX
+        offsetY = yy-objectStartY
+
+        self.GetEventHandler().OnDrawOutline(dc, self.GetX() + offsetX, self.GetY() + offsetY, self.GetWidth(), self.GetHeight())
+
+    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        if self._canvas.HasCapture():
+            self._canvas.ReleaseMouse()
+
+        if not self._draggable:
+            if self._parent:
+                self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, 0)
+            return
+            
+        self.Erase(dc)
+        
+        dc.SetLogicalFunction(wx.COPY)
+        
+        xx, yy = self._canvas.Snap(x, y)
+        offsetX = xx-objectStartX
+        offsetY = yy-objectStartY
+
+        self.Move(dc, self.GetX() + offsetX, self.GetY() + offsetY)
+
+        if self._canvas and not self._canvas.GetQuickEditMode():
+            self._canvas.Redraw(dc)
+
+    def OnRightClick(self, x, y, keys = 0, attachment = 0):
+        # If we get a ctrl-right click, this means send the message to
+        # the division, so we can invoke a user interface for dealing
+        # with regions.
+        if keys & KEY_CTRL:
+            for division in self._divisions:
+                hit = division.HitTest(x, y)
+                if hit:
+                    division.GetEventHandler().OnRightClick(x, y, keys, hit[0])
+                    break
+
+    def SetSize(self, w, h, recursive = True):
+        self.SetAttachmentSize(w, h)
+
+        xScale = w / max(1, self.GetWidth())
+        yScale = h / max(1, self.GetHeight())
+
+        self._width = w
+        self._height = h
+
+        if not recursive:
+            return
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        for object in self._children:
+            # Scale the position first
+            newX = (object.GetX()-self.GetX()) * xScale + self.GetX()
+            newY = (object.GetY()-self.GetY()) * yScale + self.GetY()
+            object.Show(False)
+            object.Move(dc, newX, newY)
+            object.Show(True)
+
+            # Now set the scaled size
+            xbound, ybound = object.GetBoundingBoxMax()
+            if not object.GetFixedWidth():
+                xbound *= xScale
+            if not object.GetFixedHeight():
+                ybound *= yScale
+            object.SetSize(xbound, ybound)
+
+        self.SetDefaultRegionSize()
+
+    def AddChild(self, child, addAfter = None):
+        """Adds a child shape to the composite.
+
+        If addAfter is not None, the shape will be added after this shape.
+        """
+        self._children.append(child)
+        child.SetParent(self)
+        if self._canvas:
+            # Ensure we add at the right position
+            if addAfter:
+                child.RemoveFromCanvas(self._canvas)
+            child.AddToCanvas(self._canvas, addAfter)
+
+    def RemoveChild(self, child):
+        """Removes the child from the composite and any constraint
+        relationships, but does not delete the child.
+        """
+        self._children.remove(child)
+        self._divisions.remove(child)
+        self.RemoveChildFromConstraints(child)
+        child.SetParent(None)
+
+    def DeleteConstraintsInvolvingChild(self, child):
+        """This function deletes constraints which mention the given child.
+
+        Used when deleting a child from the composite.
+        """
+        for constraint in self._constraints:
+            if constraint._constrainingObject == child or child in constraint._constrainedObjects:
+                self._constraints.remove(constraint)
+
+    def RemoveChildFromConstraints(self, child):
+        for constraint in self._constraints:
+            if child in constraint._constrainedObjects:
+                constraint._constrainedObjects.remove(child)
+            if constraint._constrainingObject == child:
+                constraint._constrainingObject = None
+
+            # Delete the constraint if no participants left
+            if not constraint._constrainingObject:
+                self._constraints.remove(constraint)
+
+    def AddConstraint(self, constraint):
+        """Adds a constraint to the composite."""
+        self._constraints.append(constraint)
+        if constraint._constraintId == 0:
+            constraint._constraintId = wx.NewId()
+        return constraint
+
+    def AddSimpleConstraint(self, type, constraining, constrained):
+        """Add a constraint of the given type to the composite.
+
+        constraining is the shape doing the constraining
+        constrained is a list of shapes being constrained
+        """
+        constraint = Constraint(type, constraining, constrained)
+        if constraint._constraintId == 0:
+            constraint._constraintId = wx.NewId()
+        self._constraints.append(constraint)
+        return constraint
+
+    def FindConstraint(self, cId):
+        """Finds the constraint with the given id.
+
+        Returns a tuple of the constraint and the actual composite the
+        constraint was in, in case that composite was a descendant of
+        this composit.
+
+        Returns None if not found.
+        """
+        for constraint in self._constraints:
+            if constraint._constraintId == cId:
+                return constraint, self
+
+        # If not found, try children
+        for child in self._children:
+            if isinstance(child, CompositeShape):
+                constraint = child.FindConstraint(cId)
+                if constraint:
+                    return constraint[0], child
+
+        return None
+
+    def DeleteConstraint(self, constraint):
+        """Deletes constraint from composite."""
+        self._constraints.remove(constraint)
+
+    def CalculateSize(self):
+        """Calculates the size and position of the composite based on
+        child sizes and positions.
+        """
+        maxX=-999999.9
+        maxY=-999999.9
+        minX = 999999.9
+        minY = 999999.9
+
+        for child in self._children:
+            # Recalculate size of composite objects because may not conform
+            # to size it was set to - depends on the children.
+            if isinstance(child, CompositeShape):
+                child.CalculateSize()
+
+            w, h = child.GetBoundingBoxMax()
+            if child.GetX() + w / 2>maxX:
+                maxX = child.GetX() + w / 2
+            if child.GetX()-w / 2<minX:
+                minX = child.GetX()-w / 2
+            if child.GetY() + h / 2>maxY:
+                maxY = child.GetY() + h / 2
+            if child.GetY()-h / 2<minY:
+                minY = child.GetY()-h / 2
+
+        self._width = maxX-minX
+        self._height = maxY-minY
+        self._xpos = self._width / 2 + minX
+        self._ypos = self._height / 2 + minY
+
+    def Recompute(self):
+        """Recomputes any constraints associated with the object. If FALSE is
+        returned, the constraints could not be satisfied (there was an
+        inconsistency).
+        """
+        noIterations = 0
+        changed = True
+        while changed and noIterations<500:
+            changed = self.Constrain()
+            noIterations += 1
+
+        return not changed
+
+    def Constrain(self):
+        self.CalculateSize()
+
+        changed = False
+        for child in self._children:
+            if isinstance(child, CompositeShape) and child.Constrain():
+                changed = True
+
+        for constraint in self._constraints:
+            if constraint.Evaluate():
+                changed = True
+
+        return changed
+
+    def MakeContainer(self):
+        """Makes this composite into a container by creating one child
+        DivisionShape.
+        """
+        division = self.OnCreateDivision()
+        self._divisions.append(division)
+        self.AddChild(division)
+
+        division.SetSize(self._width, self._height)
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+        
+        division.Move(dc, self.GetX(), self.GetY())
+        self.Recompute()
+        division.Show(True)
+
+    def OnCreateDivision(self):
+        return DivisionShape()
+
+    def FindContainerImage(self):
+        """Finds the image used to visualize a container. This is any child of
+        the composite that is not in the divisions list.
+        """
+        for child in self._children:
+            if child in self._divisions:
+                return child
+
+        return None
+
+    def ContainsDivision(self, division):
+        """Returns TRUE if division is a descendant of this container."""
+        if division in self._divisions:
+            return True
+
+        for child in self._children:
+            if isinstance(child, CompositeShape):
+                return child.ContainsDivision(division)
+
+        return False
+
+    def GetDivisions(self):
+        """Return the list of divisions."""
+        return self._divisions
+
+    def GetConstraints(self):
+        """Return the list of constraints."""
+        return self._constraints
+
+
+#  A division object is a composite with special properties,
+#  to be used for containment. It's a subdivision of a container.
+#  A containing node image consists of a composite with a main child shape
+#  such as rounded rectangle, plus a list of division objects.
+#  It needs to be a composite because a division contains pieces
+#  of diagram.
+#  NOTE a container has at least one wxDivisionShape for consistency.
+#  This can be subdivided, so it turns into two objects, then each of
+#  these can be subdivided, etc.
+
+DIVISION_SIDE_NONE      =0
+DIVISION_SIDE_LEFT      =1
+DIVISION_SIDE_TOP       =2
+DIVISION_SIDE_RIGHT     =3
+DIVISION_SIDE_BOTTOM    =4
+
+originalX = 0.0
+originalY = 0.0
+originalW = 0.0
+originalH = 0.0
+
+
+
+class DivisionControlPoint(ControlPoint):
+    def __init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type):
+        ControlPoint.__init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type)
+        self.SetEraseObject(False)
+
+    # Implement resizing of canvas object
+    def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+        ControlPoint.OnDragLeft(self, draw, x, y, keys, attachment)
+
+    def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+        global originalX, originalY, originalW, originalH
+
+        originalX = self._shape.GetX()
+        originalY = self._shape.GetY()
+        originalW = self._shape.GetWidth()
+        originalH = self._shape.GetHeight()
+
+        ControlPoint.OnBeginDragLeft(self, x, y, keys, attachment)
+
+    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+        ControlPoint.OnEndDragLeft(self, x, y, keys, attachment)
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        division = self._shape
+        divisionParent = division.GetParent()
+
+        # Need to check it's within the bounds of the parent composite
+        x1 = divisionParent.GetX()-divisionParent.GetWidth() / 2
+        y1 = divisionParent.GetY()-divisionParent.GetHeight() / 2
+        x2 = divisionParent.GetX() + divisionParent.GetWidth() / 2
+        y2 = divisionParent.GetY() + divisionParent.GetHeight() / 2
+
+        # Need to check it has not made the division zero or negative
+        # width / height
+        dx1 = division.GetX()-division.GetWidth() / 2
+        dy1 = division.GetY()-division.GetHeight() / 2
+        dx2 = division.GetX() + division.GetWidth() / 2
+        dy2 = division.GetY() + division.GetHeight() / 2
+
+        success = True
+        if division.GetHandleSide() == DIVISION_SIDE_LEFT:
+            if x <= x1 or x >= x2 or x >= dx2:
+                success = False
+            # Try it out first...
+            elif not division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, True):
+                success = False
+            else:
+                division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, False)
+        elif division.GetHandleSide() == DIVISION_SIDE_TOP:
+            if y <= y1 or y >= y2 or y >= dy2:
+                success = False
+            elif not division.ResizeAdjoining(DIVISION_SIDE_TOP, y, True):
+                success = False
+            else:
+                division.ResizingAdjoining(DIVISION_SIDE_TOP, y, False)
+        elif division.GetHandleSide() == DIVISION_SIDE_RIGHT:
+            if x <= x1 or x >= x2 or x <= dx1:
+                success = False
+            elif not division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, True):
+                success = False
+            else:
+                division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, False)
+        elif division.GetHandleSide() == DIVISION_SIDE_BOTTOM:
+            if y <= y1 or y >= y2 or y <= dy1:
+                success = False
+            elif not division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, True):
+                success = False
+            else:
+                division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, False)
+
+        if not success:
+            division.SetSize(originalW, originalH)
+            division.Move(dc, originalX, originalY)
+
+        divisionParent.Draw(dc)
+        division.GetEventHandler().OnDrawControlPoints(dc)
+
+
+
+DIVISION_MENU_SPLIT_HORIZONTALLY    =1
+DIVISION_MENU_SPLIT_VERTICALLY      =2
+DIVISION_MENU_EDIT_LEFT_EDGE        =3
+DIVISION_MENU_EDIT_TOP_EDGE         =4
+DIVISION_MENU_EDIT_RIGHT_EDGE       =5
+DIVISION_MENU_EDIT_BOTTOM_EDGE      =6
+DIVISION_MENU_DELETE_ALL            =7
+    
+
+
+class PopupDivisionMenu(wx.Menu):
+    def __init__(self):
+        wx.Menu.__init__(self)
+        self.Append(DIVISION_MENU_SPLIT_HORIZONTALLY,"Split horizontally")
+        self.Append(DIVISION_MENU_SPLIT_VERTICALLY,"Split vertically")
+        self.AppendSeparator()
+        self.Append(DIVISION_MENU_EDIT_LEFT_EDGE,"Edit left edge")
+        self.Append(DIVISION_MENU_EDIT_TOP_EDGE,"Edit top edge")
+
+        wx.EVT_MENU_RANGE(self, DIVISION_MENU_SPLIT_HORIZONTALLY, DIVISION_MENU_EDIT_BOTTOM_EDGE, self.OnMenu)
+
+    def SetClientData(self, data):
+        self._clientData = data
+
+    def GetClientData(self):
+        return self._clientData
+    
+    def OnMenu(self, event):
+        division = self.GetClientData()
+        if event.GetId() == DIVISION_MENU_SPLIT_HORIZONTALLY:
+            division.Divide(wx.HORIZONTAL)
+        elif event.GetId() == DIVISION_MENU_SPLIT_VERTICALLY:
+            division.Divide(wx.VERTICAL)
+        elif event.GetId() == DIVISION_MENU_EDIT_LEFT_EDGE:
+            division.EditEdge(DIVISION_SIDE_LEFT)
+        elif event.GetId() == DIVISION_MENU_EDIT_TOP_EDGE:
+            division.EditEdge(DIVISION_SIDE_TOP)
+            
+
+
+class DivisionShape(CompositeShape):
+    """A division shape is like a composite in that it can contain further
+    objects, but is used exclusively to divide another shape into regions,
+    or divisions. A wxDivisionShape is never free-standing.
+
+    Derived from:
+      wxCompositeShape
+    """
+    def __init__(self):
+        CompositeShape.__init__(self)
+        self.SetSensitivityFilter(OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_RIGHT)
+        self.SetCentreResize(False)
+        self.SetAttachmentMode(True)
+        self._leftSide = None
+        self._rightSide = None
+        self._topSide = None
+        self._bottomSide = None
+        self._handleSide = DIVISION_SIDE_NONE
+        self._leftSidePen = wx.BLACK_PEN
+        self._topSidePen = wx.BLACK_PEN
+        self._leftSideColour="BLACK"
+        self._topSideColour="BLACK"
+        self._leftSideStyle="Solid"
+        self._topSideStyle="Solid"
+        self.ClearRegions()
+
+    def SetLeftSide(self, shape):
+        """Set the the division on the left side of this division."""
+        self._leftSide = shape
+
+    def SetTopSide(self, shape):
+        """Set the the division on the top side of this division."""
+        self._topSide = shape
+        
+    def SetRightSide(self, shape):
+        """Set the the division on the right side of this division."""
+        self._rightSide = shape
+        
+    def SetBottomSide(self, shape):
+        """Set the the division on the bottom side of this division."""
+        self._bottomSide = shape
+
+    def GetLeftSide(self):
+        """Return the division on the left side of this division."""
+        return self._leftSide
+    
+    def GetTopSide(self):
+        """Return the division on the top side of this division."""
+        return self._topSide
+    
+    def GetRightSide(self):
+        """Return the division on the right side of this division."""
+        return self._rightSide
+    
+    def GetBottomSide(self):
+        """Return the division on the bottom side of this division."""
+        return self._bottomSide
+
+    def SetHandleSide(self, side):
+        """Sets the side which the handle appears on.
+
+        Either DIVISION_SIDE_LEFT or DIVISION_SIDE_TOP.
+        """
+        self._handleSide = side
+
+    def GetHandleSide(self):
+        """Return the side which the handle appears on."""
+        return self._handleSide
+
+    def SetLeftSidePen(self, pen):
+        """Set the colour for drawing the left side of the division."""
+        self._leftSidePen = pen
+        
+    def SetTopSidePen(self, pen):
+        """Set the colour for drawing the top side of the division."""
+        self._topSidePen = pen
+        
+    def GetLeftSidePen(self):
+        """Return the pen used for drawing the left side of the division."""
+        return self._leftSidePen
+
+    def GetTopSidePen(self):
+        """Return the pen used for drawing the top side of the division."""
+        return self._topSidePen
+
+    def GetLeftSideColour(self):
+        """Return the colour used for drawing the left side of the division."""
+        return self._leftSideColour
+    
+    def GetTopSideColour(self):
+        """Return the colour used for drawing the top side of the division."""
+        return self._topSideColour
+
+    def SetLeftSideColour(self, colour):
+        """Set the colour for drawing the left side of the division."""
+        self._leftSideColour = colour
+        
+    def SetTopSideColour(self, colour):
+        """Set the colour for drawing the top side of the division."""
+        self._topSideColour = colour
+        
+    def GetLeftSideStyle(self):
+        """Return the style used for the left side of the division."""
+        return self._leftSideStyle
+    
+    def GetTopSideStyle(self):
+        """Return the style used for the top side of the division."""
+        return self._topSideStyle
+
+    def SetLeftSideStyle(self, style):
+        self._leftSideStyle = style
+        
+    def SetTopSideStyle(self, style):
+        self._lefttopStyle = style
+        
+    def OnDraw(self, dc):
+        dc.SetBrush(wx.TRANSPARENT_BRUSH)
+        dc.SetBackgroundMode(wx.TRANSPARENT)
+
+        x1 = self.GetX()-self.GetWidth() / 2
+        y1 = self.GetY()-self.GetHeight() / 2
+        x2 = self.GetX() + self.GetWidth() / 2
+        y2 = self.GetY() + self.GetHeight() / 2
+
+        # Should subtract 1 pixel if drawing under Windows
+        if sys.platform[:3]=="win":
+            y2 -= 1
+
+        if self._leftSide:
+            dc.SetPen(self._leftSidePen)
+            dc.DrawLine(x1, y2, x1, y1)
+
+        if self._topSide:
+            dc.SetPen(self._topSidePen)
+            dc.DrawLine(x1, y1, x2, y1)
+
+        # For testing purposes, draw a rectangle so we know
+        # how big the division is.
+        #dc.SetBrush(wx.RED_BRUSH)
+        #dc.DrawRectangle(x1, y1, self.GetWidth(), self.GetHeight())
+        
+    def OnDrawContents(self, dc):
+        CompositeShape.OnDrawContents(self, dc)
+
+    def OnMovePre(self, dc, x, y, oldx, oldy, display = True):
+        diffX = x-oldx
+        diffY = y-oldy
+        for object in self._children:
+            object.Erase(dc)
+            object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display)
+        return True
+
+    def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+        if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
+            if self._parent:
+                hit = self._parent.HitTest(x, y)
+                if hit:
+                    attachment, dist = hit
+                self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment)
+            return
+        Shape.OnDragLeft(self, draw, x, y, keys, attachment)
+
+    def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+        if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
+            if self._parent:
+                hit = self._parent.HitTest(x, y)
+                if hit:
+                    attachment, dist = hit
+                self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment)
+            return
+        Shape.OnBeginDragLeft(x, y, keys, attachment)
+            
+    def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+        if self._canvas.HasCapture():
+            self._canvas.ReleaseMouse()
+        if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
+            if self._parent:
+                hit = self._parent.HitTest(x, y)
+                if hit:
+                    attachment, dist = hit
+                self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment)
+            return
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        dc.SetLogicalFunction(wx.COPY)
+
+        self._xpos, self._ypos = self._canvas.Snap(self._xpos, self._ypos)
+        self.GetEventHandler().OnMovePre(dc, x, y, self._oldX, self._oldY)
+
+        self.ResetControlPoints()
+        self.Draw(dc)
+        self.MoveLinks(dc)
+        self.GetEventHandler().OnDrawControlPoints(dc)
+
+        if self._canvas and not self._canvas.GetQuickEditMode():
+            self._canvas.Redraw(dc)
+
+    def SetSize(self, w, h, recursive = True):
+        self._width = w
+        self._height = h
+        RectangleShape.SetSize(self, w, h, recursive)
+
+    def CalculateSize(self):
+        pass
+
+    # Experimental
+    def OnRightClick(self, x, y, keys = 0, attachment = 0):
+        if keys & KEY_CTRL:
+            self.PopupMenu(x, y)
+        else:
+            if self._parent:
+                hit = self._parent.HitTest(x, y)
+                if hit:
+                    attachment, dist = hit
+                self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment)
+
+    # Divide wx.HORIZONTALly or wx.VERTICALly
+    def Divide(self, direction):
+        """Divide this division into two further divisions,
+        horizontally (direction is wxHORIZONTAL) or
+        vertically (direction is wxVERTICAL).
+        """
+        # Calculate existing top-left, bottom-right
+        x1 = self.GetX()-self.GetWidth() / 2
+        y1 = self.GetY()-self.GetHeight() / 2
+
+        compositeParent = self.GetParent()
+        oldWidth = self.GetWidth()
+        oldHeight = self.GetHeight()
+        if self.Selected():
+            self.Select(False)
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        if direction == wx.VERTICAL:
+            # Dividing vertically means notionally putting a horizontal
+            # line through it.
+            # Break existing piece into two.
+            newXPos1 = self.GetX()
+            newYPos1 = y1 + self.GetHeight() / 4
+            newXPos2 = self.GetX()
+            newYPos2 = y1 + 3 * self.GetHeight() / 4
+            newDivision = compositeParent.OnCreateDivision()
+            newDivision.Show(True)
+
+            self.Erase(dc)
+
+            # Anything adjoining the bottom of this division now adjoins the
+            # bottom of the new division.
+            for obj in compositeParent.GetDivisions():
+                if obj.GetTopSide() == self:
+                    obj.SetTopSide(newDivision)
+
+            newDivision.SetTopSide(self)
+            newDivision.SetBottomSide(self._bottomSide)
+            newDivision.SetLeftSide(self._leftSide)
+            newDivision.SetRightSide(self._rightSide)
+            self._bottomSide = newDivision
+
+            compositeParent.GetDivisions().append(newDivision)
+
+            # CHANGE: Need to insert this division at start of divisions in the
+            # object list, because e.g.:
+            # 1) Add division
+            # 2) Add contained object
+            # 3) Add division
+            # Division is now receiving mouse events _before_ the contained
+            # object, because it was added last (on top of all others)
+
+            # Add after the image that visualizes the container
+            compositeParent.AddChild(newDivision, compositeParent.FindContainerImage())
+
+            self._handleSide = DIVISION_SIDE_BOTTOM
+            newDivision.SetHandleSide(DIVISION_SIDE_TOP)
+
+            self.SetSize(oldWidth, oldHeight / 2)
+            self.Move(dc, newXPos1, newYPos1)
+
+            newDivision.SetSize(oldWidth, oldHeight / 2)
+            newDivision.Move(dc, newXPos2, newYPos2)
+        else:
+            # Dividing horizontally means notionally putting a vertical line
+            # through it.
+            # Break existing piece into two.
+            newXPos1 = x1 + self.GetWidth() / 4
+            newYPos1 = self.GetY()
+            newXPos2 = x1 + 3 * self.GetWidth() / 4
+            newYPos2 = self.GetY()
+            newDivision = compositeParent.OnCreateDivision()
+            newDivision.Show(True)
+
+            self.Erase(dc)
+
+            # Anything adjoining the left of this division now adjoins the
+            # left of the new division.
+            for obj in compositeParent.GetDivisions():
+                if obj.GetLeftSide() == self:
+                    obj.SetLeftSide(newDivision)
+
+            newDivision.SetTopSide(self._topSide)
+            newDivision.SetBottomSide(self._bottomSide)
+            newDivision.SetLeftSide(self)
+            newDivision.SetRightSide(self._rightSide)
+            self._rightSide = newDivision
+
+            compositeParent.GetDivisions().append(newDivision)
+            compositeParent.AddChild(newDivision, compositeParent.FindContainerImage())
+
+            self._handleSide = DIVISION_SIDE_RIGHT
+            newDivision.SetHandleSide(DIVISION_SIDE_LEFT)
+
+            self.SetSize(oldWidth / 2, oldHeight)
+            self.Move(dc, newXPos1, newYPos1)
+
+            newDivision.SetSize(oldWidth / 2, oldHeight)
+            newDivision.Move(dc, newXPos2, newYPos2)
+
+        if compositeParent.Selected():
+            compositeParent.DeleteControlPoints(dc)
+            compositeParent.MakeControlPoints()
+            compositeParent.MakeMandatoryControlPoints()
+
+        compositeParent.Draw(dc)
+        return True
+
+    def MakeControlPoints(self):
+        self.MakeMandatoryControlPoints()
+
+    def MakeMandatoryControlPoints(self):
+        maxX, maxY = self.GetBoundingBoxMax()
+        x = y = 0.0
+        direction = 0
+
+        if self._handleSide == DIVISION_SIDE_LEFT:
+            x=-maxX / 2
+            direction = CONTROL_POINT_HORIZONTAL
+        elif self._handleSide == DIVISION_SIDE_TOP:
+            y=-maxY / 2
+            direction = CONTROL_POINT_VERTICAL
+        elif self._handleSide == DIVISION_SIDE_RIGHT:
+            x = maxX / 2
+            direction = CONTROL_POINT_HORIZONTAL
+        elif self._handleSide == DIVISION_SIDE_BOTTOM:
+            y = maxY / 2
+            direction = CONTROL_POINT_VERTICAL
+
+        if self._handleSide != DIVISION_SIDE_NONE:
+            control = DivisionControlPoint(self._canvas, self, CONTROL_POINT_SIZE, x, y, direction)
+            self._canvas.AddShape(control)
+            self._controlPoints.append(control)
+
+    def ResetControlPoints(self):
+        self.ResetMandatoryControlPoints()
+
+    def ResetMandatoryControlPoints(self):
+        if not self._controlPoints:
+            return
+
+        maxX, maxY = self.GetBoundingBoxMax()
+
+        node = self._controlPoints[0]
+
+        if self._handleSide == DIVISION_SIDE_LEFT and node:
+            node._xoffset=-maxX / 2
+            node._yoffset = 0.0
+
+        if self._handleSide == DIVISION_SIDE_TOP and node:
+            node._xoffset = 0.0
+            node._yoffset=-maxY / 2
+
+        if self._handleSide == DIVISION_SIDE_RIGHT and node:
+            node._xoffset = maxX / 2
+            node._yoffset = 0.0
+
+        if self._handleSide == DIVISION_SIDE_BOTTOM and node:
+            node._xoffset = 0.0
+            node._yoffset = maxY / 2
+
+    def AdjustLeft(self, left, test):
+        """Adjust a side.
+
+        Returns FALSE if it's not physically possible to adjust it to
+        this point.
+        """
+        x2 = self.GetX() + self.GetWidth() / 2
+
+        if left >= x2:
+            return False
+
+        if test:
+            return True
+
+        newW = x2-left
+        newX = left + newW / 2
+        self.SetSize(newW, self.GetHeight())
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        self.Move(dc, newX, self.GetY())
+        return True
+
+    def AdjustTop(self, top, test):
+        """Adjust a side.
+
+        Returns FALSE if it's not physically possible to adjust it to
+        this point.
+        """
+        y2 = self.GetY() + self.GetHeight() / 2
+
+        if top >= y2:
+            return False
+
+        if test:
+            return True
+
+        newH = y2-top
+        newY = top + newH / 2
+        self.SetSize(self.GetWidth(), newH)
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        self.Move(dc, self.GetX(), newY)
+        return True
+
+    def AdjustRight(self, right, test):
+        """Adjust a side.
+
+        Returns FALSE if it's not physically possible to adjust it to
+        this point.
+        """
+        x1 = self.GetX()-self.GetWidth() / 2
+
+        if right <= x1:
+            return False
+
+        if test:
+            return True
+
+        newW = right-x1
+        newX = x1 + newW / 2
+        self.SetSize(newW, self.GetHeight())
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        self.Move(dc, newX, self.GetY())
+        return True
+    
+    def AdjustTop(self, top, test):
+        """Adjust a side.
+
+        Returns FALSE if it's not physically possible to adjust it to
+        this point.
+        """
+        y1 = self.GetY()-self.GetHeight() / 2
+
+        if bottom <= y1:
+            return False
+
+        if test:
+            return True
+
+        newH = bottom-y1
+        newY = y1 + newH / 2
+        self.SetSize(self.GetWidth(), newH)
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        self.Move(dc, self.GetX(), newY)
+        return True
+
+    # Resize adjoining divisions.
+    
+    # Behaviour should be as follows:
+    # If right edge moves, find all objects whose left edge
+    # adjoins this object, and move left edge accordingly.
+    # If left..., move ... right.
+    # If top..., move ... bottom.
+    # If bottom..., move top.
+    # If size goes to zero or end position is other side of start position,
+    # resize to original size and return.
+    #
+    def ResizeAdjoining(self, side, newPos, test):
+        """Resize adjoining divisions at the given side.
+
+        If test is TRUE, just see whether it's possible for each adjoining
+        region, returning FALSE if it's not.
+
+        side can be one of:
+
+        * DIVISION_SIDE_NONE
+        * DIVISION_SIDE_LEFT
+        * DIVISION_SIDE_TOP
+        * DIVISION_SIDE_RIGHT
+        * DIVISION_SIDE_BOTTOM
+        """
+        divisionParent = self.GetParent()
+        for division in divisionParent.GetDivisions():
+            if side == DIVISION_SIDE_LEFT:
+                if division._rightSide == self:
+                    success = division.AdjustRight(newPos, test)
+                    if not success and test:
+                        return false
+            elif side == DIVISION_SIDE_TOP:
+                if division._bottomSide == self:
+                    success = division.AdjustBottom(newPos, test)
+                    if not success and test:
+                        return False
+            elif side == DIVISION_SIDE_RIGHT:
+                if division._leftSide == self:
+                    success = division.AdjustLeft(newPos, test)
+                    if not success and test:
+                        return False
+            elif side == DIVISION_SIDE_BOTTOM:
+                if division._topSide == self:
+                    success = division.AdjustTop(newPos, test)
+                    if not success and test:
+                        return False
+        return True
+    
+    def EditEdge(self, side):
+        print "EditEdge() not implemented."
+
+    def PopupMenu(self, x, y):
+        menu = PopupDivisionMenu()
+        menu.SetClientData(self)
+        if self._leftSide:
+            menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, True)
+        else:
+            menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, False)
+        if self._topSide:
+            menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, True)
+        else:
+            menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, False)
+
+        x1, y1 = self._canvas.GetViewStart()
+        unit_x, unit_y = self._canvas.GetScrollPixelsPerUnit()
+
+        dc = wx.ClientDC(self.GetCanvas())
+        self.GetCanvas().PrepareDC(dc)
+
+        mouse_x = dc.LogicalToDeviceX(x-x1 * unit_x)
+        mouse_y = dc.LogicalToDeviceY(y-y1 * unit_y)
+
+        self._canvas.PopupMenu(menu, (mouse_x, mouse_y))
+
+