# -*- coding: utf-8 -*-
#----------------------------------------------------------------------------
# Name:         composit.py
# Purpose:      Composite class
#
# Author:       Pierre Hjälm (from C++ original by Julian Smart)
#
# Created:      2004-05-08
# Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart
# Licence:      wxWindows license
# Tags:         phoenix-port, unittest, py3-port, documented
#----------------------------------------------------------------------------
"""
The :class:`~lib.ogl.composit.CompositeShape` class.
"""
import sys
import wx

from .basic import RectangleShape, Shape, ControlPoint
from .oglmisc import *

_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):
    """The :class:`ConstraintType` class."""
    def __init__(self, theType, theName, thePhrase):
        """
        Default class constructor.
        
        :param `theType`: one of the folowing
         ====================================== ================================
         Constraint type                        Description
         ====================================== ================================
         `CONSTRAINT_CENTRED_VERTICALLY`        Centered vertically
         `CONSTRAINT_CENTRED_HORIZONTALLY`      Centered horizontally
         `CONSTRAINT_CENTRED_BOTH`              Centered in both directions
         `CONSTRAINT_LEFT_OF`                   Center left of
         `CONSTRAINT_RIGHT_OF`                  Center right of
         `CONSTRAINT_ABOVE`                     Center above
         `CONSTRAINT_BELOW`                     Center below
         `CONSTRAINT_ALIGNED_TOP`               Align top
         `CONSTRAINT_ALIGNED_BOTTOM`            Align bottom
         `CONSTRAINT_ALIGNED_LEFT`              Align left
         `CONSTRAINT_ALIGNED_RIGHT`             Align right
         `CONSTRAINT_MIDALIGNED_TOP`            Middle align top
         `CONSTRAINT_MIDALIGNED_BOTTOM`         Middle align bottom
         `CONSTRAINT_MIDALIGNED_LEFT`           Middle align left
         `CONSTRAINT_MIDALIGNED_RIGHT`          Middle align right
         ====================================== ================================

        :param `theName`: the name for the constraint
        :param `thePhrase`: the descriptive phrase

        """
        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):
    """
    The :class:`Constraint` class helps specify how child shapes are laid out
    with respect to siblings and parents.
    """
    def __init__(self, type, constraining, constrained):
        """
        Default class constructor.
        
        :param `type`: see :class:`ConstraintType` for valid types
        :param `constraining`: the constraining :class:`Shape`
        :param `constrained`: the constrained :class:`Shape`
        
        """
        self._xSpacing = 0.0
        self._ySpacing = 0.0

        self._constraintType = type
        self._constrainingObject = 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.
        
        :param `x`: the x position
        :param `y`: the y position
        
        """
        self._xSpacing = x
        self._ySpacing = y
        
    def Equals(self, a, b):
        """
        Return `True` if a and b are approximately equal (for the purposes
        of evaluating the constraint).
        
        :param `a`: ???
        :param `b`: ???
        
        """
        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._constrainingObject.GetBoundingBoxMax()
        minWidth, minHeight = self._constrainingObject.GetBoundingBoxMin()
        x = self._constrainingObject.GetX()
        y = self._constrainingObject.GetY()

        dc = wx.MemoryDC()
        dc.SelectObject(self._constrainingObject.GetCanvas()._Buffer)

        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.0)
                startY = y - minHeight / 2.0
            else: # Otherwise, use default spacing
                spacingY = self._ySpacing
                startY = y - (totalObjectHeight + (n + 1) * spacingY) / 2.0

            # Now position the objects
            changed = False
            for constrainedObject in self._constrainedObjects:
                width2, height2 = constrainedObject.GetBoundingBoxMax()
                startY += spacingY + height2 / 2.0
                if not self.Equals(startY, constrainedObject.GetY()):
                    constrainedObject.Move(dc, constrainedObject.GetX(), startY, False)
                    changed = True
                startY += height2 / 2.0
            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.0)
                startX = x - minWidth / 2.0
            else: # Otherwise, use default spacing
                spacingX = self._xSpacing
                startX = x - (totalObjectWidth + (n + 1) * spacingX) / 2.0

            # Now position the objects
            changed = False
            for constrainedObject in self._constrainedObjects:
                width2, height2 = constrainedObject.GetBoundingBoxMax()
                startX += spacingX + width2 / 2.0
                if not self.Equals(startX, constrainedObject.GetX()):
                    constrainedObject.Move(dc, startX, constrainedObject.GetY(), False)
                    changed = True
                startX += width2 / 2.0
            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.0)
                startX = x - minWidth / 2.0
            else: # Otherwise, use default spacing
                spacingX = self._xSpacing
                startX = x - (totalObjectWidth + (n + 1) * spacingX) / 2.0

            # Check if within the constraining object...
            if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight:
                spacingY = (minHeight - totalObjectHeight) / (n + 1.0)
                startY = y - minHeight / 2.0
            else: # Otherwise, use default spacing
                spacingY = self._ySpacing
                startY = y - (totalObjectHeight + (n + 1) * spacingY) / 2.0

            # Now position the objects
            changed = False
            for constrainedObject in self._constrainedObjects:
                width2, height2 = constrainedObject.GetBoundingBoxMax()
                startX += spacingX + width2 / 2.0
                startY += spacingY + height2 / 2.0

                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.0
                startY += height2 / 2.0
            return changed
        elif self._constraintType == CONSTRAINT_LEFT_OF:
            changed = False
            for constrainedObject in self._constrainedObjects:
                width2, height2 = constrainedObject.GetBoundingBoxMax()

                x3 = x - minWidth / 2.0 - width2 / 2.0 - 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.0 + width2 / 2.0 + 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.0 - height2 / 2.0 - 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.0 + height2 / 2.0 + 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.0 + width2 / 2.0 + 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.0 - width2 / 2.0 - 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.0 + height2 / 2.0 + 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.0 - height2 / 2.0 - 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.0
                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.0
                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.0
                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.0
                if not self.Equals(y3, constrainedObject.GetY()):
                    changed = True
                    constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
            return changed
        
        return False
    
OGLConstraint = wx.deprecated(Constraint,
                     "The OGLConstraint name is deprecated, use `ogl.Constraint` instead.")


class CompositeShape(RectangleShape):
    """
    The :class:`CompositeShape` is a shape with a list of child objects, and a
    list of size and positioning constraints between the children.
    """
    def __init__(self):
        """
        Default class constructor.
        
        """
        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):
        """The draw handler."""
        x1 = self._xpos - self._width / 2.0
        y1 = self._ypos - self._height / 2.0

        if self._shadowMode != SHADOW_NONE:
            if self._shadowBrush:
                dc.SetBrush(self._shadowBrush)
            dc.SetPen(wx.Pen(wx.WHITE, 1, wx.PENSTYLE_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):
        """The draw contents handler."""
        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):
        """The move 'pre' handler."""
        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):
        """The erase handler."""
        RectangleShape.OnErase(self, dc)
        for object in self._children:
            object.Erase(dc)

    def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
        """The drag left handler."""
        xx, yy = self._canvas.Snap(x, y)
        offsetX = xx - _objectStartX
        offsetY = yy - _objectStartY

        # use the DCOverlay stuff, note that drawing is done to the ClientDC
        dc = wx.ClientDC(self.GetCanvas())
        odc = wx.DCOverlay(self.GetCanvas()._Overlay, dc)
        dc.SetLogicalFunction(OGLRBLF)

        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_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):
        """The begin drag left handler."""
        global _objectStartX, _objectStartY

        _objectStartX = x
        _objectStartY = y

        # use the DCOverlay stuff, note that drawing is done to the ClientDC
        dc = wx.ClientDC(self.GetCanvas())
        odc = wx.DCOverlay(self.GetCanvas()._Overlay, dc)
        dc.SetLogicalFunction(OGLRBLF)

        dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.PENSTYLE_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):
        """The end drag left handler."""
        if self._canvas.HasCapture():
            self._canvas.ReleaseMouse()

        if not self._draggable:
            if self._parent:
                self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, 0)
            return
            
        # use the DCOverlay stuff, note that drawing is done to the ClientDC
        dc = wx.ClientDC(self.GetCanvas())
        odc = wx.DCOverlay(self.GetCanvas()._Overlay, dc)
        dc.SetLogicalFunction(wx.COPY)

        self.Erase(dc)
        
        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):
        """The right click handler.
        
        :note: 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):
        """
        Set the size.
        
        :param `w`: the width
        :param `h`: the heigth
        :param `recursive`: size the children recursively
        
        """
        self.SetAttachmentSize(w, h)

        xScale = float(w) / max(1, self.GetWidth())
        yScale = float(h) / max(1, self.GetHeight())

        self._width = w
        self._height = h

        if not recursive:
            return

        dc = wx.MemoryDC()
        dc.SelectObject(self.GetCanvas()._Buffer)

        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):
        """
        Add a shape to the composite. If addAfter is not None, the shape
        will be added after addAfter.
        
        :param `child`: an instance of :class:`~lib.ogl.Shape`
        :param `addAfter`: an instance of :class:`~lib.ogl.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.
        
        :param `child`: an instance of :class:`~lib.ogl.Shape`
        
        """
        if child in self._children:
            self._children.remove(child)
        if child in self._divisions:
            self._divisions.remove(child)
        self.RemoveChildFromConstraints(child)
        child.SetParent(None)

    def Delete(self):
        """
        Fully disconnect this shape from parents, children, the
        canvas, etc.
        """
        for child in self.GetChildren():
            self.RemoveChild(child)
            child.Delete()
        RectangleShape.Delete(self)
        self._constraints = [] 
        self._divisions = []

    def DeleteConstraintsInvolvingChild(self, child):
        """
        This function deletes constraints which mention the given child.

        Used when deleting a child from the composite.
        
        :param `child`: an instance of :class:`~lib.ogl.Shape`
        
        """
        for constraint in self._constraints:
            if constraint._constrainingObject == child or child in constraint._constrainedObjects:
                self._constraints.remove(constraint)

    def RemoveChildFromConstraints(self, child):
        """
        Removes the child from the constraints.
        
        :param `child`: an instance of :class:`~lib.ogl.Shape`
        
        """
        
        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.
        
        :param `constraint`: an instance of :class:`~lib.ogl.Shape`

        """
        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.

        :param `type`: see :class:`ConstraintType` for valid types
        :param `constraining`: the constraining :class:`Shape`
        :param `constrained`: the constrained :class:`Shape`
        
        """
        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.
        
        :param `cId`: The constraint id to find.

        :returns: None or a tuple of the constraint and the actual composite the
         constraint was in, in case that composite was a descendant of
         this composit.
        
        """
        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.
        
        :param `constraint`: the constraint to delete
        
        """
        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.0 > maxX:
                maxX = child.GetX() + w / 2.0
            if child.GetX() - w / 2.0 < minX:
                minX = child.GetX() - w / 2.0
            if child.GetY() + h / 2.0 > maxY:
                maxY = child.GetY() + h / 2.0
            if child.GetY() - h / 2.0 < minY:
                minY = child.GetY() - h / 2.0

        self._width = maxX - minX
        self._height = maxY - minY
        self._xpos = self._width / 2.0 + minX
        self._ypos = self._height / 2.0 + 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):
        """
        Constrain the children.
        
        :returns: True if constained otherwise False
        
        """
        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.MemoryDC()
        dc.SelectObject(self.GetCanvas()._Buffer)
        
        division.Move(dc, self.GetX(), self.GetY())
        self.Recompute()
        division.Show(True)

    def OnCreateDivision(self):
        """Create division handler."""
        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):
        """
        Check if division is descendant.
        
        :param `division`: divison to check
        :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


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.MemoryDC()
        dc.SelectObject(self.GetCanvas()._Buffer)

        division = self._shape
        divisionParent = division.GetParent()

        # Need to check it's within the bounds of the parent composite
        x1 = divisionParent.GetX() - divisionParent.GetWidth() / 2.0
        y1 = divisionParent.GetY() - divisionParent.GetHeight() / 2.0
        x2 = divisionParent.GetX() + divisionParent.GetWidth() / 2.0
        y2 = divisionParent.GetY() + divisionParent.GetHeight() / 2.0

        # Need to check it has not made the division zero or negative
        # width / height
        dx1 = division.GetX() - division.GetWidth() / 2.0
        dy1 = division.GetY() - division.GetHeight() / 2.0
        dx2 = division.GetX() + division.GetWidth() / 2.0
        dy2 = division.GetY() + division.GetHeight() / 2.0

        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 :class:`DivisionShape` class 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.

    """
    def __init__(self):
        """
        Default class constructor.
        """
        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.

        :param `side`: 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):
        """
        Set the left side style.
        
        :param `style`: valid values ???
        
        """
        self._leftSideStyle = style
        
    def SetTopSideStyle(self, style):
        """
        Set the top side style.
        
        :param `style`: valid values ???
        
        """
        self._lefttopStyle = style
        
    def OnDraw(self, dc):
        """The draw handler."""
        dc.SetBrush(wx.TRANSPARENT_BRUSH)
        dc.SetBackgroundMode(wx.TRANSPARENT)

        x1 = self.GetX() - self.GetWidth() / 2.0
        y1 = self.GetY() - self.GetHeight() / 2.0
        x2 = self.GetX() + self.GetWidth() / 2.0
        y2 = self.GetY() + self.GetHeight() / 2.0

        # 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):
        """The draw contens handler."""
        CompositeShape.OnDrawContents(self, dc)

    def OnMovePre(self, dc, x, y, oldx, oldy, display = True):
        """The move 'pre' handler."""
        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):
        """The drag left handler."""
        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):
        """The begin drag left handler."""
        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):
        """The end drag left handler."""
        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.MemoryDC()
        dc.SelectObject(self.GetCanvas()._Buffer)
        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):
        """
        Set the size.
        
        :param `w`: the width
        :param `h`: the heigth
        :param `recursive`: `True` recurse all children
        
        """
        self._width = w
        self._height = h
        RectangleShape.SetSize(self, w, h, recursive)

    def CalculateSize(self):
        """not implemented???"""    
        pass

    # Experimental
    def OnRightClick(self, x, y, keys = 0, attachment = 0):
        """The right click handler."""
        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)

    def Divide(self, direction):
        """Divide this division into two further divisions.
        
        :param `direction`: `wx.HORIZONTAL` for horizontal or `wx.VERTICAL` for
         vertical division.
        
        """
        # Calculate existing top-left, bottom-right
        x1 = self.GetX() - self.GetWidth() / 2.0
        y1 = self.GetY() - self.GetHeight() / 2.0

        compositeParent = self.GetParent()
        oldWidth = self.GetWidth()
        oldHeight = self.GetHeight()
        if self.Selected():
            self.Select(False)

        dc = wx.MemoryDC()
        dc.SelectObject(self.GetCanvas()._Buffer)

        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.0
            newXPos2 = self.GetX()
            newYPos2 = y1 + 3 * self.GetHeight() / 4.0
            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.0)
            self.Move(dc, newXPos1, newYPos1)

            newDivision.SetSize(oldWidth, oldHeight / 2.0)
            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.0
            newYPos1 = self.GetY()
            newXPos2 = x1 + 3 * self.GetWidth() / 4.0
            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.0, oldHeight)
            self.Move(dc, newXPos1, newYPos1)

            newDivision.SetSize(oldWidth / 2.0, oldHeight)
            newDivision.Move(dc, newXPos2, newYPos2)

        if compositeParent.Selected():
            compositeParent.DeleteControlPoints(dc)
            compositeParent.MakeControlPoints()
            compositeParent.MakeMandatoryControlPoints()

        compositeParent.Draw(dc)
        return True

    def MakeControlPoints(self):
        """Make control points."""
        self.MakeMandatoryControlPoints()

    def MakeMandatoryControlPoints(self):
        """Make mandatory control points."""
        maxX, maxY = self.GetBoundingBoxMax()
        x = y = 0.0
        direction = 0

        if self._handleSide == DIVISION_SIDE_LEFT:
            x = -maxX / 2.0
            direction = CONTROL_POINT_HORIZONTAL
        elif self._handleSide == DIVISION_SIDE_TOP:
            y = -maxY / 2.0
            direction = CONTROL_POINT_VERTICAL
        elif self._handleSide == DIVISION_SIDE_RIGHT:
            x = maxX / 2.0
            direction = CONTROL_POINT_HORIZONTAL
        elif self._handleSide == DIVISION_SIDE_BOTTOM:
            y = maxY / 2.0
            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):
        """Reset control points."""
        self.ResetMandatoryControlPoints()

    def ResetMandatoryControlPoints(self):
        """Reset mandatory control points."""
        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.0
            node._yoffset = 0.0

        if self._handleSide == DIVISION_SIDE_TOP and node:
            node._xoffset = 0.0
            node._yoffset = -maxY / 2.0

        if self._handleSide == DIVISION_SIDE_RIGHT and node:
            node._xoffset = maxX / 2.0
            node._yoffset = 0.0

        if self._handleSide == DIVISION_SIDE_BOTTOM and node:
            node._xoffset = 0.0
            node._yoffset = maxY / 2.0

    def AdjustLeft(self, left, test):
        """
        Adjust a side.
        
        :param `left`: desired left position ???
        :param `test`: if `True` just a test
        :returns: `False` if it's not physically possible to adjust it to
         this point.
        """
        x2 = self.GetX() + self.GetWidth() / 2.0

        if left >= x2:
            return False

        if test:
            return True

        newW = x2 - left
        newX = left + newW / 2.0
        self.SetSize(newW, self.GetHeight())

        dc = wx.MemoryDC()
        dc.SelectObject(self.GetCanvas()._Buffer)

        self.Move(dc, newX, self.GetY())
        return True

    def AdjustRight(self, right, test):
        """
        Adjust a side.

        :param `right`: desired right position ???
        :param `test`: if `True` just a test
        :returns: `False` if it's not physically possible to adjust it to
         this point.
        
        """
        x1 = self.GetX() - self.GetWidth() / 2.0

        if right <= x1:
            return False

        if test:
            return True

        newW = right - x1
        newX = x1 + newW / 2.0
        self.SetSize(newW, self.GetHeight())

        dc = wx.MemoryDC()
        dc.SelectObject(self.GetCanvas()._Buffer)

        self.Move(dc, newX, self.GetY())
        return True
    
    def AdjustTop(self, top, test):
        """
        Adjust a side.

        :param `top`: desired top position ???
        :param `test`: if `True` just a test
        :returns: `False` if it's not physically possible to adjust it to
         this point.

        """
        y1 = self.GetY() - self.GetHeight() / 2.0

        if top <= y1:
            return False

        if test:
            return True

        newH = top - y1
        newY = y1 + newH / 2.0
        self.SetSize(self.GetWidth(), newH)

        dc = wx.MemoryDC()
        dc.SelectObject(self.GetCanvas()._Buffer)

        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.

        :param `side`: can be one of

        ======================= =======================
        Side option             Description
        ======================= =======================
        `DIVISION_SIDE_NONE`    no side
        `DIVISION_SIDE_LEFT`    Left side
        `DIVISION_SIDE_TOP`     Top side
        `DIVISION_SIDE_RIGHT`   Right side
        `DIVISION_SIDE_BOTTOM`  Bottom side
        ======================= =======================
        
        :param `newPos`: new position
        :param `test`: if `True`, just see whether it's possible for each
         adjoining region, returning `False` if it's not.

        """
        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):
        """Popup menu handler."""
        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.MemoryDC()
        dc.SelectObject(self.GetCanvas()._Buffer)

        mouse_x = dc.LogicalToDeviceX(x - x1 * unit_x)
        mouse_y = dc.LogicalToDeviceY(y - y1 * unit_y)

        self._canvas.PopupMenu(menu, (mouse_x, mouse_y))