mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-27 21:52:25 -06:00
1569 lines
55 KiB
Python
1569 lines
55 KiB
Python
# -*- 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))
|
|
|
|
|