ca8071ca9f
Two simple problems found in the new python ogl code. First is the patch for _canvas.py. Essentially: dx = abs(dc.LogicalToDeviceX(x - self._firstDragX)) dy = abs(dc.LogicalToDeviceY(y - self._firstDragY)) was incorrect because (x,y) and (self._firstDragX, self._firstDragY) are both already in Logical coordinates. Therefore the difference between the two is also in logical coordinates, and the conversion call is an error. This bug surfaces when you have OGL on a scrollwin, and you are far from the origin of the canvas. The second change in _composit.py basically removes the assumption that the child is in both self._children and self._divisions. Causes many problems when it's not. ;) git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@30415 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
1431 lines
51 KiB
Python
1431 lines
51 KiB
Python
# -*- coding: iso-8859-1 -*-
|
|
#----------------------------------------------------------------------------
|
|
# Name: composit.py
|
|
# Purpose: Composite class
|
|
#
|
|
# Author: Pierre Hjälm (from C++ original by Julian Smart)
|
|
#
|
|
# Created: 2004-05-08
|
|
# RCS-ID: $Id$
|
|
# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
|
|
# Licence: wxWindows license
|
|
#----------------------------------------------------------------------------
|
|
|
|
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
|
|
|
|
|
|
# Backwards compatibility names. These should be removed eventually.
|
|
gyCONSTRAINT_CENTRED_VERTICALLY = CONSTRAINT_CENTRED_VERTICALLY
|
|
gyCONSTRAINT_CENTRED_HORIZONTALLY = CONSTRAINT_CENTRED_HORIZONTALLY
|
|
gyCONSTRAINT_CENTRED_BOTH = CONSTRAINT_CENTRED_BOTH
|
|
gyCONSTRAINT_LEFT_OF = CONSTRAINT_LEFT_OF
|
|
gyCONSTRAINT_RIGHT_OF = CONSTRAINT_RIGHT_OF
|
|
gyCONSTRAINT_ABOVE = CONSTRAINT_ABOVE
|
|
gyCONSTRAINT_BELOW = CONSTRAINT_BELOW
|
|
gyCONSTRAINT_ALIGNED_TOP = CONSTRAINT_ALIGNED_TOP
|
|
gyCONSTRAINT_ALIGNED_BOTTOM = CONSTRAINT_ALIGNED_BOTTOM
|
|
gyCONSTRAINT_ALIGNED_LEFT = CONSTRAINT_ALIGNED_LEFT
|
|
gyCONSTRAINT_ALIGNED_RIGHT = CONSTRAINT_ALIGNED_RIGHT
|
|
gyCONSTRAINT_MIDALIGNED_TOP = CONSTRAINT_MIDALIGNED_TOP
|
|
gyCONSTRAINT_MIDALIGNED_BOTTOM = CONSTRAINT_MIDALIGNED_BOTTOM
|
|
gyCONSTRAINT_MIDALIGNED_LEFT = CONSTRAINT_MIDALIGNED_LEFT
|
|
gyCONSTRAINT_MIDALIGNED_RIGHT = CONSTRAINT_MIDALIGNED_RIGHT
|
|
|
|
|
|
|
|
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.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._core._deprecated(Constraint,
|
|
"The OGLConstraint name is deprecated, use `ogl.Constraint` instead.")
|
|
|
|
|
|
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.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.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 = 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.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.
|
|
"""
|
|
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 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.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):
|
|
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.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 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.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):
|
|
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.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.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.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):
|
|
self.MakeMandatoryControlPoints()
|
|
|
|
def MakeMandatoryControlPoints(self):
|
|
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):
|
|
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.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.
|
|
|
|
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.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.0
|
|
|
|
if top >= y2:
|
|
return False
|
|
|
|
if test:
|
|
return True
|
|
|
|
newH = y2 - top
|
|
newY = top + newH / 2.0
|
|
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.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.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.0
|
|
|
|
if bottom <= y1:
|
|
return False
|
|
|
|
if test:
|
|
return True
|
|
|
|
newH = bottom - y1
|
|
newY = y1 + newH / 2.0
|
|
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))
|
|
|
|
|