--- /dev/null
+# -*- 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))
+
+