%define DOCSTRING
"The Object Graphics Library provides for simple drawing and manipulation
-of 2D objects."
+of 2D objects. (This version is deprecated, please use wx.lib.ogl instead.)"
%enddef
%module(docstring=DOCSTRING) ogl
%pythoncode { wx = _core }
%pythoncode { __docfilter__ = wx.__DocFilter(globals()) }
+%pythoncode {
+ import warnings
+ warnings.warn("This module is deprecated. Please use the wx.lib.ogl pacakge instead.",
+ DeprecationWarning, stacklevel=2)
+}
+
MAKE_CONST_WXSTRING_NOSWIG(EmptyString);
_treeList = [
# new stuff
- ('Recent Additions', [
+ ('Recent Additions and Updates', [
'VListBox',
'Listbook',
'MaskedNumCtrl',
+# -*- coding: iso-8859-1 -*-
# 11/20/2003 - Jeff Grimmett (grimmtooth@softhome.net)
#
# o Updated for wx namespace
#
+# 20040508 - Pierre Hjälm
+#
+# o Changed to use the python version of OGL
+# o Added TextShape, CompositeShape and CompositeShape with divisions
+#
-import wx
-import wx.ogl as ogl
+import wx
+import wx.lib.ogl as ogl
import images
-##wx.Trap()
-
#----------------------------------------------------------------------
class DiamondShape(ogl.PolygonShape):
if h == 0.0:
h = 60.0
- # Either ogl.RealPoints or 2-tuples of floats works.
-
- #points = [ ogl.RealPoint(0.0, -h/2.0),
- # ogl.RealPoint(w/2.0, 0.0),
- # ogl.RealPoint(0.0, h/2.0),
- # ogl.RealPoint(-w/2.0, 0.0),
- # ]
points = [ (0.0, -h/2.0),
(w/2.0, 0.0),
(0.0, h/2.0),
self.SetCornerRadius(-0.3)
+#----------------------------------------------------------------------
+
+class CompositeDivisionShape(ogl.CompositeShape):
+ def __init__(self, canvas):
+ ogl.CompositeShape.__init__(self)
+
+ self.SetCanvas(canvas)
+
+ # create a division in the composite
+ self.MakeContainer()
+
+ # add a shape to the original division
+ shape2 = ogl.RectangleShape(40, 60)
+ self.GetDivisions()[0].AddChild(shape2)
+
+ # now divide the division so we get 2
+ self.GetDivisions()[0].Divide(wx.HORIZONTAL)
+
+ # and add a shape to the second division (and move it to the
+ # centre of the division)
+ shape3 = ogl.CircleShape(40)
+ shape3.SetBrush(wx.CYAN_BRUSH)
+ self.GetDivisions()[1].AddChild(shape3)
+ shape3.SetX(self.GetDivisions()[1].GetX())
+
+ for division in self.GetDivisions():
+ division.SetSensitivityFilter(0)
+
+#----------------------------------------------------------------------
+
+class CompositeShape(ogl.CompositeShape):
+ def __init__(self, canvas):
+ ogl.CompositeShape.__init__(self)
+
+ self.SetCanvas(canvas)
+
+ constraining_shape = ogl.RectangleShape(120, 100)
+ constrained_shape1 = ogl.CircleShape(50)
+ constrained_shape2 = ogl.RectangleShape(80, 20)
+
+ constraining_shape.SetBrush(wx.BLUE_BRUSH)
+ constrained_shape2.SetBrush(wx.RED_BRUSH)
+
+ self.AddChild(constraining_shape)
+ self.AddChild(constrained_shape1)
+ self.AddChild(constrained_shape2)
+
+ constraint = ogl.Constraint(ogl.CONSTRAINT_MIDALIGNED_BOTTOM, constraining_shape, [constrained_shape1, constrained_shape2])
+ self.AddConstraint(constraint)
+ self.Recompute()
+
+ # If we don't do this, the shapes will be able to move on their
+ # own, instead of moving the composite
+ constraining_shape.SetDraggable(False)
+ constrained_shape1.SetDraggable(False)
+ constrained_shape2.SetDraggable(False)
+
+ # If we don't do this the shape will take all left-clicks for itself
+ constraining_shape.SetSensitivityFilter(0)
+
+
#----------------------------------------------------------------------
class DividedShape(ogl.DividedShape):
def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
print "***", self
- self.base_OnSizingEndDragLeft(pt, x, y, keys, attch)
+ ogl.DividedShape.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
self.SetRegionSizes()
self.ReformatRegions()
self.GetCanvas().Refresh()
self.statbarFrame = frame
def UpdateStatusBar(self, shape):
- x,y = shape.GetX(), shape.GetY()
+ x, y = shape.GetX(), shape.GetY()
width, height = shape.GetBoundingBoxMax()
- self.statbarFrame.SetStatusText("Pos: (%d,%d) Size: (%d, %d)" %
+ self.statbarFrame.SetStatusText("Pos: (%d, %d) Size: (%d, %d)" %
(x, y, width, height))
- def OnLeftClick(self, x, y, keys = 0, attachment = 0):
+ def OnLeftClick(self, x, y, keys=0, attachment=0):
shape = self.GetShape()
- print shape.__class__, shape.GetClassName()
canvas = shape.GetCanvas()
dc = wx.ClientDC(canvas)
canvas.PrepareDC(dc)
self.UpdateStatusBar(shape)
- def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ def OnEndDragLeft(self, x, y, keys=0, attachment=0):
shape = self.GetShape()
- self.base_OnEndDragLeft(x, y, keys, attachment)
+ ogl.ShapeEvtHandler.OnEndDragLeft(self, x, y, keys, attachment)
if not shape.Selected():
self.OnLeftClick(x, y, keys, attachment)
def OnSizingEndDragLeft(self, pt, x, y, keys, attch):
- self.base_OnSizingEndDragLeft(pt, x, y, keys, attch)
+ ogl.ShapeEvtHandler.OnSizingEndDragLeft(self, pt, x, y, keys, attch)
self.UpdateStatusBar(self.GetShape())
def OnMovePost(self, dc, x, y, oldX, oldY, display):
- self.base_OnMovePost(dc, x, y, oldX, oldY, display)
+ ogl.ShapeEvtHandler.OnMovePost(self, dc, x, y, oldX, oldY, display)
self.UpdateStatusBar(self.GetShape())
rRectBrush = wx.Brush("MEDIUM TURQUOISE", wx.SOLID)
dsBrush = wx.Brush("WHEAT", wx.SOLID)
+ self.MyAddShape(
+ CompositeDivisionShape(self),
+ 310, 310, wx.BLACK_PEN, wx.BLUE_BRUSH, "Division"
+ )
+
+ self.MyAddShape(
+ CompositeShape(self),
+ 100, 260, wx.BLACK_PEN, wx.RED_BRUSH, "Composite"
+ )
+
self.MyAddShape(
ogl.CircleShape(80),
100, 100, wx.Pen(wx.BLUE, 3), wx.GREEN_BRUSH, "Circle"
)
+ self.MyAddShape(
+ ogl.TextShape(45, 30),
+ 205, 60, wx.GREEN_PEN, wx.LIGHT_GREY_BRUSH, "Text"
+ )
+
self.MyAddShape(
ogl.RectangleShape(85, 50),
305, 60, wx.BLACK_PEN, wx.LIGHT_GREY_BRUSH, "Rectangle"
)
ds = self.MyAddShape(
- DividedShape(140, 150, self),
- 495, 145, wx.BLACK_PEN, dsBrush, ''
- )
+ DividedShape(140, 150, self),
+ 515, 145, wx.BLACK_PEN, dsBrush, ''
+ )
self.MyAddShape(
DiamondShape(90, 90),
- 345, 235, wx.Pen(wx.BLUE, 3, wx.DOT), wx.RED_BRUSH, "Polygon"
+ 445, 305, wx.Pen(wx.BLUE, 3, wx.DOT), wx.RED_BRUSH, "Polygon"
)
self.MyAddShape(
- RoundedRectangleShape(95,70),
- 140, 255, wx.Pen(wx.RED, 2), rRectBrush, "Rounded Rect"
+ RoundedRectangleShape(95, 70),
+ 345, 145, wx.Pen(wx.RED, 2), rRectBrush, "Rounded Rect"
)
bmp = images.getTest2Bitmap()
s = ogl.BitmapShape()
s.SetBitmap(bmp)
- self.MyAddShape(s, 225, 150, None, None, "Bitmap")
+ self.MyAddShape(s, 225, 130, None, None, "Bitmap")
dc = wx.ClientDC(self)
self.PrepareDC(dc)
self.diagram.AddShape(line)
line.Show(True)
- # for some reason, the shapes have to be moved for the line to show up...
- fromShape.Move(dc, fromShape.GetX(), fromShape.GetY())
-
- self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
-
def MyAddShape(self, shape, x, y, pen, brush, text):
- shape.SetDraggable(True, True)
+ # Composites have to be moved for all children to get in place
+ if isinstance(shape, ogl.CompositeShape):
+ dc = wx.ClientDC(self)
+ self.PrepareDC(dc)
+ shape.Move(dc, x, y)
+ else:
+ shape.SetDraggable(True, True)
shape.SetCanvas(self)
shape.SetX(x)
shape.SetY(y)
return shape
- def OnDestroy(self, evt):
- # Do some cleanup
- for shape in self.diagram.GetShapeList():
- if shape.GetParent() == None:
- shape.SetCanvas(None)
- shape.Destroy()
-
- self.diagram.Destroy()
-
-
def OnBeginDragLeft(self, x, y, keys):
self.log.write("OnBeginDragLeft: %s, %s, %s\n" % (x, y, keys))
#----------------------------------------------------------------------
-# The OGL library holds some resources that need to be freed before
-# the app shuts down.
-class __Cleanup:
- def __del__(self, cleanup=ogl.OGLCleanUp):
- cleanup()
-# When this module gets cleaned up by Python then __cu will be cleaned
-# up and it's __dell__ is called, which will then call ogl.OGLCleanUp.
-__cu = __Cleanup()
+overview = """<html><body>
+<h2>Object Graphics Library</h2>
-
-overview = """\
The Object Graphics Library is a library supporting the creation and
manipulation of simple and complex graphic images on a canvas.
+<p>The OGL library was originally written in C++ and provided to
+wxPython via an extension module wrapper as is most of the rest of
+wxPython. The code has now been ported to Python (with many thanks to
+Pierre Hjälm!) in order to make it be more easily maintainable and
+less likely to get rusty because nobody cares about the C++ lib any
+more.
+
+<p>The Python version should be mostly drop-in compatible with the
+wrapped C++ version, except for the location of the package
+(wx.lib.ogl instead of wx.ogl) and that the base class methods are
+called the normal Python way (superclass.Method(self, ...)) instead of the
+hacky way that had to be done to support overloaded methods with the
+old SWIG (self.base_Method(...))
+
+
"""
if __name__ == '__main__':
- import sys,os
+ import sys, os
import run
run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:])
wxPython/wx/lib/editor
wxPython/wx/lib/masked
wxPython/wx/lib/mixins
+wxPython/wx/lib/ogl
wxPython/wx/py
wxPython/wx/py/tests
wxPython/wxPython
Source: "wx\lib\editor\*.py"; DestDir: "{app}\wx\lib\editor"; Components: core
Source: "wx\lib\editor\*.txt"; DestDir: "{app}\wx\lib\editor"; Components: core
Source: "wx\lib\mixins\*.py"; DestDir: "{app}\wx\lib\mixins"; Components: core
+Source: "wx\lib\masked\*.py"; DestDir: "{app}\wx\lib\masked"; Components: core
+Source: "wx\lib\ogl\*.py"; DestDir: "{app}\wx\lib\ogl"; Components: core
Source: "wx\py\*.py"; DestDir: "{app}\wx\py"; Components: core
Source: "wx\py\*.txt"; DestDir: "{app}\wx\py"; Components: core
Source: "wx\py\*.ico"; DestDir: "{app}\wx\py"; Components: core
Switched gizmos.TreeListCtrl to the newer version of the code from the
wxCode project.
+OGL is dead! LONG LIVE OGL! (Oops, sorry. A bit of my dramatic side
+leaked out there...) The wx.ogl module has been deprecated in favor
+of the new Python port of the OGL library located at wx.lib.ogl
+contributed by Pierre Hjälm. This will hopefully greatly extend the
+life of OGL within wxPython by making it more easily maintainable and
+less prone to getting rusty as there seems to be less and less
+interest in maintaining the C++ version. At this point there are just
+a couple minor known compatibility differences, please see the
+MigrationGuide_ file for details.
+
+.. _MigrationGuide: MigrationGuide.html
============================
This document will help explain some of the major changes in wxPython
-2.5 and let you know what you need to do to adapt your programs to
-those changes. Be sure to also check in the CHANGES_ file like
-usual to see info about the not so major changes and other things that
-have been added to wxPython.
+2.5 since the 2.4 series and let you know what you need to do to adapt
+your programs to those changes. Be sure to also check in the CHANGES_
+file like usual to see info about the not so major changes and other
+things that have been added to wxPython.
.. _CHANGES: CHANGES.html
.. _here: http://www.wxwidgets.org/name.htm
This won't really affect wxPython all that much, other than the fact
-that the wxwindows.org domain name will be changing to wxwidgets.org,
-so mail list, CVS, and etc. addresses will be changing. We're going
+that the wxwindows.org domain name has changed to wxwidgets.org,
+so mail list, CVS, and etc. addresses have also changed. We're going
to try and smooth the transition as much as possible, but I wanted you
all to be aware of this change if you run into any issues.
+OGL is dead! LONG LIVE OGL!
+---------------------------
+
+The wx.ogl module has been deprecated in favor of the new Python port
+of the OGL library located at wx.lib.ogl contributed by Pierre Hjälm.
+This will hopefully greatly extend the life of OGL within wxPython by
+making it more easily maintainable and less prone to getting rusty as
+there seems to be less and less interest in maintaining the C++
+version.
+
+There are only a few known compatibility issues at this time. First
+is the location of OGL. The deprecated version is located in the
+wx.ogl module, and the new version is in the wx.lib.ogl package. So
+this just means that to start using the new version you need to adjust
+your imports. So if your code currently has something like this::
+
+ import wx
+ import wx.ogl as ogl
+
+Then just change it to this::
+
+ import wx
+ import wx.lib.ogl as ogl
+
+
+
+
+
Obsolete Modules
----------------
'wx.lib.editor',
'wx.lib.masked',
'wx.lib.mixins',
+ 'wx.lib.ogl',
'wx.py',
'wx.tools',
'wx.tools.XRCed',
--- /dev/null
+"""
+The Object Graphics Library provides for simple drawing and manipulation
+of 2D objects.
+"""
+
+__all__ = ["basic", "diagram", "canvas", "lines", "bmpshape", "divided", "composit"]
+
+from basic import *
+from diagram import *
+from canvas import *
+from lines import *
+from bmpshape import *
+from divided import *
+from composit import *
--- /dev/null
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name: basic.py
+# Purpose: The basic OGL shapes
+#
+# Author: Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created: 20040508
+# RCS-ID:
+# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# Licence: wxWindows license
+#----------------------------------------------------------------------------
+
+from __future__ import division
+
+import wx
+from math import pi, sqrt, atan, sin, cos
+
+from oglmisc import *
+
+DragOffsetX = 0.0
+DragOffsetY = 0.0
+
+
+def OGLInitialize():
+ global WhiteBackgroundPen, WhiteBackgroundBrush, TransparentPen
+ global BlackForegroundPen, NormalFont
+
+ WhiteBackgroundPen = wx.Pen(wx.WHITE, 1, wx.SOLID)
+ WhiteBackgroundBrush = wx.Brush(wx.WHITE, wx.SOLID)
+
+ TransparentPen = wx.Pen(wx.WHITE, 1, wx.TRANSPARENT)
+ BlackForegroundPen = wx.Pen(wx.BLACK, 1, wx.SOLID)
+
+ NormalFont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL)
+
+
+def OGLCleanUp():
+ pass
+
+
+class ShapeTextLine(object):
+ def __init__(self, the_x, the_y, the_line):
+ self._x = the_x
+ self._y = the_y
+ self._line = the_line
+
+ def GetX(self):
+ return self._x
+
+ def GetY(self):
+ return self._y
+
+ def SetX(self, x):
+ self._x = x
+
+ def SetY(self, y):
+ self._y = y
+
+ def SetText(self, text):
+ self._line = text
+
+ def GetText(self):
+ return self._line
+
+
+
+class ShapeEvtHandler(object):
+ def __init__(self, prev = None, shape = None):
+ self._previousHandler = prev
+ self._handlerShape = shape
+
+ def __del__(self):
+ pass
+
+ def SetShape(self, sh):
+ self._handlerShape = sh
+
+ def GetShape(self):
+ return self._handlerShape
+
+ def SetPreviousHandler(self, handler):
+ self._previousHandler = handler
+
+ def GetPreviousHandler(self):
+ return self._previousHandler
+
+ def OnDraw(self, dc):
+ if self._previousHandler:
+ self._previousHandler.OnDraw(dc)
+
+ def OnMoveLinks(self, dc):
+ if self._previousHandler:
+ self._previousHandler.OnMoveLinks(dc)
+
+ def OnMoveLink(self, dc, moveControlPoints = True):
+ if self._previousHandler:
+ self._previousHandler.OnMoveLink(dc, moveControlPoints)
+
+ def OnDrawContents(self, dc):
+ if self._previousHandler:
+ self._previousHandler.OnDrawContents(dc)
+
+ def OnDrawBranches(self, dc, erase = False):
+ if self._previousHandler:
+ self._previousHandler.OnDrawBranches(dc, erase = erase)
+
+ def OnSize(self, x, y):
+ if self._previousHandler:
+ self._previousHandler.OnSize(x, y)
+
+ def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
+ if self._previousHandler:
+ return self._previousHandler.OnMovePre(dc, x, y, old_x, old_y, display)
+ else:
+ return True
+
+ def OnMovePost(self, dc, x, y, old_x, old_y, display = True):
+ if self._previousHandler:
+ return self._previousHandler.OnMovePost(dc, x, y, old_x, old_y, display)
+ else:
+ return True
+
+ def OnErase(self, dc):
+ if self._previousHandler:
+ self._previousHandler.OnErase(dc)
+
+ def OnEraseContents(self, dc):
+ if self._previousHandler:
+ self._previousHandler.OnEraseContents(dc)
+
+ def OnHighlight(self, dc):
+ if self._previousHandler:
+ self._previousHandler.OnHighlight(dc)
+
+ def OnLeftClick(self, x, y, keys, attachment):
+ if self._previousHandler:
+ self._previousHandler.OnLeftClick(x, y, keys, attachment)
+
+ def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnLeftDoubleClick(x, y, keys, attachment)
+
+ def OnRightClick(self, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnRightClick(x, y, keys, attachment)
+
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnDragLeft(draw, x, y, keys, attachment)
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
+
+ def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnDragRight(draw, x, y, keys, attachment)
+
+ def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnBeginDragRight(x, y, keys, attachment)
+
+ def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnEndDragRight(x, y, keys, attachment)
+
+ # Control points ('handles') redirect control to the actual shape,
+ # to make it easier to override sizing behaviour.
+ def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnSizingDragLeft(pt, draw, x, y, keys, attachment)
+
+ def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnSizingBeginDragLeft(pt, x, y, keys, attachment)
+
+ def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
+ if self._previousHandler:
+ self._previousHandler.OnSizingEndDragLeft(pt, x, y, keys, attachment)
+
+ def OnBeginSize(self, w, h):
+ pass
+
+ def OnEndSize(self, w, h):
+ pass
+
+ def OnDrawOutline(self, dc, x, y, w, h):
+ if self._previousHandler:
+ self._previousHandler.OnDrawOutline(dc, x, y, w, h)
+
+ def OnDrawControlPoints(self, dc):
+ if self._previousHandler:
+ self._previousHandler.OnDrawControlPoints(dc)
+
+ def OnEraseControlPoints(self, dc):
+ if self._previousHandler:
+ self._previousHandler.OnEraseControlPoints(dc)
+
+ # Can override this to prevent or intercept line reordering.
+ def OnChangeAttachment(self, attachment, line, ordering):
+ if self._previousHandler:
+ self._previousHandler.OnChangeAttachment(attachment, line, ordering)
+
+
+
+class Shape(ShapeEvtHandler):
+ """OGL base class
+
+ Shape(canvas = None)
+
+ The wxShape is the top-level, abstract object that all other objects
+ are derived from. All common functionality is represented by wxShape's
+ members, and overriden members that appear in derived classes and have
+ behaviour as documented for wxShape, are not documented separately.
+ """
+
+ GraphicsInSizeToContents = False
+
+ def __init__(self, canvas = None):
+ ShapeEvtHandler.__init__(self)
+
+ self._eventHandler = self
+ self.SetShape(self)
+ self._id = 0
+ self._formatted = False
+ self._canvas = canvas
+ self._xpos = 0.0
+ self._ypos = 0.0
+ self._pen = wx.Pen(wx.BLACK, 1, wx.SOLID)
+ self._brush = wx.WHITE_BRUSH
+ self._font = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL)
+ self._textColour = wx.BLACK
+ self._textColourName = wx.BLACK
+ self._visible = False
+ self._selected = False
+ self._attachmentMode = ATTACHMENT_MODE_NONE
+ self._spaceAttachments = True
+ self._disableLabel = False
+ self._fixedWidth = False
+ self._fixedHeight = False
+ self._drawHandles = True
+ self._sensitivity = OP_ALL
+ self._draggable = True
+ self._parent = None
+ self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
+ self._shadowMode = SHADOW_NONE
+ self._shadowOffsetX = 6
+ self._shadowOffsetY = 6
+ self._shadowBrush = wx.BLACK_BRUSH
+ self._textMarginX = 5
+ self._textMarginY = 5
+ self._regionName="0"
+ self._centreResize = True
+ self._maintainAspectRatio = False
+ self._highlighted = False
+ self._rotation = 0.0
+ self._branchNeckLength = 10
+ self._branchStemLength = 10
+ self._branchSpacing = 10
+ self._branchStyle = BRANCHING_ATTACHMENT_NORMAL
+
+ self._regions = []
+ self._lines = []
+ self._controlPoints = []
+ self._attachmentPoints = []
+ self._text = []
+ self._children = []
+
+ # Set up a default region. Much of the above will be put into
+ # the region eventually (the duplication is for compatibility)
+ region = ShapeRegion()
+ region.SetName("0")
+ region.SetFont(wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL))
+ region.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT)
+ region.SetColour("BLACK")
+ self._regions.append(region)
+
+ def __str__(self):
+ return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__)
+
+ def GetClassName(self):
+ return str(self.__class__).split(".")[-1][:-2]
+
+ def __del__(self):
+ if self._parent:
+ i = self._parent.GetChildren().index(self)
+ self._parent.GetChildren(i).remove(self)
+
+ self.ClearText()
+ self.ClearRegions()
+ self.ClearAttachments()
+
+ if self._canvas:
+ self._canvas.RemoveShape(self)
+
+ self.GetEventHandler().OnDelete()
+
+ def Draggable(self):
+ """TRUE if the shape may be dragged by the user."""
+ return True
+
+ def SetShape(self, sh):
+ self._handlerShape = sh
+
+ def GetCanvas(self):
+ """Get the internal canvas."""
+ return self._canvas
+
+ def GetBranchStyle(self):
+ return self._branchStyle
+
+ def GetRotation(self):
+ """Return the angle of rotation in radians."""
+ return self._rotation
+
+ def SetRotation(self, rotation):
+ self._rotation = rotation
+
+ def SetHighlight(self, hi, recurse = False):
+ """Set the highlight for a shape. Shape highlighting is unimplemented."""
+ self._highlighted = hi
+ if recurse:
+ for shape in self._children:
+ shape.SetHighlight(hi, recurse)
+
+ def SetSensitivityFilter(self, sens = OP_ALL, recursive = False):
+ """Set the shape to be sensitive or insensitive to specific mouse
+ operations.
+
+ sens is a bitlist of the following:
+
+ * OP_CLICK_LEFT
+ * OP_CLICK_RIGHT
+ * OP_DRAG_LEFT
+ * OP_DRAG_RIGHT
+ * OP_ALL (equivalent to a combination of all the above).
+ """
+ self._draggable = sens & OP_DRAG_LEFT
+
+ self._sensitivity = sens
+ if recursive:
+ for shape in self._children:
+ shape.SetSensitivityFilter(sens, True)
+
+ def SetDraggable(self, drag, recursive = False):
+ """Set the shape to be draggable or not draggable."""
+ self._draggable = drag
+ if drag:
+ self._sensitivity |= OP_DRAG_LEFT
+ elif self._sensitivity & OP_DRAG_LEFT:
+ self._sensitivity -= OP_DRAG_LEFT
+
+ if recursive:
+ for shape in self._children:
+ shape.SetDraggable(drag, True)
+
+ def SetDrawHandles(self, drawH):
+ """Set the drawHandles flag for this shape and all descendants.
+ If drawH is TRUE (the default), any handles (control points) will
+ be drawn. Otherwise, the handles will not be drawn.
+ """
+ self._drawHandles = drawH
+ for shape in self._children:
+ shape.SetDrawHandles(drawH)
+
+ def SetShadowMode(self, mode, redraw = False):
+ """Set the shadow mode (whether a shadow is drawn or not).
+ mode can be one of the following:
+
+ SHADOW_NONE
+ No shadow (the default).
+ SHADOW_LEFT
+ Shadow on the left side.
+ SHADOW_RIGHT
+ Shadow on the right side.
+ """
+ if redraw and self.GetCanvas():
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+ self.Erase(dc)
+ self._shadowMode = mode
+ self.Draw(dc)
+ else:
+ self._shadowMode = mode
+
+ def SetCanvas(self, theCanvas):
+ """Identical to Shape.Attach."""
+ self._canvas = theCanvas
+ for shape in self._children:
+ shape.SetCanvas(theCanvas)
+
+ def AddToCanvas(self, theCanvas, addAfter = None):
+ """Add the shape to the canvas's shape list.
+ If addAfter is non-NULL, will add the shape after this one.
+ """
+ theCanvas.AddShape(self, addAfter)
+
+ lastImage = self
+ for object in self._children:
+ object.AddToCanvas(theCanvas, lastImage)
+ lastImage = object
+
+ def InsertInCanvas(self, theCanvas):
+ """Insert the shape at the front of the shape list of canvas."""
+ theCanvas.InsertShape(self)
+
+ lastImage = self
+ for object in self._children:
+ object.AddToCanvas(theCanvas, lastImage)
+ lastImage = object
+
+ def RemoveFromCanvas(self, theCanvas):
+ """Remove the shape from the canvas."""
+ if self.Selected():
+ self.Select(False)
+ theCanvas.RemoveShape(self)
+ for object in self._children:
+ object.RemoveFromCanvas(theCanvas)
+
+ def ClearAttachments(self):
+ """Clear internal custom attachment point shapes (of class
+ wxAttachmentPoint).
+ """
+ self._attachmentPoints = []
+
+ def ClearText(self, regionId = 0):
+ """Clear the text from the specified text region."""
+ if regionId == 0:
+ self._text=""
+ if regionId<len(self._regions):
+ self._regions[regionId].ClearText()
+
+ def ClearRegions(self):
+ """Clear the ShapeRegions from the shape."""
+ self._regions = []
+
+ def AddRegion(self, region):
+ """Add a region to the shape."""
+ self._regions.append(region)
+
+ def SetDefaultRegionSize(self):
+ """Set the default region to be consistent with the shape size."""
+ if not self._regions:
+ return
+ w, h = self.GetBoundingBoxMax()
+ self._regions[0].SetSize(w, h)
+
+ def HitTest(self, x, y):
+ """Given a point on a canvas, returns TRUE if the point was on the
+ shape, and returns the nearest attachment point and distance from
+ the given point and target.
+ """
+ width, height = self.GetBoundingBoxMax()
+ if abs(width)<4:
+ width = 4.0
+ if abs(height)<4:
+ height = 4.0
+
+ width += 4 # Allowance for inaccurate mousing
+ height += 4
+
+ left = self._xpos - (width / 2)
+ top = self._ypos - (height / 2)
+ right = self._xpos + (width / 2)
+ bottom = self._ypos + (height / 2)
+
+ nearest_attachment = 0
+
+ # If within the bounding box, check the attachment points
+ # within the object.
+ if x >= left and x <= right and y >= top and y <= bottom:
+ n = self.GetNumberOfAttachments()
+ nearest = 999999
+
+ # GetAttachmentPosition[Edge] takes a logical attachment position,
+ # i.e. if it's rotated through 90%, position 0 is East-facing.
+
+ for i in range(n):
+ e = self.GetAttachmentPositionEdge(i)
+ if e:
+ xp, yp = e
+ l = sqrt(((xp - x) * (xp - x)) + (yp - y) * (yp - y))
+ if l<nearest:
+ nearest = l
+ nearest_attachment = i
+
+ return nearest_attachment, nearest
+ return False
+
+ # Format a text string according to the region size, adding
+ # strings with positions to region text list
+
+ def FormatText(self, dc, s, i = 0):
+ """Reformat the given text region; defaults to formatting the
+ default region.
+ """
+ self.ClearText(i)
+
+ if not self._regions:
+ return
+
+ if i>len(self._regions):
+ return
+
+ region = self._regions[i]
+ region._regionText = s
+ dc.SetFont(region.GetFont())
+
+ w, h = region.GetSize()
+
+ stringList = FormatText(dc, s, (w - 2 * self._textMarginX), (h - 2 * self._textMarginY), region.GetFormatMode())
+ for s in stringList:
+ line = ShapeTextLine(0.0, 0.0, s)
+ region.GetFormattedText().append(line)
+
+ actualW = w
+ actualH = h
+ # Don't try to resize an object with more than one image (this
+ # case should be dealt with by overriden handlers)
+ if (region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS) and \
+ region.GetFormattedText().GetCount() and \
+ len(self._regions) == 1 and \
+ not Shape.GraphicsInSizeToContents:
+
+ actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText())
+ if actualW + 2 * self._textMarginX != w or actualH + 2 * self._textMarginY != h:
+ # If we are a descendant of a composite, must make sure
+ # the composite gets resized properly
+
+ topAncestor = self.GetTopAncestor()
+ if topAncestor != self:
+ Shape.GraphicsInSizeToContents = True
+
+ composite = topAncestor
+ composite.Erase(dc)
+ self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY)
+ self.Move(dc, self._xpos, self._ypos)
+ composite.CalculateSize()
+ if composite.Selected():
+ composite.DeleteControlPoints(dc)
+ composite.MakeControlPoints()
+ composite.MakeMandatoryControlPoints()
+ # Where infinite recursion might happen if we didn't stop it
+ composite.Draw(dc)
+ Shape.GraphicsInSizeToContents = False
+ else:
+ self.Erase(dc)
+
+ self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY)
+ self.Move(dc, self._xpos, self._ypos)
+ self.EraseContents(dc)
+ CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW - 2 * self._textMarginX, actualH - 2 * self._textMarginY, region.GetFormatMode())
+ self._formatted = True
+
+ def Recentre(self, dc):
+ """Do recentring (or other formatting) for all the text regions
+ for this shape.
+ """
+ w, h = self.GetBoundingBoxMin()
+ for region in self._regions:
+ CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, w - 2 * self._textMarginX, h - 2 * self._textMarginY, region.GetFormatMode())
+
+ def GetPerimeterPoint(self, x1, y1, x2, y2):
+ """Get the point at which the line from (x1, y1) to (x2, y2) hits
+ the shape. Returns False if the line doesn't hit the perimeter.
+ """
+ return False
+
+ def SetPen(self, the_pen):
+ """Set the pen for drawing the shape's outline."""
+ self._pen = the_pen
+
+ def SetBrush(self, the_brush):
+ """Set the brush for filling the shape's shape."""
+ self._brush = the_brush
+
+ # Get the top - most (non-division) ancestor, or self
+ def GetTopAncestor(self):
+ """Return the top-most ancestor of this shape (the root of
+ the composite).
+ """
+ if not self.GetParent():
+ return self
+
+ if isinstance(self.GetParent(), DivisionShape):
+ return self
+ return self.GetParent().GetTopAncestor()
+
+ # Region functions
+ def SetFont(self, the_font, regionId = 0):
+ """Set the font for the specified text region."""
+ self._font = the_font
+ if regionId<len(self._regions):
+ self._regions[regionId].SetFont(the_font)
+
+ def GetFont(self, regionId = 0):
+ """Get the font for the specified text region."""
+ if regionId >= len(self._regions):
+ return None
+ return self._regions[regionId].GetFont()
+
+ def SetFormatMode(self, mode, regionId = 0):
+ """Set the format mode of the default text region. The argument
+ can be a bit list of the following:
+
+ FORMAT_NONE
+ No formatting.
+ FORMAT_CENTRE_HORIZ
+ Horizontal centring.
+ FORMAT_CENTRE_VERT
+ Vertical centring.
+ """
+ if regionId<len(self._regions):
+ self._regions[regionId].SetFormatMode(mode)
+
+ def GetFormatMode(self, regionId = 0):
+ if regionId >= len(self._regions):
+ return 0
+ return self._regions[regionId].GetFormatMode()
+
+ def SetTextColour(self, the_colour, regionId = 0):
+ """Set the colour for the specified text region."""
+ self._textColour = wx.TheColourDatabase.Find(the_colour)
+ self._textColourName = the_colour
+
+ if regionId<len(self._regions):
+ self._regions[regionId].SetColour(the_colour)
+
+ def GetTextColour(self, regionId = 0):
+ """Get the colour for the specified text region."""
+ if regionId >= len(self._regions):
+ return ""
+ return self._regions[regionId].GetTextColour()
+
+ def SetRegionName(self, name, regionId = 0):
+ """Set the name for this region.
+ The name for a region is unique within the scope of the whole
+ composite, whereas a region id is unique only for a single image.
+ """
+ if regionId<len(self._regions):
+ self._regions[regionId].SetName(name)
+
+ def GetRegionName(self, regionId = 0):
+ """Get the region's name.
+ A region's name can be used to uniquely determine a region within
+ an entire composite image hierarchy. See also Shape.SetRegionName.
+ """
+ if regionId >= len(self._regions):
+ return ""
+ return self._regions[regionId].GetName()
+
+ def GetRegionId(self, name):
+ """Get the region's identifier by name.
+ This is not unique for within an entire composite, but is unique
+ for the image.
+ """
+ for i, r in enumerate(self._regions):
+ if r.GetName() == name:
+ return i
+ return -1
+
+ # Name all _regions in all subimages recursively
+ def NameRegions(self, parentName=""):
+ """Make unique names for all the regions in a shape or composite shape."""
+ n = self.GetNumberOfTextRegions()
+ for i in range(n):
+ if parentName:
+ buff = parentName+"."+str(i)
+ else:
+ buff = str(i)
+ self.SetRegionName(buff, i)
+
+ for j, child in enumerate(self._children):
+ if parentName:
+ buff = parentName+"."+str(j)
+ else:
+ buff = str(j)
+ child.NameRegions(buff)
+
+ # Get a region by name, possibly looking recursively into composites
+ def FindRegion(self, name):
+ """Find the actual image ('this' if non-composite) and region id
+ for the given region name.
+ """
+ id = self.GetRegionId(name)
+ if id>-1:
+ return self, id
+
+ for child in self._children:
+ actualImage, regionId = child.FindRegion(name)
+ if actualImage:
+ return actualImage, regionId
+
+ return None,-1
+
+ # Finds all region names for this image (composite or simple).
+ def FindRegionNames(self):
+ """Get a list of all region names for this image (composite or simple)."""
+ list = []
+ n = self.GetNumberOfTextRegions()
+ for i in range(n):
+ list.append(self.GetRegionName(i))
+
+ for child in self._children:
+ list += child.FindRegionNames()
+
+ return list
+
+ def AssignNewIds(self):
+ """Assign new ids to this image and its children."""
+ self._id = wx.NewId()
+ for child in self._children:
+ child.AssignNewIds()
+
+ def OnDraw(self, dc):
+ pass
+
+ def OnMoveLinks(self, dc):
+ # Want to set the ends of all attached links
+ # to point to / from this object
+
+ for line in self._lines:
+ line.GetEventHandler().OnMoveLink(dc)
+
+ def OnDrawContents(self, dc):
+ if not self._regions:
+ return
+
+ bound_x, bound_y = self.GetBoundingBoxMin()
+
+ if self._pen:
+ dc.SetPen(self._pen)
+
+ region = self._regions[0]
+ if region.GetFont():
+ dc.SetFont(region.GetFont())
+
+ dc.SetTextForeground(region.GetActualColourObject())
+ dc.SetBackgroundMode(wx.TRANSPARENT)
+ if not self._formatted:
+ CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode())
+ self._formatted = True
+
+ if not self.GetDisableLabel():
+ DrawFormattedText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode())
+
+
+ def DrawContents(self, dc):
+ """Draw the internal graphic of the shape (such as text).
+
+ Do not override this function: override OnDrawContents, which
+ is called by this function.
+ """
+ self.GetEventHandler().OnDrawContents(dc)
+
+ def OnSize(self, x, y):
+ pass
+
+ def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
+ return True
+
+ def OnErase(self, dc):
+ if not self._visible:
+ return
+
+ # Erase links
+ for line in self._lines:
+ line.GetEventHandler().OnErase(dc)
+
+ self.GetEventHandler().OnEraseContents(dc)
+
+ def OnEraseContents(self, dc):
+ if not self._visible:
+ return
+
+ xp, yp = self.GetX(), self.GetY()
+ minX, minY = self.GetBoundingBoxMin()
+ maxX, maxY = self.GetBoundingBoxMax()
+
+ topLeftX = xp - maxX / 2 - 2
+ topLeftY = yp - maxY / 2 - 2
+
+ penWidth = 0
+ if self._pen:
+ penWidth = self._pen.GetWidth()
+
+ dc.SetPen(self.GetBackgroundPen())
+ dc.SetBrush(self.GetBackgroundBrush())
+
+ dc.DrawRectangle(topLeftX - penWidth, topLeftY - penWidth, maxX + penWidth * 2 + 4, maxY + penWidth * 2 + 4)
+
+ def EraseLinks(self, dc, attachment=-1, recurse = False):
+ """Erase links attached to this shape, but do not repair damage
+ caused to other shapes.
+ """
+ if not self._visible:
+ return
+
+ for line in self._lines:
+ if attachment==-1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment):
+ line.GetEventHandler().OnErase(dc)
+
+ if recurse:
+ for child in self._children:
+ child.EraseLinks(dc, attachment, recurse)
+
+ def DrawLinks(self, dc, attachment=-1, recurse = False):
+ """Draws any lines linked to this shape."""
+ if not self._visible:
+ return
+
+ for line in self._lines:
+ if attachment==-1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment):
+ line.GetEventHandler().Draw(dc)
+
+ if recurse:
+ for child in self._children:
+ child.DrawLinks(dc, attachment, recurse)
+
+ # Returns TRUE if pt1 <= pt2 in the sense that one point comes before
+ # another on an edge of the shape.
+ # attachmentPoint is the attachment point (= side) in question.
+
+ # This is the default, rectangular implementation.
+ def AttachmentSortTest(self, attachmentPoint, pt1, pt2):
+ """Return TRUE if pt1 is less than or equal to pt2, in the sense
+ that one point comes before another on an edge of the shape.
+
+ attachment is the attachment point (side) in question.
+
+ This function is used in Shape.MoveLineToNewAttachment to determine
+ the new line ordering.
+ """
+ physicalAttachment = self.LogicalToPhysicalAttachment(attachmentPoint)
+ if physicalAttachment in [0, 2]:
+ return pt1.x <= pt2.x
+ elif physicalAttachment in [1, 3]:
+ return pt1.y <= pt2.y
+
+ return False
+
+ def MoveLineToNewAttachment(self, dc, to_move, x, y):
+ """Move the given line (which must already be attached to the shape)
+ to a different attachment point on the shape, or a different order
+ on the same attachment.
+
+ Calls Shape.AttachmentSortTest and then
+ ShapeEvtHandler.OnChangeAttachment.
+ """
+ if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
+ return False
+
+ # Is (x, y) on this object? If so, find the new attachment point
+ # the user has moved the point to
+ hit = self.HitTest(x, y)
+ if not hit:
+ return False
+
+ newAttachment, distance = hit
+
+ self.EraseLinks(dc)
+
+ if to_move.GetTo() == self:
+ oldAttachment = to_move.GetAttachmentTo()
+ else:
+ oldAttachment = to_move.GetAttachmentFrom()
+
+ # The links in a new ordering
+ # First, add all links to the new list
+ newOrdering = self._lines[:]
+
+ # Delete the line object from the list of links; we're going to move
+ # it to another position in the list
+ del newOrdering[newOrdering.index(to_move)]
+
+ old_x=-99999.9
+ old_y=-99999.9
+
+ found = False
+
+ for line in newOrdering:
+ if line.GetTo() == self and oldAttachment == line.GetAttachmentTo() or \
+ line.GetFrom() == self and oldAttachment == line.GetAttachmentFrom():
+ startX, startY, endX, endY = line.GetEnds()
+ if line.GetTo() == self:
+ xp = endX
+ yp = endY
+ else:
+ xp = startX
+ yp = startY
+
+ thisPoint = wx.RealPoint(xp, yp)
+ lastPoint = wx.RealPoint(old_x, old_y)
+ newPoint = wx.RealPoint(x, y)
+
+ if self.AttachmentSortTest(newAttachment, newPoint, thisPoint) and self.AttachmentSortTest(newAttachment, lastPoint, newPoint):
+ found = True
+ newOrdering.insert(newOrdering.index(line), to_move)
+
+ old_x = xp
+ old_y = yp
+ if found:
+ break
+
+ if not found:
+ newOrdering.append(to_move)
+
+ self.GetEventHandler().OnChangeAttachment(newAttachment, to_move, newOrdering)
+ return True
+
+ def OnChangeAttachment(self, attachment, line, ordering):
+ if line.GetTo() == self:
+ line.SetAttachmentTo(attachment)
+ else:
+ line.SetAttachmentFrom(attachment)
+
+ self.ApplyAttachmentOrdering(ordering)
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+ self.MoveLinks(dc)
+
+ if not self.GetCanvas().GetQuickEditMode():
+ self.GetCanvas().Redraw(dc)
+
+ # Reorders the lines according to the given list
+ def ApplyAttachmentOrdering(self, linesToSort):
+ """Apply the line ordering in linesToSort to the shape, to reorder
+ the way lines are attached.
+ """
+ linesStore = self._lines[:]
+
+ self._lines = []
+
+ for line in linesToSort:
+ if line in linesStore:
+ del linesStore[linesStore.index(line)]
+ self._lines.append(line)
+
+ # Now add any lines that haven't been listed in linesToSort
+ self._lines += linesStore
+
+ def SortLines(self, attachment, linesToSort):
+ """ Reorder the lines coming into the node image at this attachment
+ position, in the order in which they appear in linesToSort.
+
+ Any remaining lines not in the list will be added to the end.
+ """
+ # This is a temporary store of all the lines at this attachment
+ # point. We'll tick them off as we've processed them.
+ linesAtThisAttachment = []
+
+ for line in self._lines[:]:
+ if line.GetTo() == self and line.GetAttachmentTo() == attachment or \
+ line.GetFrom() == self and line.GetAttachmentFrom() == attachment:
+ linesAtThisAttachment.append(line)
+ del self._lines[self._lines.index(line)]
+
+ for line in linesToSort:
+ if line in linesAtThisAttachment:
+ # Done this one
+ del linesAtThisAttachment[linesAtThisAttachment.index(line)]
+ self._lines.Append(line)
+
+ # Now add any lines that haven't been listed in linesToSort
+ self._lines += linesAtThisAttachment
+
+ def OnHighlight(self, dc):
+ pass
+
+ def OnLeftClick(self, x, y, keys = 0, attachment = 0):
+ if self._sensitivity & OP_CLICK_LEFT != OP_CLICK_LEFT:
+ if self._parent:
+ attachment, dist = self._parent.HitTest(x, y)
+ self._parent.GetEventHandler().OnLeftClick(x, y, keys, attachment)
+
+ def OnRightClick(self, x, y, keys = 0, attachment = 0):
+ if self._sensitivity & OP_CLICK_RIGHT != OP_CLICK_RIGHT:
+ attachment, dist = self._parent.HitTest(x, y)
+ self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment)
+
+ 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
+
+ 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)
+
+ xx = x + DragOffsetX
+ yy = y + DragOffsetY
+
+ xx, yy = self._canvas.Snap(xx, yy)
+ w, h = self.GetBoundingBoxMax()
+ self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ global DragOffsetX, DragOffsetY
+
+ 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
+
+ DragOffsetX = self._xpos - x
+ DragOffsetY = self._ypos - y
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ # New policy: don't erase shape until end of drag.
+ # self.Erase(dc)
+ xx = x + DragOffsetX
+ yy = y + DragOffsetY
+ xx, yy = self._canvas.Snap(xx, yy)
+ dc.SetLogicalFunction(OGLRBLF)
+
+ dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
+ dc.SetPen(dottedPen)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ w, h = self.GetBoundingBoxMax()
+ self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
+ self._canvas.CaptureMouse()
+
+ 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)
+ xx = x + DragOffsetX
+ yy = y + DragOffsetY
+ xx, yy = self._canvas.Snap(xx, yy)
+
+ # New policy: erase shape at end of drag.
+ self.Erase(dc)
+
+ self.Move(dc, xx, yy)
+ if self._canvas and not self._canvas.GetQuickEditMode():
+ self._canvas.Redraw(dc)
+
+ def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
+ if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
+ if self._parent:
+ attachment, dist = self._parent.HitTest(x, y)
+ self._parent.GetEventHandler().OnDragRight(draw, x, y, keys, attachment)
+ return
+
+ def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
+ if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
+ if self._parent:
+ attachment, dist = self._parent.HitTest(x, y)
+ self._parent.GetEventHandler().OnBeginDragRight(x, y, keys, attachment)
+ return
+
+ def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
+ if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
+ if self._parent:
+ attachment, dist = self._parent.HitTest(x, y)
+ self._parent.GetEventHandler().OnEndDragRight(x, y, keys, attachment)
+ return
+
+ def OnDrawOutline(self, dc, x, y, w, h):
+ points = [[x - w / 2, y - h / 2],
+ [x + w / 2, y - h / 2],
+ [x + w / 2, y + h / 2],
+ [x - w / 2, y + h / 2],
+ [x - w / 2, y - h / 2],
+ ]
+
+ #dc.DrawLines([[round(x), round(y)] for x, y in points])
+ dc.DrawLines(points)
+
+ def Attach(self, can):
+ """Set the shape's internal canvas pointer to point to the given canvas."""
+ self._canvas = can
+
+ def Detach(self):
+ """Disassociates the shape from its canvas."""
+ self._canvas = None
+
+ def Move(self, dc, x, y, display = True):
+ """Move the shape to the given position.
+ Redraw if display is TRUE.
+ """
+ old_x = self._xpos
+ old_y = self._ypos
+
+ if not self.GetEventHandler().OnMovePre(dc, x, y, old_x, old_y, display):
+ return
+
+ self._xpos, self._ypos = x, y
+
+ self.ResetControlPoints()
+
+ if display:
+ self.Draw(dc)
+
+ self.MoveLinks(dc)
+
+ self.GetEventHandler().OnMovePost(dc, x, y, old_x, old_y, display)
+
+ def MoveLinks(self, dc):
+ """Redraw all the lines attached to the shape."""
+ self.GetEventHandler().OnMoveLinks(dc)
+
+ def Draw(self, dc):
+ """Draw the whole shape and any lines attached to it.
+
+ Do not override this function: override OnDraw, which is called
+ by this function.
+ """
+ if self._visible:
+ self.GetEventHandler().OnDraw(dc)
+ self.GetEventHandler().OnDrawContents(dc)
+ self.GetEventHandler().OnDrawControlPoints(dc)
+ self.GetEventHandler().OnDrawBranches(dc)
+
+ def Flash(self):
+ """Flash the shape."""
+ if self.GetCanvas():
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas.PrepareDC(dc)
+
+ dc.SetLogicalFunction(OGLRBLF)
+ self.Draw(dc)
+ dc.SetLogicalFunction(wx.COPY)
+ self.Draw(dc)
+
+ def Show(self, show):
+ """Set a flag indicating whether the shape should be drawn."""
+ self._visible = show
+ for child in self._children:
+ child.Show(show)
+
+ def Erase(self, dc):
+ """Erase the shape.
+ Does not repair damage caused to other shapes.
+ """
+ self.GetEventHandler().OnErase(dc)
+ self.GetEventHandler().OnEraseControlPoints(dc)
+ self.GetEventHandler().OnDrawBranches(dc, erase = True)
+
+ def EraseContents(self, dc):
+ """Erase the shape contents, that is, the area within the shape's
+ minimum bounding box.
+ """
+ self.GetEventHandler().OnEraseContents(dc)
+
+ def AddText(self, string):
+ """Add a line of text to the shape's default text region."""
+ if not self._regions:
+ return
+
+ region = self._regions[0]
+ region.ClearText()
+ new_line = ShapeTextLine(0, 0, string)
+ text = region.GetFormattedText()
+ text.append(new_line)
+
+ self._formatted = False
+
+ def SetSize(self, x, y, recursive = True):
+ """Set the shape's size."""
+ self.SetAttachmentSize(x, y)
+ self.SetDefaultRegionSize()
+
+ def SetAttachmentSize(self, w, h):
+ width, height = self.GetBoundingBoxMin()
+ if width == 0:
+ scaleX = 1.0
+ else:
+ scaleX = long(w) / width
+ if height == 0:
+ scaleY = 1.0
+ else:
+ scaleY = long(h) / height
+
+ for point in self._attachmentPoints:
+ point._x = point._x * scaleX
+ point._y = point._y * scaleY
+
+ # Add line FROM this object
+ def AddLine(self, line, other, attachFrom = 0, attachTo = 0, positionFrom=-1, positionTo=-1):
+ """Add a line between this shape and the given other shape, at the
+ specified attachment points.
+
+ The position in the list of lines at each end can also be specified,
+ so that the line will be drawn at a particular point on its attachment
+ point.
+ """
+ if positionFrom==-1:
+ if not line in self._lines:
+ self._lines.append(line)
+ else:
+ # Don't preserve old ordering if we have new ordering instructions
+ try:
+ self._lines.remove(line)
+ except ValueError:
+ pass
+ if positionFrom<len(self._lines):
+ self._lines.insert(positionFrom, line)
+ else:
+ self._lines.append(line)
+
+ if positionTo==-1:
+ if not other in other._lines:
+ other._lines.append(line)
+ else:
+ # Don't preserve old ordering if we have new ordering instructions
+ try:
+ other._lines.remove(line)
+ except ValueError:
+ pass
+ if positionTo<len(other._lines):
+ other._lines.insert(positionTo, line)
+ else:
+ other._lines.append(line)
+
+ line.SetFrom(self)
+ line.SetTo(other)
+ line.SetAttachments(attachFrom, attachTo)
+
+ dc = wx.ClientDC(self._canvas)
+ self._canvas.PrepareDC(dc)
+ self.MoveLinks(dc)
+
+ def RemoveLine(self, line):
+ """Remove the given line from the shape's list of attached lines."""
+ if line.GetFrom() == self:
+ line.GetTo()._lines.remove(line)
+ else:
+ line.GetFrom()._lines.remove(line)
+
+ self._lines.remove(line)
+
+ # Default - make 6 control points
+ def MakeControlPoints(self):
+ """Make a list of control points (draggable handles) appropriate to
+ the shape.
+ """
+ maxX, maxY = self.GetBoundingBoxMax()
+ minX, minY = self.GetBoundingBoxMin()
+
+ widthMin = minX + CONTROL_POINT_SIZE + 2
+ heightMin = minY + CONTROL_POINT_SIZE + 2
+
+ # Offsets from main object
+ top=-heightMin / 2
+ bottom = heightMin / 2 + (maxY - minY)
+ left=-widthMin / 2
+ right = widthMin / 2 + (maxX - minX)
+
+ control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, top, CONTROL_POINT_DIAGONAL)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, top, CONTROL_POINT_VERTICAL)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, top, CONTROL_POINT_DIAGONAL)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, 0, CONTROL_POINT_HORIZONTAL)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, bottom, CONTROL_POINT_DIAGONAL)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, bottom, CONTROL_POINT_VERTICAL)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, bottom, CONTROL_POINT_DIAGONAL)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, 0, CONTROL_POINT_HORIZONTAL)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ def MakeMandatoryControlPoints(self):
+ """Make the mandatory control points.
+
+ For example, the control point on a dividing line should appear even
+ if the divided rectangle shape's handles should not appear (because
+ it is the child of a composite, and children are not resizable).
+ """
+ for child in self._children:
+ child.MakeMandatoryControlPoints()
+
+ def ResetMandatoryControlPoints(self):
+ """Reset the mandatory control points."""
+ for child in self._children:
+ child.ResetMandatoryControlPoints()
+
+ def ResetControlPoints(self):
+ """Reset the positions of the control points (for instance when the
+ shape's shape has changed).
+ """
+ self.ResetMandatoryControlPoints()
+
+ if len(self._controlPoints) == 0:
+ return
+
+ maxX, maxY = self.GetBoundingBoxMax()
+ minX, minY = self.GetBoundingBoxMin()
+
+ widthMin = minX + CONTROL_POINT_SIZE + 2
+ heightMin = minY + CONTROL_POINT_SIZE + 2
+
+ # Offsets from main object
+ top=-heightMin / 2
+ bottom = heightMin / 2 + (maxY - minY)
+ left=-widthMin / 2
+ right = widthMin / 2 + (maxX - minX)
+
+ self._controlPoints[0]._xoffset = left
+ self._controlPoints[0]._yoffset = top
+
+ self._controlPoints[1]._xoffset = 0
+ self._controlPoints[1]._yoffset = top
+
+ self._controlPoints[2]._xoffset = right
+ self._controlPoints[2]._yoffset = top
+
+ self._controlPoints[3]._xoffset = right
+ self._controlPoints[3]._yoffset = 0
+
+ self._controlPoints[4]._xoffset = right
+ self._controlPoints[4]._yoffset = bottom
+
+ self._controlPoints[5]._xoffset = 0
+ self._controlPoints[5]._yoffset = bottom
+
+ self._controlPoints[6]._xoffset = left
+ self._controlPoints[6]._yoffset = bottom
+
+ self._controlPoints[7]._xoffset = left
+ self._controlPoints[7]._yoffset = 0
+
+ def DeleteControlPoints(self, dc = None):
+ """Delete the control points (or handles) for the shape.
+
+ Does not redraw the shape.
+ """
+ for control in self._controlPoints:
+ if dc:
+ control.GetEventHandler().OnErase(dc)
+ self._canvas.RemoveShape(control)
+ del control
+ self._controlPoints = []
+
+ # Children of divisions are contained objects,
+ # so stop here
+ if not isinstance(self, DivisionShape):
+ for child in self._children:
+ child.DeleteControlPoints(dc)
+
+ def OnDrawControlPoints(self, dc):
+ if not self._drawHandles:
+ return
+
+ dc.SetBrush(wx.BLACK_BRUSH)
+ dc.SetPen(wx.BLACK_PEN)
+
+ for control in self._controlPoints:
+ control.Draw(dc)
+
+ # Children of divisions are contained objects,
+ # so stop here.
+ # This test bypasses the type facility for speed
+ # (critical when drawing)
+
+ if not isinstance(self, DivisionShape):
+ for child in self._children:
+ child.GetEventHandler().OnDrawControlPoints(dc)
+
+ def OnEraseControlPoints(self, dc):
+ for control in self._controlPoints:
+ control.Erase(dc)
+
+ if not isinstance(self, DivisionShape):
+ for child in self._children:
+ child.GetEventHandler().OnEraseControlPoints(dc)
+
+ def Select(self, select, dc = None):
+ """Select or deselect the given shape, drawing or erasing control points
+ (handles) as necessary.
+ """
+ self._selected = select
+ if select:
+ self.MakeControlPoints()
+ # Children of divisions are contained objects,
+ # so stop here
+ if not isinstance(self, DivisionShape):
+ for child in self._children:
+ child.MakeMandatoryControlPoints()
+ if dc:
+ self.GetEventHandler().OnDrawControlPoints(dc)
+ else:
+ self.DeleteControlPoints(dc)
+ if not isinstance(self, DivisionShape):
+ for child in self._children:
+ child.DeleteControlPoints(dc)
+
+ def Selected(self):
+ """TRUE if the shape is currently selected."""
+ return self._selected
+
+ def AncestorSelected(self):
+ """TRUE if the shape's ancestor is currently selected."""
+ if self._selected:
+ return True
+ if not self.GetParent():
+ return False
+ return self.GetParent().AncestorSelected()
+
+ def GetNumberOfAttachments(self):
+ """Get the number of attachment points for this shape."""
+ # Should return the MAXIMUM attachment point id here,
+ # so higher-level functions can iterate through all attachments,
+ # even if they're not contiguous.
+
+ if len(self._attachmentPoints) == 0:
+ return 4
+ else:
+ maxN = 3
+ for point in self._attachmentPoints:
+ if point._id>maxN:
+ maxN = point._id
+ return maxN + 1
+
+ def AttachmentIsValid(self, attachment):
+ """TRUE if attachment is a valid attachment point."""
+ if len(self._attachmentPoints) == 0:
+ return attachment in range(4)
+
+ for point in self._attachmentPoints:
+ if point._id == attachment:
+ return True
+ return False
+
+ def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
+ """Get the position at which the given attachment point should be drawn.
+
+ If attachment isn't found among the attachment points of the shape,
+ returns None.
+ """
+ if self._attachmentMode == ATTACHMENT_MODE_NONE:
+ return self._xpos, self._ypos
+ elif self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
+ pt, stemPt = self.GetBranchingAttachmentPoint(attachment, nth)
+ return pt.x, pt.y
+ elif self._attachmentMode == ATTACHMENT_MODE_EDGE:
+ if len(self._attachmentPoints):
+ for point in self._attachmentPoints:
+ if point._id == attachment:
+ return self._xpos + point._x, self._ypos + point._y
+ return None
+ else:
+ # Assume is rectangular
+ w, h = self.GetBoundingBoxMax()
+ top = self._ypos + h / 2
+ bottom = self._ypos - h / 2
+ left = self._xpos - w / 2
+ right = self._xpos + w / 2
+
+ # wtf?
+ line and line.IsEnd(self)
+
+ physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
+
+ # Simplified code
+ if physicalAttachment == 0:
+ pt = self.CalcSimpleAttachment((left, bottom), (right, bottom), nth, no_arcs, line)
+ elif physicalAttachment == 1:
+ pt = self.CalcSimpleAttachment((right, bottom), (right, top), nth, no_arcs, line)
+ elif physicalAttachment == 2:
+ pt = self.CalcSimpleAttachment((left, top), (right, top), nth, no_arcs, line)
+ elif physicalAttachment == 3:
+ pt = self.CalcSimpleAttachment((left, bottom), (left, top), nth, no_arcs, line)
+ else:
+ return None
+ return pt[0], pt[1]
+ return None
+
+ def GetBoundingBoxMax(self):
+ """Get the maximum bounding box for the shape, taking into account
+ external features such as shadows.
+ """
+ ww, hh = self.GetBoundingBoxMin()
+ if self._shadowMode != SHADOW_NONE:
+ ww += self._shadowOffsetX
+ hh += self._shadowOffsetY
+ return ww, hh
+
+ def GetBoundingBoxMin(self):
+ """Get the minimum bounding box for the shape, that defines the area
+ available for drawing the contents (such as text).
+
+ Must be overridden.
+ """
+ return 0, 0
+
+ def HasDescendant(self, image):
+ """TRUE if image is a descendant of this composite."""
+ if image == self:
+ return True
+ for child in self._children:
+ if child.HasDescendant(image):
+ return True
+ return False
+
+ # Clears points from a list of wxRealPoints, and clears list
+ # Useless in python? /pi
+ def ClearPointList(self, list):
+ list = []
+
+ # Assuming the attachment lies along a vertical or horizontal line,
+ # calculate the position on that point.
+ def CalcSimpleAttachment(self, pt1, pt2, nth, noArcs, line):
+ """Assuming the attachment lies along a vertical or horizontal line,
+ calculate the position on that point.
+
+ Parameters:
+
+ pt1
+ The first point of the line repesenting the edge of the shape.
+
+ pt2
+ The second point of the line representing the edge of the shape.
+
+ nth
+ The position on the edge (for example there may be 6 lines at
+ this attachment point, and this may be the 2nd line.
+
+ noArcs
+ The number of lines at this edge.
+
+ line
+ The line shape.
+
+ Remarks
+
+ This function expects the line to be either vertical or horizontal,
+ and determines which.
+ """
+ isEnd = line and line.IsEnd(self)
+
+ # Are we horizontal or vertical?
+ isHorizontal = RoughlyEqual(pt1[1], pt2[1])
+
+ if isHorizontal:
+ if pt1[0]>pt2[0]:
+ firstPoint = pt2
+ secondPoint = pt1
+ else:
+ firstPoint = pt1
+ secondPoint = pt2
+
+ if self._spaceAttachments:
+ if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ # Align line according to the next handle along
+ point = line.GetNextControlPoint(self)
+ if point[0]<firstPoint[0]:
+ x = firstPoint[0]
+ elif point[0]>secondPoint[0]:
+ x = secondPoint[0]
+ else:
+ x = point[0]
+ else:
+ x = firstPoint[0] + (nth + 1) * (secondPoint[0] - firstPoint[0]) / (noArcs + 1)
+ else:
+ x = (secondPoint[0] - firstPoint[0]) / 2 # Midpoint
+ y = pt1[1]
+ else:
+ assert RoughlyEqual(pt1[0], pt2[0])
+
+ if pt1[1]>pt2[1]:
+ firstPoint = pt2
+ secondPoint = pt1
+ else:
+ firstPoint = pt1
+ secondPoint = pt2
+
+ if self._spaceAttachments:
+ if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ # Align line according to the next handle along
+ point = line.GetNextControlPoint(self)
+ if point[1]<firstPoint[1]:
+ y = firstPoint[1]
+ elif point[1]>secondPoint[1]:
+ y = secondPoint[1]
+ else:
+ y = point[1]
+ else:
+ y = firstPoint[1] + (nth + 1) * (secondPoint[1] - firstPoint[1]) / (noArcs + 1)
+ else:
+ y = (secondPoint[1] - firstPoint[1]) / 2 # Midpoint
+ x = pt1[0]
+
+ return x, y
+
+ # Return the zero-based position in m_lines of line
+ def GetLinePosition(self, line):
+ """Get the zero-based position of line in the list of lines
+ for this shape.
+ """
+ try:
+ return self._lines.index(line)
+ except:
+ return 0
+
+
+ # |________|
+ # | <- root
+ # | <- neck
+ # shoulder1 ->---------<- shoulder2
+ # | | | | |
+ # <- branching attachment point N-1
+
+ def GetBranchingAttachmentInfo(self, attachment):
+ """Get information about where branching connections go.
+
+ Returns FALSE if there are no lines at this attachment.
+ """
+ physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
+
+ # Number of lines at this attachment
+ lineCount = self.GetAttachmentLineCount(attachment)
+
+ if not lineCount:
+ return False
+
+ totalBranchLength = self._branchSpacing * (lineCount - 1)
+ root = self.GetBranchingAttachmentRoot(attachment)
+
+ neck = wx.RealPoint()
+ shoulder1 = wx.RealPoint()
+ shoulder2 = wx.RealPoint()
+
+ # Assume that we have attachment points 0 to 3: top, right, bottom, left
+ if physicalAttachment == 0:
+ neck.x = self.GetX()
+ neck.y = root.y - self._branchNeckLength
+
+ shoulder1.x = root.x - totalBranchLength / 2
+ shoulder2.x = root.x + totalBranchLength / 2
+
+ shoulder1.y = neck.y
+ shoulder2.y = neck.y
+ elif physicalAttachment == 1:
+ neck.x = root.x + self._branchNeckLength
+ neck.y = root.y
+
+ shoulder1.x = neck.x
+ shoulder2.x = neck.x
+
+ shoulder1.y = neck.y - totalBranchLength / 2
+ shoulder1.y = neck.y + totalBranchLength / 2
+ elif physicalAttachment == 2:
+ neck.x = self.GetX()
+ neck.y = root.y + self._branchNeckLength
+
+ shoulder1.x = root.x - totalBranchLength / 2
+ shoulder2.x = root.x + totalBranchLength / 2
+
+ shoulder1.y = neck.y
+ shoulder2.y = neck.y
+ elif physicalAttachment == 3:
+ neck.x = root.x - self._branchNeckLength
+ neck.y = root.y
+
+ shoulder1.x = neck.x
+ shoulder2.x = neck.x
+
+ shoulder1.y = neck.y - totalBranchLength / 2
+ shoulder2.y = neck.y + totalBranchLength / 2
+ else:
+ raise "Unrecognised attachment point in GetBranchingAttachmentInfo"
+ return root, neck, shoulder1, shoulder2
+
+ def GetBranchingAttachmentPoint(self, attachment, n):
+ physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
+
+ root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
+ pt = wx.RealPoint()
+ stemPt = wx.RealPoint()
+
+ if physicalAttachment == 0:
+ pt.y = neck.y - self._branchStemLength
+ pt.x = shoulder1.x + n * self._branchSpacing
+
+ stemPt.x = pt.x
+ stemPt.y = neck.y
+ elif physicalAttachment == 2:
+ pt.y = neck.y + self._branchStemLength
+ pt.x = shoulder1.x + n * self._branchStemLength
+
+ stemPt.x = pt.x
+ stemPt.y = neck.y
+ elif physicalAttachment == 1:
+ pt.x = neck.x + self._branchStemLength
+ pt.y = shoulder1.y + n * self._branchSpacing
+
+ stemPt.x = neck.x
+ stemPt.y = pt.y
+ elif physicalAttachment == 3:
+ pt.x = neck.x - self._branchStemLength
+ pt.y = shoulder1.y + n * self._branchSpacing
+
+ stemPt.x = neck.x
+ stemPt.y = pt.y
+ else:
+ raise "Unrecognised attachment point in GetBranchingAttachmentPoint"
+
+ return pt, stemPt
+
+ def GetAttachmentLineCount(self, attachment):
+ """Get the number of lines at this attachment position."""
+ count = 0
+ for lineShape in self._lines:
+ if lineShape.GetFrom() == self and lineShape.GetAttachmentFrom() == attachment:
+ count += 1
+ elif lineShape.GetTo() == self and lineShape.GetAttachmentTo() == attachment:
+ count += 1
+ return count
+
+ def GetBranchingAttachmentRoot(self, attachment):
+ """Get the root point at the given attachment."""
+ physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
+
+ root = wx.RealPoint()
+
+ width, height = self.GetBoundingBoxMax()
+
+ # Assume that we have attachment points 0 to 3: top, right, bottom, left
+ if physicalAttachment == 0:
+ root.x = self.GetX()
+ root.y = self.GetY() - height / 2
+ elif physicalAttachment == 1:
+ root.x = self.GetX() + width / 2
+ root.y = self.GetY()
+ elif physicalAttachment == 2:
+ root.x = self.GetX()
+ root.y = self.GetY() + height / 2
+ elif physicalAttachment == 3:
+ root.x = self.GetX() - width / 2
+ root.y = self.GetY()
+ else:
+ raise "Unrecognised attachment point in GetBranchingAttachmentRoot"
+
+ return root
+
+ # Draw or erase the branches (not the actual arcs though)
+ def OnDrawBranchesAttachment(self, dc, attachment, erase = False):
+ count = self.GetAttachmentLineCount(attachment)
+ if count == 0:
+ return
+
+ root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
+
+ if erase:
+ dc.SetPen(wx.WHITE_PEN)
+ dc.SetBrush(wx.WHITE_BRUSH)
+ else:
+ dc.SetPen(wx.BLACK_PEN)
+ dc.SetBrush(wx.BLACK_BRUSH)
+
+ # Draw neck
+ dc.DrawLine(root, neck)
+
+ if count>1:
+ # Draw shoulder-to-shoulder line
+ dc.DrawLine(shoulder1, shoulder2)
+ # Draw all the little branches
+ for i in range(count):
+ pt, stemPt = self.GetBranchingAttachmentPoint(attachment, i)
+ dc.DrawLine(stemPt, pt)
+
+ if self.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB and count>1:
+ blobSize = 6
+ dc.DrawEllipse(stemPt.x - blobSize / 2, stemPt.y - blobSize / 2, blobSize, blobSize)
+
+ def OnDrawBranches(self, dc, erase = False):
+ if self._attachmentMode != ATTACHMENT_MODE_BRANCHING:
+ return
+ for i in range(self.GetNumberOfAttachments()):
+ self.OnDrawBranchesAttachment(dc, i, erase)
+
+ def GetAttachmentPositionEdge(self, attachment, nth = 0, no_arcs = 1, line = None):
+ """ Only get the attachment position at the _edge_ of the shape,
+ ignoring branching mode. This is used e.g. to indicate the edge of
+ interest, not the point on the attachment branch.
+ """
+ oldMode = self._attachmentMode
+
+ # Calculate as if to edge, not branch
+ if self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
+ self._attachmentMode = ATTACHMENT_MODE_EDGE
+ res = self.GetAttachmentPosition(attachment, nth, no_arcs, line)
+ self._attachmentMode = oldMode
+
+ return res
+
+ def PhysicalToLogicalAttachment(self, physicalAttachment):
+ """ Rotate the standard attachment point from physical
+ (0 is always North) to logical (0 -> 1 if rotated by 90 degrees)
+ """
+ if RoughlyEqual(self.GetRotation(), 0):
+ i = physicalAttachment
+ elif RoughlyEqual(self.GetRotation(), pi / 2):
+ i = physicalAttachment - 1
+ elif RoughlyEqual(self.GetRotation(), pi):
+ i = physicalAttachment - 2
+ elif RoughlyEqual(self.GetRotation(), 3 * pi / 2):
+ i = physicalAttachment - 3
+ else:
+ # Can't handle -- assume the same
+ return physicalAttachment
+
+ if i<0:
+ i += 4
+
+ return i
+
+ def LogicalToPhysicalAttachment(self, logicalAttachment):
+ """Rotate the standard attachment point from logical
+ to physical (0 is always North).
+ """
+ if RoughlyEqual(self.GetRotation(), 0):
+ i = logicalAttachment
+ elif RoughlyEqual(self.GetRotation(), pi / 2):
+ i = logicalAttachment + 1
+ elif RoughlyEqual(self.GetRotation(), pi):
+ i = logicalAttachment + 2
+ elif RoughlyEqual(self.GetRotation(), 3 * pi / 2):
+ i = logicalAttachment + 3
+ else:
+ return logicalAttachment
+
+ if i>3:
+ i -= 4
+
+ return i
+
+ def Rotate(self, x, y, theta):
+ """Rotate about the given axis by the given amount in radians."""
+ self._rotation = theta
+ if self._rotation<0:
+ self._rotation += 2 * pi
+ elif self._rotation>2 * pi:
+ self._rotation -= 2 * pi
+
+ def GetBackgroundPen(self):
+ """Return pen of the right colour for the background."""
+ if self.GetCanvas():
+ return wx.Pen(self.GetCanvas().GetBackgroundColour(), 1, wx.SOLID)
+ return WhiteBackgroundPen
+
+ def GetBackgroundBrush(self):
+ """Return brush of the right colour for the background."""
+ if self.GetCanvas():
+ return wx.Brush(self.GetCanvas().GetBackgroundColour(), wx.SOLID)
+ return WhiteBackgroundBrush
+
+ def GetX(self):
+ """Get the x position of the centre of the shape."""
+ return self._xpos
+
+ def GetY(self):
+ """Get the y position of the centre of the shape."""
+ return self._ypos
+
+ def SetX(self, x):
+ """Set the x position of the shape."""
+ self._xpos = x
+
+ def SetY(self, y):
+ """Set the y position of the shape."""
+ self._ypos = y
+
+ def GetParent(self):
+ """Return the parent of this shape, if it is part of a composite."""
+ return self._parent
+
+ def SetParent(self, p):
+ self._parent = p
+
+ def GetChildren(self):
+ """Return the list of children for this shape."""
+ return self._children
+
+ def GetDrawHandles(self):
+ """Return the list of drawhandles."""
+ return self._drawHandles
+
+ def GetEventHandler(self):
+ """Return the event handler for this shape."""
+ return self._eventHandler
+
+ def SetEventHandler(self, handler):
+ """Set the event handler for this shape."""
+ self._eventHandler = handler
+
+ def Recompute(self):
+ """Recomputes any constraints associated with the shape.
+
+ Normally applicable to CompositeShapes only, but harmless for
+ other classes of Shape.
+ """
+ return True
+
+ def IsHighlighted(self):
+ """TRUE if the shape is highlighted. Shape highlighting is unimplemented."""
+ return self._highlighted
+
+ def GetSensitivityFilter(self):
+ """Return the sensitivity filter, a bitlist of values.
+
+ See Shape.SetSensitivityFilter.
+ """
+ return self._sensitivity
+
+ def SetFixedSize(self, x, y):
+ """Set the shape to be fixed size."""
+ self._fixedWidth = x
+ self._fixedHeight = y
+
+ def GetFixedSize(self):
+ """Return flags indicating whether the shape is of fixed size in
+ either direction.
+ """
+ return self._fixedWidth, self._fixedHeight
+
+ def GetFixedWidth(self):
+ """TRUE if the shape cannot be resized in the horizontal plane."""
+ return self._fixedWidth
+
+ def GetFixedHeight(self):
+ """TRUE if the shape cannot be resized in the vertical plane."""
+ return self._fixedHeight
+
+ def SetSpaceAttachments(self, sp):
+ """Indicate whether lines should be spaced out evenly at the point
+ they touch the node (sp = True), or whether they should join at a single
+ point (sp = False).
+ """
+ self._spaceAttachments = sp
+
+ def GetSpaceAttachments(self):
+ """Return whether lines should be spaced out evenly at the point they
+ touch the node (True), or whether they should join at a single point
+ (False).
+ """
+ return self._spaceAttachments
+
+ def SetCentreResize(self, cr):
+ """Specify whether the shape is to be resized from the centre (the
+ centre stands still) or from the corner or side being dragged (the
+ other corner or side stands still).
+ """
+ self._centreResize = cr
+
+ def GetCentreResize(self):
+ """TRUE if the shape is to be resized from the centre (the centre stands
+ still), or FALSE if from the corner or side being dragged (the other
+ corner or side stands still)
+ """
+ return self._centreResize
+
+ def SetMaintainAspectRatio(self, ar):
+ """Set whether a shape that resizes should not change the aspect ratio
+ (width and height should be in the original proportion).
+ """
+ self._maintainAspectRatio = ar
+
+ def GetMaintainAspectRatio(self):
+ """TRUE if shape keeps aspect ratio during resize."""
+ return self._maintainAspectRatio
+
+ def GetLines(self):
+ """Return the list of lines connected to this shape."""
+ return self._lines
+
+ def SetDisableLabel(self, flag):
+ """Set flag to TRUE to stop the default region being shown."""
+ self._disableLabel = flag
+
+ def GetDisableLabel(self):
+ """TRUE if the default region will not be shown, FALSE otherwise."""
+ return self._disableLabel
+
+ def SetAttachmentMode(self, mode):
+ """Set the attachment mode.
+
+ If TRUE, attachment points will be significant when drawing lines to
+ and from this shape.
+ If FALSE, lines will be drawn as if to the centre of the shape.
+ """
+ self._attachmentMode = mode
+
+ def GetAttachmentMode(self):
+ """Return the attachment mode.
+
+ See Shape.SetAttachmentMode.
+ """
+ return self._attachmentMode
+
+ def SetId(self, i):
+ """Set the integer identifier for this shape."""
+ self._id = i
+
+ def GetId(self):
+ """Return the integer identifier for this shape."""
+ return self._id
+
+ def IsShown(self):
+ """TRUE if the shape is in a visible state, FALSE otherwise.
+
+ Note that this has nothing to do with whether the window is hidden
+ or the shape has scrolled off the canvas; it refers to the internal
+ visibility flag.
+ """
+ return self._visible
+
+ def GetPen(self):
+ """Return the pen used for drawing the shape's outline."""
+ return self._pen
+
+ def GetBrush(self):
+ """Return the brush used for filling the shape."""
+ return self._brush
+
+ def GetNumberOfTextRegions(self):
+ """Return the number of text regions for this shape."""
+ return len(self._regions)
+
+ def GetRegions(self):
+ """Return the list of ShapeRegions."""
+ return self._regions
+
+ # Control points ('handles') redirect control to the actual shape, to
+ # make it easier to override sizing behaviour.
+ def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
+ bound_x, bound_y = self.GetBoundingBoxMin()
+
+ 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)
+
+ if self.GetCentreResize():
+ # Maintain the same centre point
+ new_width = 2.0 * abs(x - self.GetX())
+ new_height = 2.0 * abs(y - self.GetY())
+
+ # Constrain sizing according to what control point you're dragging
+ if pt._type == CONTROL_POINT_HORIZONTAL:
+ if self.GetMaintainAspectRatio():
+ new_height = bound_y * (new_width / bound_x)
+ else:
+ new_height = bound_y
+ elif pt._type == CONTROL_POINT_VERTICAL:
+ if self.GetMaintainAspectRatio():
+ new_width = bound_x * (new_height / bound_y)
+ else:
+ new_width = bound_x
+ elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
+ new_height = bound_y * (new_width / bound_x)
+
+ if self.GetFixedWidth():
+ new_width = bound_x
+
+ if self.GetFixedHeight():
+ new_height = bound_y
+
+ pt._controlPointDragEndWidth = new_width
+ pt._controlPointDragEndHeight = new_height
+
+ self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
+ else:
+ # Don't maintain the same centre point
+ newX1 = min(pt._controlPointDragStartX, x)
+ newY1 = min(pt._controlPointDragStartY, y)
+ newX2 = max(pt._controlPointDragStartX, x)
+ newY2 = max(pt._controlPointDragStartY, y)
+ if pt._type == CONTROL_POINT_HORIZONTAL:
+ newY1 = pt._controlPointDragStartY
+ newY2 = newY1 + pt._controlPointDragStartHeight
+ elif pt._type == CONTROL_POINT_VERTICAL:
+ newX1 = pt._controlPointDragStartX
+ newX2 = newX1 + pt._controlPointDragStartWidth
+ elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()):
+ newH = (newX2 - newX1) * (pt._controlPointDragStartHeight / pt._controlPointDragStartWidth)
+ if self.GetY()>pt._controlPointDragStartY:
+ newY2 = newY1 + newH
+ else:
+ newY1 = newY2 - newH
+
+ newWidth = newX2 - newX1
+ newHeight = newY2 - newY1
+
+ if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
+ newWidth = bound_x * (newHeight / bound_y)
+
+ if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
+ newHeight = bound_y * (newWidth / bound_x)
+
+ pt._controlPointDragPosX = newX1 + newWidth / 2
+ pt._controlPointDragPosY = newY1 + newHeight / 2
+ if self.GetFixedWidth():
+ newWidth = bound_x
+
+ if self.GetFixedHeight():
+ newHeight = bound_y
+
+ pt._controlPointDragEndWidth = newWidth
+ pt._controlPointDragEndHeight = newHeight
+ self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
+
+ def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
+ self._canvas.CaptureMouse()
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ dc.SetLogicalFunction(OGLRBLF)
+
+ bound_x, bound_y = self.GetBoundingBoxMin()
+ self.GetEventHandler().OnEndSize(bound_x, bound_y)
+
+ # Choose the 'opposite corner' of the object as the stationary
+ # point in case this is non-centring resizing.
+ if pt.GetX()<self.GetX():
+ pt._controlPointDragStartX = self.GetX() + bound_x / 2
+ else:
+ pt._controlPointDragStartX = self.GetX() - bound_x / 2
+
+ if pt.GetY()<self.GetY():
+ pt._controlPointDragStartY = self.GetY() + bound_y / 2
+ else:
+ pt._controlPointDragStartY = self.GetY() - bound_y / 2
+
+ if pt._type == CONTROL_POINT_HORIZONTAL:
+ pt._controlPointDragStartY = self.GetY() - bound_y / 2
+ elif pt._type == CONTROL_POINT_VERTICAL:
+ pt._controlPointDragStartX = self.GetX() - bound_x / 2
+
+ # We may require the old width and height
+ pt._controlPointDragStartWidth = bound_x
+ pt._controlPointDragStartHeight = bound_y
+
+ dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
+ dc.SetPen(dottedPen)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ if self.GetCentreResize():
+ new_width = 2.0 * abs(x - self.GetX())
+ new_height = 2.0 * abs(y - self.GetY())
+
+ # Constrain sizing according to what control point you're dragging
+ if pt._type == CONTROL_POINT_HORIZONTAL:
+ if self.GetMaintainAspectRatio():
+ new_height = bound_y * (new_width / bound_x)
+ else:
+ new_height = bound_y
+ elif pt._type == CONTROL_POINT_VERTICAL:
+ if self.GetMaintainAspectRatio():
+ new_width = bound_x * (new_height / bound_y)
+ else:
+ new_width = bound_x
+ elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
+ new_height = bound_y * (new_width / bound_x)
+
+ if self.GetFixedWidth():
+ new_width = bound_x
+
+ if self.GetFixedHeight():
+ new_height = bound_y
+
+ pt._controlPointDragEndWidth = new_width
+ pt._controlPointDragEndHeight = new_height
+ self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
+ else:
+ # Don't maintain the same centre point
+ newX1 = min(pt._controlPointDragStartX, x)
+ newY1 = min(pt._controlPointDragStartY, y)
+ newX2 = max(pt._controlPointDragStartX, x)
+ newY2 = max(pt._controlPointDragStartY, y)
+ if pt._type == CONTROL_POINT_HORIZONTAL:
+ newY1 = pt._controlPointDragStartY
+ newY2 = newY1 + pt._controlPointDragStartHeight
+ elif pt._type == CONTROL_POINT_VERTICAL:
+ newX1 = pt._controlPointDragStartX
+ newX2 = newX1 + pt._controlPointDragStartWidth
+ elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEYS or self.GetMaintainAspectRatio()):
+ newH = (newX2 - newX1) * (pt._controlPointDragStartHeight / pt._controlPointDragStartWidth)
+ if pt.GetY()>pt._controlPointDragStartY:
+ newY2 = newY1 + newH
+ else:
+ newY1 = newY2 - newH
+
+ newWidth = newX2 - newX1
+ newHeight = newY2 - newY1
+
+ if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
+ newWidth = bound_x * (newHeight / bound_y)
+
+ if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
+ newHeight = bound_y * (newWidth / bound_x)
+
+ pt._controlPointDragPosX = newX1 + newWidth / 2
+ pt._controlPointDragPosY = newY1 + newHeight / 2
+ if self.GetFixedWidth():
+ newWidth = bound_x
+
+ if self.GetFixedHeight():
+ newHeight = bound_y
+
+ pt._controlPointDragEndWidth = newWidth
+ pt._controlPointDragEndHeight = newHeight
+ self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
+
+ def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ if self._canvas.HasCapture():
+ self._canvas.ReleaseMouse()
+ dc.SetLogicalFunction(wx.COPY)
+ self.Recompute()
+ self.ResetControlPoints()
+
+ self.Erase(dc)
+
+ self.SetSize(pt._controlPointDragEndWidth, pt._controlPointDragEndHeight)
+
+ # The next operation could destroy this control point (it does for
+ # label objects, via formatting the text), so save all values we're
+ # going to use, or we'll be accessing garbage.
+
+ #return
+
+ if self.GetCentreResize():
+ self.Move(dc, self.GetX(), self.GetY())
+ else:
+ self.Move(dc, pt._controlPointDragPosX, pt._controlPointDragPosY)
+
+ # Recursively redraw links if we have a composite
+ if len(self.GetChildren()):
+ self.DrawLinks(dc,-1, True)
+
+ width, height = self.GetBoundingBoxMax()
+ self.GetEventHandler().OnEndSize(width, height)
+
+ if not self._canvas.GetQuickEditMode() and pt._eraseObject:
+ self._canvas.Redraw(dc)
+
+
+
+class RectangleShape(Shape):
+ """
+ The wxRectangleShape has rounded or square corners.
+
+ Derived from:
+ Shape
+ """
+ def __init__(self, w = 0.0, h = 0.0):
+ Shape.__init__(self)
+ self._width = w
+ self._height = h
+ self._cornerRadius = 0.0
+ self.SetDefaultRegionSize()
+
+ def OnDraw(self, dc):
+ x1 = self._xpos - self._width / 2
+ y1 = self._ypos - self._height / 2
+
+ if self._shadowMode != SHADOW_NONE:
+ if self._shadowBrush:
+ dc.SetBrush(self._shadowBrush)
+ dc.SetPen(TransparentPen)
+
+ 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)
+
+ if self._pen:
+ if self._pen.GetWidth() == 0:
+ dc.SetPen(TransparentPen)
+ else:
+ dc.SetPen(self._pen)
+ if self._brush:
+ dc.SetBrush(self._brush)
+
+ if self._cornerRadius:
+ dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
+ else:
+ dc.DrawRectangle(x1, y1, self._width, self._height)
+
+ def GetBoundingBoxMin(self):
+ return self._width, self._height
+
+ def SetSize(self, x, y, recursive = False):
+ self.SetAttachmentSize(x, y)
+ self._width = max(x, 1)
+ self._height = max(y, 1)
+ self.SetDefaultRegionSize()
+
+ def GetCornerRadius(self):
+ """Get the radius of the rectangle's rounded corners."""
+ return self._cornerRadius
+
+ def SetCornerRadius(self, rad):
+ """Set the radius of the rectangle's rounded corners.
+
+ If the radius is zero, a non-rounded rectangle will be drawn.
+ If the radius is negative, the value is the proportion of the smaller
+ dimension of the rectangle.
+ """
+ self._cornerRadius = rad
+
+ # Assume (x1, y1) is centre of box (most generally, line end at box)
+ def GetPerimeterPoint(self, x1, y1, x2, y2):
+ bound_x, bound_y = self.GetBoundingBoxMax()
+ return FindEndForBox(bound_x, bound_y, self._xpos, self._ypos, x2, y2)
+
+ def GetWidth(self):
+ return self._width
+
+ def GetHeight(self):
+ return self._height
+
+ def SetWidth(self, w):
+ self._width = w
+
+ def SetHeight(self, h):
+ self._height = h
+
+
+
+class PolygonShape(Shape):
+ """A PolygonShape's shape is defined by a number of points passed to
+ the object's constructor. It can be used to create new shapes such as
+ diamonds and triangles.
+ """
+ def __init__(self):
+ Shape.__init__(self)
+
+ self._points = None
+ self._originalPoints = None
+
+ def Create(self, the_points = None):
+ """Takes a list of wx.RealPoints or tuples; each point is an offset
+ from the centre.
+ """
+ self.ClearPoints()
+
+ if not the_points:
+ self._originalPoints = []
+ self._points = []
+ else:
+ self._originalPoints = the_points
+
+ # Duplicate the list of points
+ self._points = []
+ for point in the_points:
+ new_point = wx.Point(point[0], point[1])
+ self._points.append(new_point)
+ self.CalculateBoundingBox()
+ self._originalWidth = self._boundWidth
+ self._originalHeight = self._boundHeight
+ self.SetDefaultRegionSize()
+
+ def ClearPoints(self):
+ self._points = []
+ self._originalPoints = []
+
+ # Width and height. Centre of object is centre of box
+ def GetBoundingBoxMin(self):
+ return self._boundWidth, self._boundHeight
+
+ def GetPoints(self):
+ """Return the internal list of polygon vertices."""
+ return self._points
+
+ def GetOriginalPoints(self):
+ return self._originalPoints
+
+ def GetOriginalWidth(self):
+ return self._originalWidth
+
+ def GetOriginalHeight(self):
+ return self._originalHeight
+
+ def SetOriginalWidth(self, w):
+ self._originalWidth = w
+
+ def SetOriginalHeight(self, h):
+ self._originalHeight = h
+
+ def CalculateBoundingBox(self):
+ # Calculate bounding box at construction (and presumably resize) time
+ left = 10000
+ right=-10000
+ top = 10000
+ bottom=-10000
+
+ for point in self._points:
+ if point.x<left:
+ left = point.x
+ if point.x>right:
+ right = point.x
+
+ if point.y<top:
+ top = point.y
+ if point.y>bottom:
+ bottom = point.y
+
+ self._boundWidth = right - left
+ self._boundHeight = bottom - top
+
+ def CalculatePolygonCentre(self):
+ """Recalculates the centre of the polygon, and
+ readjusts the point offsets accordingly.
+ Necessary since the centre of the polygon
+ is expected to be the real centre of the bounding
+ box.
+ """
+ left = 10000
+ right=-10000
+ top = 10000
+ bottom=-10000
+
+ for point in self._points:
+ if point.x<left:
+ left = point.x
+ if point.x>right:
+ right = point.x
+
+ if point.y<top:
+ top = point.y
+ if point.y>bottom:
+ bottom = point.y
+
+ bwidth = right - left
+ bheight = bottom - top
+
+ newCentreX = left + bwidth / 2
+ newCentreY = top + bheight / 2
+
+ for point in self._points:
+ point.x -= newCentreX
+ point.y -= newCentreY
+ self._xpos += newCentreX
+ self._ypos += newCentreY
+
+ def HitTest(self, x, y):
+ # Imagine four lines radiating from this point. If all of these lines
+ # hit the polygon, we're inside it, otherwise we're not. Obviously
+ # we'd need more radiating lines to be sure of correct results for
+ # very strange (concave) shapes.
+ endPointsX = [x, x + 1000, x, x - 1000]
+ endPointsY = [y - 1000, y, y + 1000, y]
+
+ xpoints = []
+ ypoints = []
+
+ for point in self._points:
+ xpoints.append(point.x + self._xpos)
+ ypoints.append(point.y + self._ypos)
+
+ # We assume it's inside the polygon UNLESS one or more
+ # lines don't hit the outline.
+ isContained = True
+
+ for i in range(4):
+ if not PolylineHitTest(xpoints, ypoints, x, y, endPointsX[i], endPointsY[i]):
+ isContained = False
+
+ if not isContained:
+ return False
+
+ nearest_attachment = 0
+
+ # If a hit, check the attachment points within the object
+ nearest = 999999
+
+ for i in range(self.GetNumberOfAttachments()):
+ e = self.GetAttachmentPositionEdge(i)
+ if e:
+ xp, yp = e
+ l = sqrt((xp - x) * (xp - x) + (yp - y) * (yp - y))
+ if l<nearest:
+ nearest = l
+ nearest_attachment = i
+
+ return nearest_attachment, nearest
+
+ # Really need to be able to reset the shape! Otherwise, if the
+ # points ever go to zero, we've lost it, and can't resize.
+ def SetSize(self, new_width, new_height, recursive = True):
+ self.SetAttachmentSize(new_width, new_height)
+
+ # Multiply all points by proportion of new size to old size
+ x_proportion = abs(new_width / self._originalWidth)
+ y_proportion = abs(new_height / self._originalHeight)
+
+ for i in range(max(len(self._points), len(self._originalPoints))):
+ self._points[i].x = self._originalPoints[i][0] * x_proportion
+ self._points[i].y = self._originalPoints[i][1] * y_proportion
+
+ self._boundWidth = abs(new_width)
+ self._boundHeight = abs(new_height)
+ self.SetDefaultRegionSize()
+
+ # Make the original points the same as the working points
+ def UpdateOriginalPoints(self):
+ """If we've changed the shape, must make the original points match the
+ working points with this function.
+ """
+ self._originalPoints = []
+
+ for point in self._points:
+ original_point = wx.RealPoint(point.x, point.y)
+ self._originalPoints.append(original_point)
+
+ self.CalculateBoundingBox()
+ self._originalWidth = self._boundWidth
+ self._originalHeight = self._boundHeight
+
+ def AddPolygonPoint(self, pos):
+ """Add a control point after the given point."""
+ try:
+ firstPoint = self._points[pos]
+ except ValueError:
+ firstPoint = self._points[0]
+
+ try:
+ secondPoint = self._points[pos + 1]
+ except ValueError:
+ secondPoint = self._points[0]
+
+ x = (secondPoint.x - firstPoint.x) / 2 + firstPoint.x
+ y = (secondPoint.y - firstPoint.y) / 2 + firstPoint.y
+ point = wx.RealPoint(x, y)
+
+ if pos >= len(self._points) - 1:
+ self._points.append(point)
+ else:
+ self._points.insert(pos + 1, point)
+
+ self.UpdateOriginalPoints()
+
+ if self._selected:
+ self.DeleteControlPoints()
+ self.MakeControlPoints()
+
+ def DeletePolygonPoint(self, pos):
+ """Delete the given control point."""
+ if pos<len(self._points):
+ del self._points[pos]
+ self.UpdateOriginalPoints()
+ if self._selected:
+ self.DeleteControlPoints()
+ self.MakeControlPoints()
+
+ # Assume (x1, y1) is centre of box (most generally, line end at box)
+ def GetPerimeterPoint(self, x1, y1, x2, y2):
+ # First check for situation where the line is vertical,
+ # and we would want to connect to a point on that vertical --
+ # oglFindEndForPolyline can't cope with this (the arrow
+ # gets drawn to the wrong place).
+ if self._attachmentMode == ATTACHMENT_MODE_NONE and x1 == x2:
+ # Look for the point we'd be connecting to. This is
+ # a heuristic...
+ for point in self._points:
+ if point.x == 0:
+ if y2>y1 and point.y>0:
+ return point.x + self._xpos, point.y + self._ypos
+ elif y2<y1 and point.y<0:
+ return point.x + self._xpos, point.y + self._ypos
+
+ xpoints = []
+ ypoints = []
+ for point in self._points:
+ xpoints.append(point.x + self._xpos)
+ ypoints.append(point.y + self._ypos)
+
+ return FindEndForPolyline(xpoints, ypoints, x1, y1, x2, y2)
+
+ def OnDraw(self, dc):
+ if self._shadowMode != SHADOW_NONE:
+ if self._shadowBrush:
+ dc.SetBrush(self._shadowBrush)
+ dc.SetPen(TransparentPen)
+
+ dc.DrawPolygon(self._points, self._xpos + self._shadowOffsetX, self._ypos, self._shadowOffsetY)
+
+ if self._pen:
+ if self._pen.GetWidth() == 0:
+ dc.SetPen(TransparentPen)
+ else:
+ dc.SetPen(self._pen)
+ if self._brush:
+ dc.SetBrush(self._brush)
+ dc.DrawPolygon(self._points, self._xpos, self._ypos)
+
+ def OnDrawOutline(self, dc, x, y, w, h):
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ # Multiply all points by proportion of new size to old size
+ x_proportion = abs(w / self._originalWidth)
+ y_proportion = abs(h / self._originalHeight)
+
+ intPoints = []
+ for point in self._originalPoints:
+ intPoints.append(wx.Point(x_proportion * point[0], y_proportion * point[1]))
+ dc.DrawPolygon(intPoints, x, y)
+
+ # Make as many control points as there are vertices
+ def MakeControlPoints(self):
+ for point in self._points:
+ control = PolygonControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point, point.x, point.y)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ def ResetControlPoints(self):
+ for i in range(min(len(self._points), len(self._controlPoints))):
+ point = self._points[i]
+ self._controlPoints[i]._xoffset = point.x
+ self._controlPoints[i]._yoffset = point.y
+ self._controlPoints[i].polygonVertex = point
+
+ def GetNumberOfAttachments(self):
+ maxN = max(len(self._points) - 1, 0)
+ for point in self._attachmentPoints:
+ if point._id>maxN:
+ maxN = point._id
+ return maxN + 1
+
+ def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
+ if self._attachmentMode == ATTACHMENT_MODE_EDGE and self._points and attachment<len(self._points):
+ point = self._points[0]
+ return point.x + self._xpos, point.y + self._ypos
+ return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line)
+
+ def AttachmentIsValid(self, attachment):
+ if not self._points:
+ return False
+
+ if attachment >= 0 and attachment<len(self._points):
+ return True
+
+ for point in self._attachmentPoints:
+ if point._id == attachment:
+ return True
+
+ return False
+
+ # Rotate about the given axis by the given amount in radians
+ def Rotate(self, x, y, theta):
+ actualTheta = theta - self._rotation
+
+ # Rotate attachment points
+ sinTheta = sin(actualTheta)
+ cosTheta = cos(actualTheta)
+
+ for point in self._attachmentPoints:
+ x1 = point._x
+ y1 = point._y
+
+ point._x = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
+ point._y = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
+
+ for point in self._points:
+ x1 = point.x
+ y1 = point.y
+
+ point.x = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
+ point.y = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
+
+ for point in self._originalPoints:
+ x1 = point.x
+ y1 = point.y
+
+ point.x = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
+ point.y = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
+
+ self._rotation = theta
+
+ self.CalculatePolygonCentre()
+ self.CalculateBoundingBox()
+ self.ResetControlPoints()
+
+ # Control points ('handles') redirect control to the actual shape, to
+ # make it easier to override sizing behaviour.
+ def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
+ 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)
+
+ # Code for CTRL-drag in C++ version commented out
+
+ pt.CalculateNewSize(x, y)
+
+ self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize().x, pt.GetNewSize().y)
+
+ def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ self.Erase(dc)
+
+ dc.SetLogicalFunction(OGLRBLF)
+
+ bound_x, bound_y = self.GetBoundingBoxMin()
+
+ dist = sqrt((x - self.GetX()) * (x - self.GetX()) + (y - self.GetY()) * (y - self.GetY()))
+
+ pt._originalDistance = dist
+ pt._originalSize.x = bound_x
+ pt._originalSize.y = bound_y
+
+ if pt._originalDistance == 0:
+ pt._originalDistance = 0.0001
+
+ dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
+ dc.SetPen(dottedPen)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ # Code for CTRL-drag in C++ version commented out
+
+ pt.CalculateNewSize(x, y)
+
+ self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize().x, pt.GetNewSize().y)
+
+ self._canvas.CaptureMouse()
+
+ def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ if self._canvas.HasCapture():
+ self._canvas.ReleaseMouse()
+ dc.SetLogicalFunction(wx.COPY)
+
+ # If we're changing shape, must reset the original points
+ if keys & KEY_CTRL:
+ self.CalculateBoundingBox()
+ self.CalculatePolygonCentre()
+ else:
+ self.SetSize(pt.GetNewSize().x, pt.GetNewSize().y)
+
+ self.Recompute()
+ self.ResetControlPoints()
+ self.Move(dc, self.GetX(), self.GetY())
+ if not self._canvas.GetQuickEditMode():
+ self._canvas.Redraw(dc)
+
+
+
+class EllipseShape(Shape):
+ """The EllipseShape behaves similarly to the RectangleShape but is
+ elliptical.
+
+ Derived from:
+ wxShape
+ """
+ def __init__(self, w, h):
+ Shape.__init__(self)
+ self._width = w
+ self._height = h
+ self.SetDefaultRegionSize()
+
+ def GetBoundingBoxMin(self):
+ return self._width, self._height
+
+ def GetPerimeterPoint(self, x1, y1, x2, y2):
+ bound_x, bound_y = self.GetBoundingBoxMax()
+
+ return DrawArcToEllipse(self._xpos, self._ypos, bound_x, bound_y, x2, y2, x1, y1)
+
+ def GetWidth(self):
+ return self._width
+
+ def GetHeight(self):
+ return self._height
+
+ def SetWidth(self, w):
+ self._width = w
+
+ def SetHeight(self, h):
+ self._height = h
+
+ def OnDraw(self, dc):
+ if self._shadowMode != SHADOW_NONE:
+ if self._shadowBrush:
+ dc.SetBrush(self._shadowBrush)
+ dc.SetPen(TransparentPen)
+ dc.DrawEllipse(self._xpos - self.GetWidth() / 2 + self._shadowOffsetX,
+ self._ypos - self.GetHeight() / 2 + self._shadowOffsetY,
+ self.GetWidth(), self.GetHeight())
+
+ if self._pen:
+ if self._pen.GetWidth() == 0:
+ dc.SetPen(TransparentPen)
+ else:
+ dc.SetPen(self._pen)
+ if self._brush:
+ dc.SetBrush(self._brush)
+ dc.DrawEllipse(self._xpos - self.GetWidth() / 2, self._ypos - self.GetHeight() / 2, self.GetWidth(), self.GetHeight())
+
+ def SetSize(self, x, y, recursive = True):
+ self.SetAttachmentSize(x, y)
+ self._width = x
+ self._height = y
+ self.SetDefaultRegionSize()
+
+ def GetNumberOfAttachments(self):
+ return Shape.GetNumberOfAttachments(self)
+
+ # There are 4 attachment points on an ellipse - 0 = top, 1 = right,
+ # 2 = bottom, 3 = left.
+ def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
+ if self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
+ return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line)
+
+ if self._attachmentMode != ATTACHMENT_MODE_NONE:
+ top = self._ypos + self._height / 2
+ bottom = self._ypos - self._height / 2
+ left = self._xpos - self._width / 2
+ right = self._xpos + self._width / 2
+
+ physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
+
+ if physicalAttachment == 0:
+ if self._spaceAttachments:
+ x = left + (nth + 1) * self._width / (no_arcs + 1)
+ else:
+ x = self._xpos
+ y = top
+ # We now have the point on the bounding box: but get the point
+ # on the ellipse by imagining a vertical line from
+ # (x, self._ypos - self_height - 500) to (x, self._ypos) intersecting
+ # the ellipse.
+
+ return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos - self._height - 500, x, self._ypos)
+ elif physicalAttachment == 1:
+ x = right
+ if self._spaceAttachments:
+ y = bottom + (nth + 1) * self._height / (no_arcs + 1)
+ else:
+ y = self._ypos
+ return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos + self._width + 500, y, self._xpos, y)
+ elif physicalAttachment == 2:
+ if self._spaceAttachments:
+ x = left + (nth + 1) * self._width / (no_arcs + 1)
+ else:
+ x = self._xpos
+ y = bottom
+ return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos + self._height + 500, x, self._ypos)
+ elif physicalAttachment == 3:
+ x = left
+ if self._spaceAttachments:
+ y = bottom + (nth + 1) * self._height / (no_arcs + 1)
+ else:
+ y = self._ypos
+ return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos - self._width - 500, y, self._xpos, y)
+ else:
+ return Shape.GetAttachmentPosition(self, attachment, x, y, nth, no_arcs, line)
+ else:
+ return self._xpos, self._ypos
+
+
+
+class CircleShape(EllipseShape):
+ """An EllipseShape whose width and height are the same."""
+ def __init__(self, diameter):
+ EllipseShape.__init__(self, diameter, diameter)
+ self.SetMaintainAspectRatio(True)
+
+ def GetPerimeterPoint(self, x1, y1, x2, y2):
+ return FindEndForCircle(self._width / 2, self._xpos, self._ypos, x2, y2)
+
+
+
+class TextShape(RectangleShape):
+ """As wxRectangleShape, but only the text is displayed."""
+ def __init__(self, width, height):
+ RectangleShape.__init__(self, width, height)
+
+ def OnDraw(self, dc):
+ pass
+
+
+
+class ShapeRegion(object):
+ """Object region."""
+ def __init__(self, region = None):
+ if region:
+ self._regionText = region._regionText
+ self._regionName = region._regionName
+ self._textColour = region._textColour
+
+ self._font = region._font
+ self._minHeight = region._minHeight
+ self._minWidth = region._minWidth
+ self._width = region._width
+ self._height = region._height
+ self._x = region._x
+ self._y = region._y
+
+ self._regionProportionX = region._regionProportionX
+ self._regionProportionY = region._regionProportionY
+ self._formatMode = region._formatMode
+ self._actualColourObject = region._actualColourObject
+ self._actualPenObject = None
+ self._penStyle = region._penStyle
+ self._penColour = region._penColour
+
+ self.ClearText()
+ for line in region._formattedText:
+ new_line = ShapeTextLine(line.GetX(), line.GetY(), line.GetText())
+ self._formattedText.append(new_line)
+ else:
+ self._regionText=""
+ self._font = NormalFont
+ self._minHeight = 5.0
+ self._minWidth = 5.0
+ self._width = 0.0
+ self._height = 0.0
+ self._x = 0.0
+ self._y = 0.0
+
+ self._regionProportionX=-1.0
+ self._regionProportionY=-1.0
+ self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
+ self._regionName=""
+ self._textColour="BLACK"
+ self._penColour="BLACK"
+ self._penStyle = wx.SOLID
+ self._actualColourObject = wx.TheColourDatabase.Find("BLACK")
+ self._actualPenObject = None
+
+ self._formattedText = []
+
+ def ClearText(self):
+ self._formattedText = []
+
+ def SetFont(self, f):
+ self._font = f
+
+ def SetMinSize(self, w, h):
+ self._minWidth = w
+ self._minHeight = h
+
+ def SetSize(self, w, h):
+ self._width = w
+ self._height = h
+
+ def SetPosition(self, xp, yp):
+ self._x = xp
+ self._y = yp
+
+ def SetProportions(self, xp, yp):
+ self._regionProportionX = xp
+ self._regionProportionY = yp
+
+ def SetFormatMode(self, mode):
+ self._formatMode = mode
+
+ def SetColour(self, col):
+ self._textColour = col
+ self._actualColourObject = col
+
+ def GetActualColourObject(self):
+ self._actualColourObject = wx.TheColourDatabase.Find(self.GetColour())
+ return self._actualColourObject
+
+ def SetPenColour(self, col):
+ self._penColour = col
+ self._actualPenObject = None
+
+ # Returns NULL if the pen is invisible
+ # (different to pen being transparent; indicates that
+ # region boundary should not be drawn.)
+ def GetActualPen(self):
+ if self._actualPenObject:
+ return self._actualPenObject
+
+ if not self._penColour:
+ return None
+ if self._penColour=="Invisible":
+ return None
+ self._actualPenObject = wx.ThePenList.FindOrCreatePen(self._penColour, 1, self._penStyle)
+ return self._actualPenObject
+
+ def SetText(self, s):
+ self._regionText = s
+
+ def SetName(self, s):
+ self._regionName = s
+
+ def GetText(self):
+ return self._regionText
+
+ def GetFont(self):
+ return self._font
+
+ def GetMinSize(self):
+ return self._minWidth, self._minHeight
+
+ def GetProportion(self):
+ return self._regionProportionX, self._regionProportionY
+
+ def GetSize(self):
+ return self._width, self._height
+
+ def GetPosition(self):
+ return self._x, self._y
+
+ def GetFormatMode(self):
+ return self._formatMode
+
+ def GetName(self):
+ return self._regionName
+
+ def GetColour(self):
+ return self._textColour
+
+ def GetFormattedText(self):
+ return self._formattedText
+
+ def GetPenColour(self):
+ return self._penColour
+
+ def GetPenStyle(self):
+ return self._penStyle
+
+ def SetPenStyle(self, style):
+ self._penStyle = style
+ self._actualPenObject = None
+
+ def GetWidth(self):
+ return self._width
+
+ def GetHeight(self):
+ return self._height
+
+
+
+class ControlPoint(RectangleShape):
+ def __init__(self, theCanvas, object, size, the_xoffset, the_yoffset, the_type):
+ RectangleShape.__init__(self, size, size)
+
+ self._canvas = theCanvas
+ self._shape = object
+ self._xoffset = the_xoffset
+ self._yoffset = the_yoffset
+ self._type = the_type
+ self.SetPen(BlackForegroundPen)
+ self.SetBrush(wx.BLACK_BRUSH)
+ self._oldCursor = None
+ self._visible = True
+ self._eraseObject = True
+
+ # Don't even attempt to draw any text - waste of time
+ def OnDrawContents(self, dc):
+ pass
+
+ def OnDraw(self, dc):
+ self._xpos = self._shape.GetX() + self._xoffset
+ self._ypos = self._shape.GetY() + self._yoffset
+ RectangleShape.OnDraw(self, dc)
+
+ def OnErase(self, dc):
+ RectangleShape.OnErase(self, dc)
+
+ # Implement resizing of canvas object
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
+
+ def GetNumberOfAttachments(self):
+ return 1
+
+ def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
+ return self._xpos, self._ypos
+
+ def SetEraseObject(self, er):
+ self._eraseObject = er
+
+
+class PolygonControlPoint(ControlPoint):
+ def __init__(self, theCanvas, object, size, vertex, the_xoffset, the_yoffset):
+ ControlPoint.__init__(self, theCanvas, object, size, the_xoffset, the_yoffset, 0)
+ self._polygonVertex = vertex
+ self._originalDistance = 0.0
+ self._newSize = wx.RealPoint()
+ self._originalSize = wx.RealPoint()
+
+ def GetNewSize(self):
+ return self._newSize
+
+ # Calculate what new size would be, at end of resize
+ def CalculateNewSize(self, x, y):
+ bound_x, bound_y = self.GetShape().GetBoundingBoxMax()
+ dist = sqrt((x - self._shape.GetX()) * (x - self._shape.GetX()) + (y - self._shape.GetY()) * (y - self._shape.GetY()))
+
+ self._newSize.x = dist / self._originalDistance * self._originalSize.x
+ self._newSize.y = dist / self._originalDistance * self._originalSize.y
+
+ # Implement resizing polygon or moving the vertex
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
+
+from canvas import *
+from lines import *
+from composit import *
--- /dev/null
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name: bmpshape.py
+# Purpose: Bitmap shape
+#
+# Author: Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created: 20040508
+# RCS-ID:
+# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# Licence: wxWindows license
+#----------------------------------------------------------------------------
+
+from __future__ import division
+
+from basic import RectangleShape
+
+
+class BitmapShape(RectangleShape):
+ """Draws a bitmap (non-resizable)."""
+ def __init__(self):
+ RectangleShape.__init__(self, 100, 50)
+ self._filename=""
+
+ def OnDraw(self, dc):
+ if not self._bitmap.Ok():
+ return
+
+ x = self._xpos-self._bitmap.GetWidth() / 2
+ y = self._ypos-self._bitmap.GetHeight() / 2
+ dc.DrawBitmap(self._bitmap, x, y, True)
+
+ def SetSize(self, w, h, recursive = True):
+ if self._bitmap.Ok():
+ w = self._bitmap.GetWidth()
+ h = self._bitmap.GetHeight()
+
+ self.SetAttachmentSize(w, h)
+
+ self._width = w
+ self._height = h
+
+ self.SetDefaultRegionSize()
+
+ def GetBitmap(self):
+ """Return a the bitmap associated with this shape."""
+ return self._bitmap
+
+ def SetBitmap(self, bitmap):
+ """Set the bitmap associated with this shape.
+
+ You can delete the bitmap from the calling application, since
+ reference counting will take care of holding on to the internal bitmap
+ data.
+ """
+ self._bitmap = bitmap
+ if self._bitmap.Ok():
+ self.SetSize(self._bitmap.GetWidth(), self._bitmap.GetHeight())
+
+ def SetFilename(self, f):
+ """Set the bitmap filename."""
+ self._filename = f
+
+ def GetFilename(self):
+ """Return the bitmap filename."""
+ return self._filename
--- /dev/null
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name: canvas.py
+# Purpose: The canvas class
+#
+# Author: Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created: 20040508
+# RCS-ID:
+# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# Licence: wxWindows license
+#----------------------------------------------------------------------------
+
+from __future__ import division
+
+import wx
+from lines import LineShape
+from composit import *
+
+NoDragging, StartDraggingLeft, ContinueDraggingLeft, StartDraggingRight, ContinueDraggingRight = 0, 1, 2, 3, 4
+
+KEY_SHIFT, KEY_CTRL = 1, 2
+
+
+
+# Helper function: True if 'contains' wholly contains 'contained'.
+def WhollyContains(contains, contained):
+ xp1, yp1 = contains.GetX(), contains.GetY()
+ xp2, yp2 = contained.GetX(), contained.GetY()
+
+ w1, h1 = contains.GetBoundingBoxMax()
+ w2, h2 = contained.GetBoundingBoxMax()
+
+ left1 = xp1-w1 / 2.0
+ top1 = yp1-h1 / 2.0
+ right1 = xp1 + w1 / 2.0
+ bottom1 = yp1 + h1 / 2.0
+
+ left2 = xp2-w2 / 2.0
+ top2 = yp2-h2 / 2.0
+ right2 = xp2 + w2 / 2.0
+ bottom2 = yp2 + h2 / 2.0
+
+ return ((left1 <= left2) and (top1 <= top2) and (right1 >= right2) and (bottom1 >= bottom2))
+
+
+
+class ShapeCanvas(wx.ScrolledWindow):
+ def __init__(self, parent = None, id=-1, pos = wx.DefaultPosition, size = wx.DefaultSize, style = wx.BORDER, name="ShapeCanvas"):
+ wx.ScrolledWindow.__init__(self, parent, id, pos, size, style, name)
+
+ self._shapeDiagram = None
+ self._dragState = NoDragging
+ self._draggedShape = None
+ self._oldDragX = 0
+ self._oldDragY = 0
+ self._firstDragX = 0
+ self._firstDragY = 0
+ self._checkTolerance = True
+
+ wx.EVT_PAINT(self, self.OnPaint)
+ wx.EVT_MOUSE_EVENTS(self, self.OnMouseEvent)
+
+ def SetDiagram(self, diag):
+ self._shapeDiagram = diag
+
+ def GetDiagram(self):
+ return self._shapeDiagram
+
+ def OnPaint(self, evt):
+ dc = wx.PaintDC(self)
+ self.PrepareDC(dc)
+
+ dc.SetBackground(wx.Brush(self.GetBackgroundColour(), wx.SOLID))
+ dc.Clear()
+
+ if self.GetDiagram():
+ self.GetDiagram().Redraw(dc)
+
+ def OnMouseEvent(self, evt):
+ dc = wx.ClientDC(self)
+ self.PrepareDC(dc)
+
+ x, y = evt.GetLogicalPosition(dc)
+
+ keys = 0
+ if evt.ShiftDown():
+ keys |= KEY_SHIFT
+ if evt.ControlDown():
+ keys |= KEY_CTRL
+
+ dragging = evt.Dragging()
+
+ # Check if we're within the tolerance for mouse movements.
+ # If we're very close to the position we started dragging
+ # from, this may not be an intentional drag at all.
+ if dragging:
+ dx = abs(dc.LogicalToDeviceX(x-self._firstDragX))
+ dy = abs(dc.LogicalToDeviceY(y-self._firstDragY))
+ if self._checkTolerance and (dx <= self.GetDiagram().GetMouseTolerance()) and (dy <= self.GetDiagram().GetMouseTolerance()):
+ return
+ # If we've ignored the tolerance once, then ALWAYS ignore
+ # tolerance in this drag, even if we come back within
+ # the tolerance range.
+ self._checkTolerance = False
+
+ # Dragging - note that the effect of dragging is left entirely up
+ # to the object, so no movement is done unless explicitly done by
+ # object.
+ if dragging and self._draggedShape and self._dragState == StartDraggingLeft:
+ self._dragState = ContinueDraggingLeft
+
+ # If the object isn't m_draggable, transfer message to canvas
+ if self._draggedShape.Draggable():
+ self._draggedShape.GetEventHandler().OnBeginDragLeft(x, y, keys, self._draggedAttachment)
+ else:
+ self._draggedShape = None
+ self.OnBeginDragLeft(x, y, keys)
+
+ self._oldDragX, self._oldDragY = x, y
+
+ elif dragging and self._draggedShape and self._dragState == ContinueDraggingLeft:
+ # Continue dragging
+ self._draggedShape.GetEventHandler().OnDragLeft(False, self._oldDragX, self._oldDragY, keys, self._draggedAttachment)
+ self._draggedShape.GetEventHandler().OnDragLeft(True, x, y, keys, self._draggedAttachment)
+ self._oldDragX, self._oldDragY = x, y
+
+ elif evt.LeftUp and self._draggedShape and self._dragState == ContinueDraggingLeft:
+ self._dragState = NoDragging
+ self._checkTolerance = True
+
+ self._draggedShape.GetEventHandler().OnDragLeft(False, self._oldDragX, self._oldDragY, keys, self._draggedAttachment)
+ self._draggedShape.GetEventHandler().OnEndDragLeft(x, y, keys, self._draggedAttachment)
+ self._draggedShape = None
+
+ elif dragging and self._draggedShape and self._dragState == StartDraggingRight:
+ self._dragState = ContinueDraggingRight
+ if self._draggedShape.Draggable:
+ self._draggedShape.GetEventHandler().OnBeginDragRight(x, y, keys, self._draggedAttachment)
+ else:
+ self._draggedShape = None
+ self.OnBeginDragRight(x, y, keys)
+ self._oldDragX, self._oldDragY = x, y
+
+ elif dragging and self._draggedShape and self._dragState == ContinueDraggingRight:
+ # Continue dragging
+ self._draggedShape.GetEventHandler().OnDragRight(False, self._oldDragX, self._oldDragY, keys, self._draggedAttachment)
+ self._draggedShape.GetEventHandler().OnDragRight(True, x, y, keys, self._draggedAttachment)
+ self._oldDragX, self._oldDragY = x, y
+
+ elif evt.RightUp() and self._draggedShape and self._dragState == ContinueDraggingRight:
+ self._dragState = NoDragging
+ self._checkTolerance = True
+
+ self._draggedShape.GetEventHandler().OnDragRight(False, self._oldDragX, self._oldDragY, keys, self._draggedAttachment)
+ self._draggedShape.GetEventHandler().OnEndDragRight(x, y, keys, self._draggedAttachment)
+ self._draggedShape = None
+
+ # All following events sent to canvas, not object
+ elif dragging and not self._draggedShape and self._dragState == StartDraggingLeft:
+ self._dragState = ContinueDraggingLeft
+ self.OnBeginDragLeft(x, y, keys)
+ self._oldDragX, self._oldDragY = x, y
+
+ elif dragging and not self._draggedShape and self._dragState == ContinueDraggingLeft:
+ # Continue dragging
+ self.OnDragLeft(False, self._oldDragX, self._oldDragY, keys)
+ self.OnDragLeft(True, x, y, keys)
+ self._oldDragX, self._oldDragY = x, y
+
+ elif evt.LeftUp() and not self._draggedShape and self._dragState == ContinueDraggingLeft:
+ self._dragState = NoDragging
+ self._checkTolerance = True
+
+ self.OnDragLeft(False, self._oldDragX, self._oldDragY, keys)
+ self.OnEndDragLeft(x, y, keys)
+ self._draggedShape = None
+
+ elif dragging and not self._draggedShape and self._dragState == StartDraggingRight:
+ self._dragState = ContinueDraggingRight
+ self.OnBeginDragRight(x, y, keys)
+ self._oldDragX, self._oldDragY = x, y
+
+ elif dragging and not self._draggedShape and self._dragState == ContinueDraggingRight:
+ # Continue dragging
+ self.OnDragRight(False, self._oldDragX, self._oldDragY, keys)
+ self.OnDragRight(True, x, y, keys)
+ self._oldDragX, self._oldDragY = x, y
+
+ elif evt.RightUp() and not self._draggedShape and self._dragState == ContinueDraggingRight:
+ self._dragState = NoDragging
+ self._checkTolerance = True
+
+ self.OnDragRight(False, self._oldDragX, self._oldDragY, keys)
+ self.OnEndDragRight(x, y, keys)
+ self._draggedShape = None
+
+ # Non-dragging events
+ elif evt.IsButton():
+ self._checkTolerance = True
+
+ # Find the nearest object
+ attachment = 0
+
+ nearest_object, attachment = self.FindShape(x, y)
+ if nearest_object: # Object event
+ if evt.LeftDown():
+ self._draggedShape = nearest_object
+ self._draggedAttachment = attachment
+ self._dragState = StartDraggingLeft
+ self._firstDragX = x
+ self._firstDragY = y
+
+ elif evt.LeftUp():
+ # N.B. Only register a click if the same object was
+ # identified for down *and* up.
+ if nearest_object == self._draggedShape:
+ nearest_object.GetEventHandler().OnLeftClick(x, y, keys, attachment)
+ self._draggedShape = None
+ self._dragState = NoDragging
+
+ elif evt.LeftDClick():
+ nearest_object.GetEventHandler().OnLeftDoubleClick(x, y, keys, attachment)
+ self._draggedShape = None
+ self._dragState = NoDragging
+
+ elif evt.RightDown():
+ self._draggedShape = nearest_object
+ self._draggedAttachment = attachment
+ self._dragState = StartDraggingRight
+ self._firstDragX = x
+ self._firstDragY = y
+
+ elif evt.RightUp():
+ if nearest_object == self._draggedShape:
+ nearest_object.GetEventHandler().OnRightClick(x, y, keys, attachment)
+ self._draggedShape = None
+ self._dragState = NoDragging
+
+ else: # Canvas event
+ if evt.LeftDown():
+ self._draggedShape = None
+ self._dragState = StartDraggingLeft
+ self._firstDragX = x
+ self._firstDragY = y
+
+ elif evt.LeftUp():
+ self.OnLeftClick(x, y, keys)
+ self._draggedShape = None
+ self._dragState = NoDragging
+
+ elif evt.RightDown():
+ self._draggedShape = None
+ self._dragState = StartDraggingRight
+ self._firstDragX = x
+ self._firstDragY = y
+
+ elif evt.RightUp():
+ self.OnRightClick(x, y, keys)
+ self._draggedShape = None
+ self._dragState = NoDragging
+
+ def FindShape(self, x, y, info = None, notObject = None):
+ nearest = 100000.0
+ nearest_attachment = 0
+ nearest_object = None
+
+ # Go backward through the object list, since we want:
+ # (a) to have the control points drawn LAST to overlay
+ # the other objects
+ # (b) to find the control points FIRST if they exist
+
+ for object in self.GetDiagram().GetShapeList()[::-1]:
+ # First pass for lines, which might be inside a container, so we
+ # want lines to take priority over containers. This first loop
+ # could fail if we clickout side a line, so then we'll
+ # try other shapes.
+ if object.IsShown() and \
+ isinstance(object, LineShape) and \
+ object.HitTest(x, y) and \
+ ((info == None) or isinstance(object, info)) and \
+ (not notObject or not notObject.HasDescendant(object)):
+ temp_attachment, dist = object.HitTest(x, y)
+ # A line is trickier to spot than a normal object.
+ # For a line, since it's the diagonal of the box
+ # we use for the hit test, we may have several
+ # lines in the box and therefore we need to be able
+ # to specify the nearest point to the centre of the line
+ # as our hit criterion, to give the user some room for
+ # manouevre.
+ if dist<nearest:
+ nearest = dist
+ nearest_object = object
+ nearest_attachment = temp_attachment
+
+ for object in self.GetDiagram().GetShapeList()[::-1]:
+ # On second pass, only ever consider non-composites or
+ # divisions. If children want to pass up control to
+ # the composite, that's up to them.
+ if (object.IsShown() and
+ (isinstance(object, DivisionShape) or
+ not isinstance(object, CompositeShape)) and
+ object.HitTest(x, y) and
+ (info == None or isinstance(object, info)) and
+ (not notObject or not notObject.HasDescendant(object))):
+ temp_attachment, dist = object.HitTest(x, y)
+ if not isinstance(object, LineShape):
+ # If we've hit a container, and we have already
+ # found a line in the first pass, then ignore
+ # the container in case the line is in the container.
+ # Check for division in case line straddles divisions
+ # (i.e. is not wholly contained).
+ if not nearest_object or not (isinstance(object, DivisionShape) or WhollyContains(object, nearest_object)):
+ nearest_object = object
+ nearest_attachment = temp_attachment
+ break
+
+ return nearest_object, nearest_attachment
+
+ def AddShape(self, object, addAfter = None):
+ self.GetDiagram().AddShape(object, addAfter)
+
+ def InsertShape(self, object):
+ self.GetDiagram().InsertShape(object)
+
+ def RemoveShape(self, object):
+ self.GetDiagram().RemoveShape(object)
+
+ def GetQuickEditMode(self):
+ return self.GetDiagram().GetQuickEditMode()
+
+ def Redraw(self, dc):
+ self.GetDiagram().Redraw(dc)
+
+ def Snap(self, x, y):
+ return self.GetDiagram().Snap(x, y)
+
+ def OnLeftClick(self, x, y, keys = 0):
+ pass
+
+ def OnRightClick(self, x, y, keys = 0):
+ pass
+
+ def OnDragLeft(self, draw, x, y, keys = 0):
+ pass
+
+ def OnBeginDragLeft(self, x, y, keys = 0):
+ pass
+
+ def OnEndDragLeft(self, x, y, keys = 0):
+ pass
+
+ def OnDragRight(self, draw, x, y, keys = 0):
+ pass
+
+ def OnBeginDragRight(self, x, y, keys = 0):
+ pass
+
+ def OnEndDragRight(self, x, y, keys = 0):
+ pass
--- /dev/null
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name: composit.py
+# Purpose: Composite class
+#
+# Author: Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created: 20040508
+# RCS-ID:
+# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# Licence: wxWindows license
+#----------------------------------------------------------------------------
+
+from __future__ import division
+
+import sys
+import wx
+
+from basic import RectangleShape, Shape, ControlPoint
+from oglmisc import *
+
+KEY_SHIFT, KEY_CTRL = 1, 2
+
+objectStartX = 0.0
+objectStartY = 0.0
+
+CONSTRAINT_CENTRED_VERTICALLY = 1
+CONSTRAINT_CENTRED_HORIZONTALLY = 2
+CONSTRAINT_CENTRED_BOTH = 3
+CONSTRAINT_LEFT_OF = 4
+CONSTRAINT_RIGHT_OF = 5
+CONSTRAINT_ABOVE = 6
+CONSTRAINT_BELOW = 7
+CONSTRAINT_ALIGNED_TOP = 8
+CONSTRAINT_ALIGNED_BOTTOM = 9
+CONSTRAINT_ALIGNED_LEFT = 10
+CONSTRAINT_ALIGNED_RIGHT = 11
+
+# Like aligned, but with the objects centred on the respective edge
+# of the reference object.
+CONSTRAINT_MIDALIGNED_TOP = 12
+CONSTRAINT_MIDALIGNED_BOTTOM = 13
+CONSTRAINT_MIDALIGNED_LEFT = 14
+CONSTRAINT_MIDALIGNED_RIGHT = 15
+
+
+
+class ConstraintType(object):
+ def __init__(self, theType, theName, thePhrase):
+ self._type = theType
+ self._name = theName
+ self._phrase = thePhrase
+
+
+
+ConstraintTypes = [
+ [CONSTRAINT_CENTRED_VERTICALLY,
+ ConstraintType(CONSTRAINT_CENTRED_VERTICALLY, "Centre vertically", "centred vertically w.r.t.")],
+
+ [CONSTRAINT_CENTRED_HORIZONTALLY,
+ ConstraintType(CONSTRAINT_CENTRED_HORIZONTALLY, "Centre horizontally", "centred horizontally w.r.t.")],
+
+ [CONSTRAINT_CENTRED_BOTH,
+ ConstraintType(CONSTRAINT_CENTRED_BOTH, "Centre", "centred w.r.t.")],
+
+ [CONSTRAINT_LEFT_OF,
+ ConstraintType(CONSTRAINT_LEFT_OF, "Left of", "left of")],
+
+ [CONSTRAINT_RIGHT_OF,
+ ConstraintType(CONSTRAINT_RIGHT_OF, "Right of", "right of")],
+
+ [CONSTRAINT_ABOVE,
+ ConstraintType(CONSTRAINT_ABOVE, "Above", "above")],
+
+ [CONSTRAINT_BELOW,
+ ConstraintType(CONSTRAINT_BELOW, "Below", "below")],
+
+ # Alignment
+ [CONSTRAINT_ALIGNED_TOP,
+ ConstraintType(CONSTRAINT_ALIGNED_TOP, "Top-aligned", "aligned to the top of")],
+
+ [CONSTRAINT_ALIGNED_BOTTOM,
+ ConstraintType(CONSTRAINT_ALIGNED_BOTTOM, "Bottom-aligned", "aligned to the bottom of")],
+
+ [CONSTRAINT_ALIGNED_LEFT,
+ ConstraintType(CONSTRAINT_ALIGNED_LEFT, "Left-aligned", "aligned to the left of")],
+
+ [CONSTRAINT_ALIGNED_RIGHT,
+ ConstraintType(CONSTRAINT_ALIGNED_RIGHT, "Right-aligned", "aligned to the right of")],
+
+ # Mid-alignment
+ [CONSTRAINT_MIDALIGNED_TOP,
+ ConstraintType(CONSTRAINT_MIDALIGNED_TOP, "Top-midaligned", "centred on the top of")],
+
+ [CONSTRAINT_MIDALIGNED_BOTTOM,
+ ConstraintType(CONSTRAINT_MIDALIGNED_BOTTOM, "Bottom-midaligned", "centred on the bottom of")],
+
+ [CONSTRAINT_MIDALIGNED_LEFT,
+ ConstraintType(CONSTRAINT_MIDALIGNED_LEFT, "Left-midaligned", "centred on the left of")],
+
+ [CONSTRAINT_MIDALIGNED_RIGHT,
+ ConstraintType(CONSTRAINT_MIDALIGNED_RIGHT, "Right-midaligned", "centred on the right of")]
+ ]
+
+
+
+
+class Constraint(object):
+ """A Constraint object helps specify how child shapes are laid out with
+ respect to siblings and parents.
+
+ Derived from:
+ wxObject
+ """
+ def __init__(self, type, constraining, constrained):
+ self._xSpacing = 0.0
+ self._ySpacing = 0.0
+
+ self._constraintType = type
+ self._constraintingObject = constraining
+
+ self._constraintId = 0
+ self._constraintName="noname"
+
+ self._constrainedObjects = constrained[:]
+
+ def __repr__(self):
+ return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__)
+
+ def SetSpacing(self, x, y):
+ """Sets the horizontal and vertical spacing for the constraint."""
+ self._xSpacing = x
+ self._ySpacing = y
+
+ def Equals(self, a, b):
+ """Return TRUE if x and y are approximately equal (for the purposes
+ of evaluating the constraint).
+ """
+ marg = 0.5
+
+ return b <= a + marg and b >= a-marg
+
+ def Evaluate(self):
+ """Evaluate this constraint and return TRUE if anything changed."""
+ maxWidth, maxHeight = self._constraintingObject.GetBoundingBoxMax()
+ minWidth, minHeight = self._constraintingObject.GetBoundingBoxMin()
+ x = self._constraintingObject.GetX()
+ y = self._constraintingObject.GetY()
+
+ dc = wx.ClientDC(self._constraintingObject.GetCanvas())
+ self._constraintingObject.GetCanvas().PrepareDC(dc)
+
+ if self._constraintType == CONSTRAINT_CENTRED_VERTICALLY:
+ n = len(self._constrainedObjects)
+ totalObjectHeight = 0.0
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ totalObjectHeight += height2
+
+ # Check if within the constraining object...
+ if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight:
+ spacingY = (minHeight-totalObjectHeight) / (n + 1)
+ startY = y-minHeight / 2
+ else: # Otherwise, use default spacing
+ spacingY = self._ySpacing
+ startY = y-(totalObjectHeight + (n + 1) * spacingY) / 2
+
+ # Now position the objects
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ startY += spacingY + height2 / 2
+ if not self.Equals(startY, constrainedObject.GetY()):
+ constrainedObject.Move(dc, constrainedObject.GetX(), startY, False)
+ changed = True
+ startY += height2 / 2
+ return changed
+ elif self._constraintType == CONSTRAINT_CENTRED_HORIZONTALLY:
+ n = len(self._constrainedObjects)
+ totalObjectWidth = 0.0
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ totalObjectWidth += width2
+
+ # Check if within the constraining object...
+ if totalObjectWidth + (n + 1) * self._xSpacing<minWidth:
+ spacingX = (minWidth-totalObjectWidth) / (n + 1)
+ startX = x-minWidth / 2
+ else: # Otherwise, use default spacing
+ spacingX = self._xSpacing
+ startX = x-(totalObjectWidth + (n + 1) * spacingX) / 2
+
+ # Now position the objects
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ startX += spacingX + width2 / 2
+ if not self.Equals(startX, constrainedObject.GetX()):
+ constrainedObject.Move(dc, startX, constrainedObject.GetY(), False)
+ changed = True
+ startX += width2 / 2
+ return changed
+ elif self._constraintType == CONSTRAINT_CENTRED_BOTH:
+ n = len(self._constrainedObjects)
+ totalObjectWidth = 0.0
+ totalObjectHeight = 0.0
+
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ totalObjectWidth += width2
+ totalObjectHeight += height2
+
+ # Check if within the constraining object...
+ if totalObjectHeight + (n + 1) * self._xSpacing <= minWidth:
+ spacingX = (minWidth-totalObjectWidth) / (n + 1)
+ startX = x-minWidth / 2
+ else: # Otherwise, use default spacing
+ spacingX = self._xSpacing
+ startX = x-(totalObjectWidth + (n + 1) * spacingX) / 2
+
+ # Check if within the constraining object...
+ if totalObjectHeight + (n + 1) * self._ySpacing <= minHeight:
+ spacingY = (minHeight-totalObjectHeight) / (n + 1)
+ startY = y-minHeight / 2
+ else: # Otherwise, use default spacing
+ spacingY = self._ySpacing
+ startY = y-(totalObjectHeight + (n + 1) * spacingY) / 2
+
+ # Now position the objects
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ startX += spacingX + width2 / 2
+ startY += spacingY + height2 / 2
+
+ if not self.Equals(startX, constrainedObject.GetX()) or not self.Equals(startY, constrainedObject.GetY()):
+ constrainedObject.Move(dc, startX, startY, False)
+ changed = True
+
+ startX += width2 / 2
+ startY += height2 / 2
+ return changed
+ elif self._constraintType == CONSTRAINT_LEFT_OF:
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+
+ x3 = x-minWidth / 2-width2 / 2-self._xSpacing
+ if not self.Equals(x3, constrainedObject.GetX()):
+ changed = True
+ constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+ return changed
+ elif self._constraintType == CONSTRAINT_RIGHT_OF:
+ changed = False
+
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ x3 = x + minWidth / 2 + width2 / 2 + self._xSpacing
+ if not self.Equals(x3, constrainedObject.GetX()):
+ constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+ changed = True
+ return changed
+ elif self._constraintType == CONSTRAINT_ABOVE:
+ changed = False
+
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+
+ y3 = y-minHeight / 2-height2 / 2-self._ySpacing
+ if not self.Equals(y3, constrainedObject.GetY()):
+ changed = True
+ constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+ return changed
+ elif self._constraintType == CONSTRAINT_BELOW:
+ changed = False
+
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+
+ y3 = y + minHeight / 2 + height2 / 2 + self._ySpacing
+ if not self.Equals(y3, constrainedObject.GetY()):
+ changed = True
+ constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+ return changed
+ elif self._constraintType == CONSTRAINT_ALIGNED_LEFT:
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ x3 = x-minWidth / 2 + width2 / 2 + self._xSpacing
+ if not self.Equals(x3, constrainedObject.GetX()):
+ changed = True
+ constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+ return changed
+ elif self._constraintType == CONSTRAINT_ALIGNED_RIGHT:
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ x3 = x + minWidth / 2-width2 / 2-self._xSpacing
+ if not self.Equals(x3, constrainedObject.GetX()):
+ changed = True
+ constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+ return changed
+ elif self._constraintType == CONSTRAINT_ALIGNED_TOP:
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ y3 = y-minHeight / 2 + height2 / 2 + self._ySpacing
+ if not self.Equals(y3, constrainedObject.GetY()):
+ changed = True
+ constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+ return changed
+ elif self._constraintType == CONSTRAINT_ALIGNED_BOTTOM:
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ width2, height2 = constrainedObject.GetBoundingBoxMax()
+ y3 = y + minHeight / 2-height2 / 2-self._ySpacing
+ if not self.Equals(y3, constrainedObject.GetY()):
+ changed = True
+ constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+ return changed
+ elif self._constraintType == CONSTRAINT_MIDALIGNED_LEFT:
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ x3 = x-minWidth / 2
+ if not self.Equals(x3, constrainedObject.GetX()):
+ changed = True
+ constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+ return changed
+ elif self._constraintType == CONSTRAINT_MIDALIGNED_RIGHT:
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ x3 = x + minWidth / 2
+ if not self.Equals(x3, constrainedObject.GetX()):
+ changed = True
+ constrainedObject.Move(dc, x3, constrainedObject.GetY(), False)
+ return changed
+ elif self._constraintType == CONSTRAINT_MIDALIGNED_TOP:
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ y3 = y-minHeight / 2
+ if not self.Equals(y3, constrainedObject.GetY()):
+ changed = True
+ constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+ return changed
+ elif self._constraintType == CONSTRAINT_MIDALIGNED_BOTTOM:
+ changed = False
+ for constrainedObject in self._constrainedObjects:
+ y3 = y + minHeight / 2
+ if not self.Equals(y3, constrainedObject.GetY()):
+ changed = True
+ constrainedObject.Move(dc, constrainedObject.GetX(), y3, False)
+ return changed
+
+ return False
+
+
+
+class CompositeShape(RectangleShape):
+ """This is an object with a list of child objects, and a list of size
+ and positioning constraints between the children.
+
+ Derived from:
+ wxRectangleShape
+ """
+ def __init__(self):
+ RectangleShape.__init__(self, 100.0, 100.0)
+
+ self._oldX = self._xpos
+ self._oldY = self._ypos
+
+ self._constraints = []
+ self._divisions = [] # In case it's a container
+
+ def OnDraw(self, dc):
+ x1 = self._xpos-self._width / 2
+ y1 = self._ypos-self._height / 2
+
+ if self._shadowMode != SHADOW_NONE:
+ if self._shadowBrush:
+ dc.SetBrush(self._shadowBrush)
+ dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT))
+
+ if self._cornerRadius:
+ dc.DrawRoundedRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height, self._cornerRadius)
+ else:
+ dc.DrawRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height)
+
+ # For debug purposes /pi
+ #dc.DrawRectangle(x1, y1, self._width, self._height)
+
+ def OnDrawContents(self, dc):
+ for object in self._children:
+ object.Draw(dc)
+ object.DrawLinks(dc)
+
+ Shape.OnDrawContents(self, dc)
+
+ def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
+ diffX = x-old_x
+ diffY = y-old_y
+
+ for object in self._children:
+ object.Erase(dc)
+ object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display)
+
+ return True
+
+ def OnErase(self, dc):
+ RectangleShape.OnErase(self, dc)
+ for object in self._children:
+ object.Erase(dc)
+
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ xx, yy = self._canvas.Snap(x, y)
+ offsetX = xx-objectStartX
+ offsetY = yy-objectStartY
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ dc.SetLogicalFunction(OGLRBLF)
+ dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
+ dc.SetPen(dottedPen)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ self.GetEventHandler().OnDrawOutline(dc, self.GetX() + offsetX, self.GetY() + offsetY, self.GetWidth(), self.GetHeight())
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ global objectStartX, objectStartY
+
+ objectStartX = x
+ objectStartY = y
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ #self.Erase(dc)
+
+ dc.SetLogicalFunction(OGLRBLF)
+ dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
+ dc.SetPen(dottedPen)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ self._canvas.CaptureMouse()
+
+ xx, yy = self._canvas.Snap(x, y)
+ offsetX = xx-objectStartX
+ offsetY = yy-objectStartY
+
+ self.GetEventHandler().OnDrawOutline(dc, self.GetX() + offsetX, self.GetY() + offsetY, self.GetWidth(), self.GetHeight())
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ if self._canvas.HasCapture():
+ self._canvas.ReleaseMouse()
+
+ if not self._draggable:
+ if self._parent:
+ self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, 0)
+ return
+
+ self.Erase(dc)
+
+ dc.SetLogicalFunction(wx.COPY)
+
+ xx, yy = self._canvas.Snap(x, y)
+ offsetX = xx-objectStartX
+ offsetY = yy-objectStartY
+
+ self.Move(dc, self.GetX() + offsetX, self.GetY() + offsetY)
+
+ if self._canvas and not self._canvas.GetQuickEditMode():
+ self._canvas.Redraw(dc)
+
+ def OnRightClick(self, x, y, keys = 0, attachment = 0):
+ # If we get a ctrl-right click, this means send the message to
+ # the division, so we can invoke a user interface for dealing
+ # with regions.
+ if keys & KEY_CTRL:
+ for division in self._divisions:
+ hit = division.HitTest(x, y)
+ if hit:
+ division.GetEventHandler().OnRightClick(x, y, keys, hit[0])
+ break
+
+ def SetSize(self, w, h, recursive = True):
+ self.SetAttachmentSize(w, h)
+
+ xScale = w / max(1, self.GetWidth())
+ yScale = h / max(1, self.GetHeight())
+
+ self._width = w
+ self._height = h
+
+ if not recursive:
+ return
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ for object in self._children:
+ # Scale the position first
+ newX = (object.GetX()-self.GetX()) * xScale + self.GetX()
+ newY = (object.GetY()-self.GetY()) * yScale + self.GetY()
+ object.Show(False)
+ object.Move(dc, newX, newY)
+ object.Show(True)
+
+ # Now set the scaled size
+ xbound, ybound = object.GetBoundingBoxMax()
+ if not object.GetFixedWidth():
+ xbound *= xScale
+ if not object.GetFixedHeight():
+ ybound *= yScale
+ object.SetSize(xbound, ybound)
+
+ self.SetDefaultRegionSize()
+
+ def AddChild(self, child, addAfter = None):
+ """Adds a child shape to the composite.
+
+ If addAfter is not None, the shape will be added after this shape.
+ """
+ self._children.append(child)
+ child.SetParent(self)
+ if self._canvas:
+ # Ensure we add at the right position
+ if addAfter:
+ child.RemoveFromCanvas(self._canvas)
+ child.AddToCanvas(self._canvas, addAfter)
+
+ def RemoveChild(self, child):
+ """Removes the child from the composite and any constraint
+ relationships, but does not delete the child.
+ """
+ self._children.remove(child)
+ self._divisions.remove(child)
+ self.RemoveChildFromConstraints(child)
+ child.SetParent(None)
+
+ def DeleteConstraintsInvolvingChild(self, child):
+ """This function deletes constraints which mention the given child.
+
+ Used when deleting a child from the composite.
+ """
+ for constraint in self._constraints:
+ if constraint._constrainingObject == child or child in constraint._constrainedObjects:
+ self._constraints.remove(constraint)
+
+ def RemoveChildFromConstraints(self, child):
+ for constraint in self._constraints:
+ if child in constraint._constrainedObjects:
+ constraint._constrainedObjects.remove(child)
+ if constraint._constrainingObject == child:
+ constraint._constrainingObject = None
+
+ # Delete the constraint if no participants left
+ if not constraint._constrainingObject:
+ self._constraints.remove(constraint)
+
+ def AddConstraint(self, constraint):
+ """Adds a constraint to the composite."""
+ self._constraints.append(constraint)
+ if constraint._constraintId == 0:
+ constraint._constraintId = wx.NewId()
+ return constraint
+
+ def AddSimpleConstraint(self, type, constraining, constrained):
+ """Add a constraint of the given type to the composite.
+
+ constraining is the shape doing the constraining
+ constrained is a list of shapes being constrained
+ """
+ constraint = Constraint(type, constraining, constrained)
+ if constraint._constraintId == 0:
+ constraint._constraintId = wx.NewId()
+ self._constraints.append(constraint)
+ return constraint
+
+ def FindConstraint(self, cId):
+ """Finds the constraint with the given id.
+
+ Returns a tuple of the constraint and the actual composite the
+ constraint was in, in case that composite was a descendant of
+ this composit.
+
+ Returns None if not found.
+ """
+ for constraint in self._constraints:
+ if constraint._constraintId == cId:
+ return constraint, self
+
+ # If not found, try children
+ for child in self._children:
+ if isinstance(child, CompositeShape):
+ constraint = child.FindConstraint(cId)
+ if constraint:
+ return constraint[0], child
+
+ return None
+
+ def DeleteConstraint(self, constraint):
+ """Deletes constraint from composite."""
+ self._constraints.remove(constraint)
+
+ def CalculateSize(self):
+ """Calculates the size and position of the composite based on
+ child sizes and positions.
+ """
+ maxX=-999999.9
+ maxY=-999999.9
+ minX = 999999.9
+ minY = 999999.9
+
+ for child in self._children:
+ # Recalculate size of composite objects because may not conform
+ # to size it was set to - depends on the children.
+ if isinstance(child, CompositeShape):
+ child.CalculateSize()
+
+ w, h = child.GetBoundingBoxMax()
+ if child.GetX() + w / 2>maxX:
+ maxX = child.GetX() + w / 2
+ if child.GetX()-w / 2<minX:
+ minX = child.GetX()-w / 2
+ if child.GetY() + h / 2>maxY:
+ maxY = child.GetY() + h / 2
+ if child.GetY()-h / 2<minY:
+ minY = child.GetY()-h / 2
+
+ self._width = maxX-minX
+ self._height = maxY-minY
+ self._xpos = self._width / 2 + minX
+ self._ypos = self._height / 2 + minY
+
+ def Recompute(self):
+ """Recomputes any constraints associated with the object. If FALSE is
+ returned, the constraints could not be satisfied (there was an
+ inconsistency).
+ """
+ noIterations = 0
+ changed = True
+ while changed and noIterations<500:
+ changed = self.Constrain()
+ noIterations += 1
+
+ return not changed
+
+ def Constrain(self):
+ self.CalculateSize()
+
+ changed = False
+ for child in self._children:
+ if isinstance(child, CompositeShape) and child.Constrain():
+ changed = True
+
+ for constraint in self._constraints:
+ if constraint.Evaluate():
+ changed = True
+
+ return changed
+
+ def MakeContainer(self):
+ """Makes this composite into a container by creating one child
+ DivisionShape.
+ """
+ division = self.OnCreateDivision()
+ self._divisions.append(division)
+ self.AddChild(division)
+
+ division.SetSize(self._width, self._height)
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ division.Move(dc, self.GetX(), self.GetY())
+ self.Recompute()
+ division.Show(True)
+
+ def OnCreateDivision(self):
+ return DivisionShape()
+
+ def FindContainerImage(self):
+ """Finds the image used to visualize a container. This is any child of
+ the composite that is not in the divisions list.
+ """
+ for child in self._children:
+ if child in self._divisions:
+ return child
+
+ return None
+
+ def ContainsDivision(self, division):
+ """Returns TRUE if division is a descendant of this container."""
+ if division in self._divisions:
+ return True
+
+ for child in self._children:
+ if isinstance(child, CompositeShape):
+ return child.ContainsDivision(division)
+
+ return False
+
+ def GetDivisions(self):
+ """Return the list of divisions."""
+ return self._divisions
+
+ def GetConstraints(self):
+ """Return the list of constraints."""
+ return self._constraints
+
+
+# A division object is a composite with special properties,
+# to be used for containment. It's a subdivision of a container.
+# A containing node image consists of a composite with a main child shape
+# such as rounded rectangle, plus a list of division objects.
+# It needs to be a composite because a division contains pieces
+# of diagram.
+# NOTE a container has at least one wxDivisionShape for consistency.
+# This can be subdivided, so it turns into two objects, then each of
+# these can be subdivided, etc.
+
+DIVISION_SIDE_NONE =0
+DIVISION_SIDE_LEFT =1
+DIVISION_SIDE_TOP =2
+DIVISION_SIDE_RIGHT =3
+DIVISION_SIDE_BOTTOM =4
+
+originalX = 0.0
+originalY = 0.0
+originalW = 0.0
+originalH = 0.0
+
+
+
+class DivisionControlPoint(ControlPoint):
+ def __init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type):
+ ControlPoint.__init__(self, the_canvas, object, size, the_xoffset, the_yoffset, the_type)
+ self.SetEraseObject(False)
+
+ # Implement resizing of canvas object
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ ControlPoint.OnDragLeft(self, draw, x, y, keys, attachment)
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ global originalX, originalY, originalW, originalH
+
+ originalX = self._shape.GetX()
+ originalY = self._shape.GetY()
+ originalW = self._shape.GetWidth()
+ originalH = self._shape.GetHeight()
+
+ ControlPoint.OnBeginDragLeft(self, x, y, keys, attachment)
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ ControlPoint.OnEndDragLeft(self, x, y, keys, attachment)
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ division = self._shape
+ divisionParent = division.GetParent()
+
+ # Need to check it's within the bounds of the parent composite
+ x1 = divisionParent.GetX()-divisionParent.GetWidth() / 2
+ y1 = divisionParent.GetY()-divisionParent.GetHeight() / 2
+ x2 = divisionParent.GetX() + divisionParent.GetWidth() / 2
+ y2 = divisionParent.GetY() + divisionParent.GetHeight() / 2
+
+ # Need to check it has not made the division zero or negative
+ # width / height
+ dx1 = division.GetX()-division.GetWidth() / 2
+ dy1 = division.GetY()-division.GetHeight() / 2
+ dx2 = division.GetX() + division.GetWidth() / 2
+ dy2 = division.GetY() + division.GetHeight() / 2
+
+ success = True
+ if division.GetHandleSide() == DIVISION_SIDE_LEFT:
+ if x <= x1 or x >= x2 or x >= dx2:
+ success = False
+ # Try it out first...
+ elif not division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, True):
+ success = False
+ else:
+ division.ResizeAdjoining(DIVISION_SIDE_LEFT, x, False)
+ elif division.GetHandleSide() == DIVISION_SIDE_TOP:
+ if y <= y1 or y >= y2 or y >= dy2:
+ success = False
+ elif not division.ResizeAdjoining(DIVISION_SIDE_TOP, y, True):
+ success = False
+ else:
+ division.ResizingAdjoining(DIVISION_SIDE_TOP, y, False)
+ elif division.GetHandleSide() == DIVISION_SIDE_RIGHT:
+ if x <= x1 or x >= x2 or x <= dx1:
+ success = False
+ elif not division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, True):
+ success = False
+ else:
+ division.ResizeAdjoining(DIVISION_SIDE_RIGHT, x, False)
+ elif division.GetHandleSide() == DIVISION_SIDE_BOTTOM:
+ if y <= y1 or y >= y2 or y <= dy1:
+ success = False
+ elif not division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, True):
+ success = False
+ else:
+ division.ResizeAdjoining(DIVISION_SIDE_BOTTOM, y, False)
+
+ if not success:
+ division.SetSize(originalW, originalH)
+ division.Move(dc, originalX, originalY)
+
+ divisionParent.Draw(dc)
+ division.GetEventHandler().OnDrawControlPoints(dc)
+
+
+
+DIVISION_MENU_SPLIT_HORIZONTALLY =1
+DIVISION_MENU_SPLIT_VERTICALLY =2
+DIVISION_MENU_EDIT_LEFT_EDGE =3
+DIVISION_MENU_EDIT_TOP_EDGE =4
+DIVISION_MENU_EDIT_RIGHT_EDGE =5
+DIVISION_MENU_EDIT_BOTTOM_EDGE =6
+DIVISION_MENU_DELETE_ALL =7
+
+
+
+class PopupDivisionMenu(wx.Menu):
+ def __init__(self):
+ wx.Menu.__init__(self)
+ self.Append(DIVISION_MENU_SPLIT_HORIZONTALLY,"Split horizontally")
+ self.Append(DIVISION_MENU_SPLIT_VERTICALLY,"Split vertically")
+ self.AppendSeparator()
+ self.Append(DIVISION_MENU_EDIT_LEFT_EDGE,"Edit left edge")
+ self.Append(DIVISION_MENU_EDIT_TOP_EDGE,"Edit top edge")
+
+ wx.EVT_MENU_RANGE(self, DIVISION_MENU_SPLIT_HORIZONTALLY, DIVISION_MENU_EDIT_BOTTOM_EDGE, self.OnMenu)
+
+ def SetClientData(self, data):
+ self._clientData = data
+
+ def GetClientData(self):
+ return self._clientData
+
+ def OnMenu(self, event):
+ division = self.GetClientData()
+ if event.GetId() == DIVISION_MENU_SPLIT_HORIZONTALLY:
+ division.Divide(wx.HORIZONTAL)
+ elif event.GetId() == DIVISION_MENU_SPLIT_VERTICALLY:
+ division.Divide(wx.VERTICAL)
+ elif event.GetId() == DIVISION_MENU_EDIT_LEFT_EDGE:
+ division.EditEdge(DIVISION_SIDE_LEFT)
+ elif event.GetId() == DIVISION_MENU_EDIT_TOP_EDGE:
+ division.EditEdge(DIVISION_SIDE_TOP)
+
+
+
+class DivisionShape(CompositeShape):
+ """A division shape is like a composite in that it can contain further
+ objects, but is used exclusively to divide another shape into regions,
+ or divisions. A wxDivisionShape is never free-standing.
+
+ Derived from:
+ wxCompositeShape
+ """
+ def __init__(self):
+ CompositeShape.__init__(self)
+ self.SetSensitivityFilter(OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_RIGHT)
+ self.SetCentreResize(False)
+ self.SetAttachmentMode(True)
+ self._leftSide = None
+ self._rightSide = None
+ self._topSide = None
+ self._bottomSide = None
+ self._handleSide = DIVISION_SIDE_NONE
+ self._leftSidePen = wx.BLACK_PEN
+ self._topSidePen = wx.BLACK_PEN
+ self._leftSideColour="BLACK"
+ self._topSideColour="BLACK"
+ self._leftSideStyle="Solid"
+ self._topSideStyle="Solid"
+ self.ClearRegions()
+
+ def SetLeftSide(self, shape):
+ """Set the the division on the left side of this division."""
+ self._leftSide = shape
+
+ def SetTopSide(self, shape):
+ """Set the the division on the top side of this division."""
+ self._topSide = shape
+
+ def SetRightSide(self, shape):
+ """Set the the division on the right side of this division."""
+ self._rightSide = shape
+
+ def SetBottomSide(self, shape):
+ """Set the the division on the bottom side of this division."""
+ self._bottomSide = shape
+
+ def GetLeftSide(self):
+ """Return the division on the left side of this division."""
+ return self._leftSide
+
+ def GetTopSide(self):
+ """Return the division on the top side of this division."""
+ return self._topSide
+
+ def GetRightSide(self):
+ """Return the division on the right side of this division."""
+ return self._rightSide
+
+ def GetBottomSide(self):
+ """Return the division on the bottom side of this division."""
+ return self._bottomSide
+
+ def SetHandleSide(self, side):
+ """Sets the side which the handle appears on.
+
+ Either DIVISION_SIDE_LEFT or DIVISION_SIDE_TOP.
+ """
+ self._handleSide = side
+
+ def GetHandleSide(self):
+ """Return the side which the handle appears on."""
+ return self._handleSide
+
+ def SetLeftSidePen(self, pen):
+ """Set the colour for drawing the left side of the division."""
+ self._leftSidePen = pen
+
+ def SetTopSidePen(self, pen):
+ """Set the colour for drawing the top side of the division."""
+ self._topSidePen = pen
+
+ def GetLeftSidePen(self):
+ """Return the pen used for drawing the left side of the division."""
+ return self._leftSidePen
+
+ def GetTopSidePen(self):
+ """Return the pen used for drawing the top side of the division."""
+ return self._topSidePen
+
+ def GetLeftSideColour(self):
+ """Return the colour used for drawing the left side of the division."""
+ return self._leftSideColour
+
+ def GetTopSideColour(self):
+ """Return the colour used for drawing the top side of the division."""
+ return self._topSideColour
+
+ def SetLeftSideColour(self, colour):
+ """Set the colour for drawing the left side of the division."""
+ self._leftSideColour = colour
+
+ def SetTopSideColour(self, colour):
+ """Set the colour for drawing the top side of the division."""
+ self._topSideColour = colour
+
+ def GetLeftSideStyle(self):
+ """Return the style used for the left side of the division."""
+ return self._leftSideStyle
+
+ def GetTopSideStyle(self):
+ """Return the style used for the top side of the division."""
+ return self._topSideStyle
+
+ def SetLeftSideStyle(self, style):
+ self._leftSideStyle = style
+
+ def SetTopSideStyle(self, style):
+ self._lefttopStyle = style
+
+ def OnDraw(self, dc):
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.SetBackgroundMode(wx.TRANSPARENT)
+
+ x1 = self.GetX()-self.GetWidth() / 2
+ y1 = self.GetY()-self.GetHeight() / 2
+ x2 = self.GetX() + self.GetWidth() / 2
+ y2 = self.GetY() + self.GetHeight() / 2
+
+ # Should subtract 1 pixel if drawing under Windows
+ if sys.platform[:3]=="win":
+ y2 -= 1
+
+ if self._leftSide:
+ dc.SetPen(self._leftSidePen)
+ dc.DrawLine(x1, y2, x1, y1)
+
+ if self._topSide:
+ dc.SetPen(self._topSidePen)
+ dc.DrawLine(x1, y1, x2, y1)
+
+ # For testing purposes, draw a rectangle so we know
+ # how big the division is.
+ #dc.SetBrush(wx.RED_BRUSH)
+ #dc.DrawRectangle(x1, y1, self.GetWidth(), self.GetHeight())
+
+ def OnDrawContents(self, dc):
+ CompositeShape.OnDrawContents(self, dc)
+
+ def OnMovePre(self, dc, x, y, oldx, oldy, display = True):
+ diffX = x-oldx
+ diffY = y-oldy
+ for object in self._children:
+ object.Erase(dc)
+ object.Move(dc, object.GetX() + diffX, object.GetY() + diffY, display)
+ return True
+
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
+ if self._parent:
+ hit = self._parent.HitTest(x, y)
+ if hit:
+ attachment, dist = hit
+ self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment)
+ return
+ Shape.OnDragLeft(self, draw, x, y, keys, attachment)
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
+ if self._parent:
+ hit = self._parent.HitTest(x, y)
+ if hit:
+ attachment, dist = hit
+ self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment)
+ return
+ Shape.OnBeginDragLeft(x, y, keys, attachment)
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ if self._canvas.HasCapture():
+ self._canvas.ReleaseMouse()
+ if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
+ if self._parent:
+ hit = self._parent.HitTest(x, y)
+ if hit:
+ attachment, dist = hit
+ self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment)
+ return
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ dc.SetLogicalFunction(wx.COPY)
+
+ self._xpos, self._ypos = self._canvas.Snap(self._xpos, self._ypos)
+ self.GetEventHandler().OnMovePre(dc, x, y, self._oldX, self._oldY)
+
+ self.ResetControlPoints()
+ self.Draw(dc)
+ self.MoveLinks(dc)
+ self.GetEventHandler().OnDrawControlPoints(dc)
+
+ if self._canvas and not self._canvas.GetQuickEditMode():
+ self._canvas.Redraw(dc)
+
+ def SetSize(self, w, h, recursive = True):
+ self._width = w
+ self._height = h
+ RectangleShape.SetSize(self, w, h, recursive)
+
+ def CalculateSize(self):
+ pass
+
+ # Experimental
+ def OnRightClick(self, x, y, keys = 0, attachment = 0):
+ if keys & KEY_CTRL:
+ self.PopupMenu(x, y)
+ else:
+ if self._parent:
+ hit = self._parent.HitTest(x, y)
+ if hit:
+ attachment, dist = hit
+ self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment)
+
+ # Divide wx.HORIZONTALly or wx.VERTICALly
+ def Divide(self, direction):
+ """Divide this division into two further divisions,
+ horizontally (direction is wxHORIZONTAL) or
+ vertically (direction is wxVERTICAL).
+ """
+ # Calculate existing top-left, bottom-right
+ x1 = self.GetX()-self.GetWidth() / 2
+ y1 = self.GetY()-self.GetHeight() / 2
+
+ compositeParent = self.GetParent()
+ oldWidth = self.GetWidth()
+ oldHeight = self.GetHeight()
+ if self.Selected():
+ self.Select(False)
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ if direction == wx.VERTICAL:
+ # Dividing vertically means notionally putting a horizontal
+ # line through it.
+ # Break existing piece into two.
+ newXPos1 = self.GetX()
+ newYPos1 = y1 + self.GetHeight() / 4
+ newXPos2 = self.GetX()
+ newYPos2 = y1 + 3 * self.GetHeight() / 4
+ newDivision = compositeParent.OnCreateDivision()
+ newDivision.Show(True)
+
+ self.Erase(dc)
+
+ # Anything adjoining the bottom of this division now adjoins the
+ # bottom of the new division.
+ for obj in compositeParent.GetDivisions():
+ if obj.GetTopSide() == self:
+ obj.SetTopSide(newDivision)
+
+ newDivision.SetTopSide(self)
+ newDivision.SetBottomSide(self._bottomSide)
+ newDivision.SetLeftSide(self._leftSide)
+ newDivision.SetRightSide(self._rightSide)
+ self._bottomSide = newDivision
+
+ compositeParent.GetDivisions().append(newDivision)
+
+ # CHANGE: Need to insert this division at start of divisions in the
+ # object list, because e.g.:
+ # 1) Add division
+ # 2) Add contained object
+ # 3) Add division
+ # Division is now receiving mouse events _before_ the contained
+ # object, because it was added last (on top of all others)
+
+ # Add after the image that visualizes the container
+ compositeParent.AddChild(newDivision, compositeParent.FindContainerImage())
+
+ self._handleSide = DIVISION_SIDE_BOTTOM
+ newDivision.SetHandleSide(DIVISION_SIDE_TOP)
+
+ self.SetSize(oldWidth, oldHeight / 2)
+ self.Move(dc, newXPos1, newYPos1)
+
+ newDivision.SetSize(oldWidth, oldHeight / 2)
+ newDivision.Move(dc, newXPos2, newYPos2)
+ else:
+ # Dividing horizontally means notionally putting a vertical line
+ # through it.
+ # Break existing piece into two.
+ newXPos1 = x1 + self.GetWidth() / 4
+ newYPos1 = self.GetY()
+ newXPos2 = x1 + 3 * self.GetWidth() / 4
+ newYPos2 = self.GetY()
+ newDivision = compositeParent.OnCreateDivision()
+ newDivision.Show(True)
+
+ self.Erase(dc)
+
+ # Anything adjoining the left of this division now adjoins the
+ # left of the new division.
+ for obj in compositeParent.GetDivisions():
+ if obj.GetLeftSide() == self:
+ obj.SetLeftSide(newDivision)
+
+ newDivision.SetTopSide(self._topSide)
+ newDivision.SetBottomSide(self._bottomSide)
+ newDivision.SetLeftSide(self)
+ newDivision.SetRightSide(self._rightSide)
+ self._rightSide = newDivision
+
+ compositeParent.GetDivisions().append(newDivision)
+ compositeParent.AddChild(newDivision, compositeParent.FindContainerImage())
+
+ self._handleSide = DIVISION_SIDE_RIGHT
+ newDivision.SetHandleSide(DIVISION_SIDE_LEFT)
+
+ self.SetSize(oldWidth / 2, oldHeight)
+ self.Move(dc, newXPos1, newYPos1)
+
+ newDivision.SetSize(oldWidth / 2, oldHeight)
+ newDivision.Move(dc, newXPos2, newYPos2)
+
+ if compositeParent.Selected():
+ compositeParent.DeleteControlPoints(dc)
+ compositeParent.MakeControlPoints()
+ compositeParent.MakeMandatoryControlPoints()
+
+ compositeParent.Draw(dc)
+ return True
+
+ def MakeControlPoints(self):
+ self.MakeMandatoryControlPoints()
+
+ def MakeMandatoryControlPoints(self):
+ maxX, maxY = self.GetBoundingBoxMax()
+ x = y = 0.0
+ direction = 0
+
+ if self._handleSide == DIVISION_SIDE_LEFT:
+ x=-maxX / 2
+ direction = CONTROL_POINT_HORIZONTAL
+ elif self._handleSide == DIVISION_SIDE_TOP:
+ y=-maxY / 2
+ direction = CONTROL_POINT_VERTICAL
+ elif self._handleSide == DIVISION_SIDE_RIGHT:
+ x = maxX / 2
+ direction = CONTROL_POINT_HORIZONTAL
+ elif self._handleSide == DIVISION_SIDE_BOTTOM:
+ y = maxY / 2
+ direction = CONTROL_POINT_VERTICAL
+
+ if self._handleSide != DIVISION_SIDE_NONE:
+ control = DivisionControlPoint(self._canvas, self, CONTROL_POINT_SIZE, x, y, direction)
+ self._canvas.AddShape(control)
+ self._controlPoints.append(control)
+
+ def ResetControlPoints(self):
+ self.ResetMandatoryControlPoints()
+
+ def ResetMandatoryControlPoints(self):
+ if not self._controlPoints:
+ return
+
+ maxX, maxY = self.GetBoundingBoxMax()
+
+ node = self._controlPoints[0]
+
+ if self._handleSide == DIVISION_SIDE_LEFT and node:
+ node._xoffset=-maxX / 2
+ node._yoffset = 0.0
+
+ if self._handleSide == DIVISION_SIDE_TOP and node:
+ node._xoffset = 0.0
+ node._yoffset=-maxY / 2
+
+ if self._handleSide == DIVISION_SIDE_RIGHT and node:
+ node._xoffset = maxX / 2
+ node._yoffset = 0.0
+
+ if self._handleSide == DIVISION_SIDE_BOTTOM and node:
+ node._xoffset = 0.0
+ node._yoffset = maxY / 2
+
+ def AdjustLeft(self, left, test):
+ """Adjust a side.
+
+ Returns FALSE if it's not physically possible to adjust it to
+ this point.
+ """
+ x2 = self.GetX() + self.GetWidth() / 2
+
+ if left >= x2:
+ return False
+
+ if test:
+ return True
+
+ newW = x2-left
+ newX = left + newW / 2
+ self.SetSize(newW, self.GetHeight())
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ self.Move(dc, newX, self.GetY())
+ return True
+
+ def AdjustTop(self, top, test):
+ """Adjust a side.
+
+ Returns FALSE if it's not physically possible to adjust it to
+ this point.
+ """
+ y2 = self.GetY() + self.GetHeight() / 2
+
+ if top >= y2:
+ return False
+
+ if test:
+ return True
+
+ newH = y2-top
+ newY = top + newH / 2
+ self.SetSize(self.GetWidth(), newH)
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ self.Move(dc, self.GetX(), newY)
+ return True
+
+ def AdjustRight(self, right, test):
+ """Adjust a side.
+
+ Returns FALSE if it's not physically possible to adjust it to
+ this point.
+ """
+ x1 = self.GetX()-self.GetWidth() / 2
+
+ if right <= x1:
+ return False
+
+ if test:
+ return True
+
+ newW = right-x1
+ newX = x1 + newW / 2
+ self.SetSize(newW, self.GetHeight())
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ self.Move(dc, newX, self.GetY())
+ return True
+
+ def AdjustTop(self, top, test):
+ """Adjust a side.
+
+ Returns FALSE if it's not physically possible to adjust it to
+ this point.
+ """
+ y1 = self.GetY()-self.GetHeight() / 2
+
+ if bottom <= y1:
+ return False
+
+ if test:
+ return True
+
+ newH = bottom-y1
+ newY = y1 + newH / 2
+ self.SetSize(self.GetWidth(), newH)
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ self.Move(dc, self.GetX(), newY)
+ return True
+
+ # Resize adjoining divisions.
+
+ # Behaviour should be as follows:
+ # If right edge moves, find all objects whose left edge
+ # adjoins this object, and move left edge accordingly.
+ # If left..., move ... right.
+ # If top..., move ... bottom.
+ # If bottom..., move top.
+ # If size goes to zero or end position is other side of start position,
+ # resize to original size and return.
+ #
+ def ResizeAdjoining(self, side, newPos, test):
+ """Resize adjoining divisions at the given side.
+
+ If test is TRUE, just see whether it's possible for each adjoining
+ region, returning FALSE if it's not.
+
+ side can be one of:
+
+ * DIVISION_SIDE_NONE
+ * DIVISION_SIDE_LEFT
+ * DIVISION_SIDE_TOP
+ * DIVISION_SIDE_RIGHT
+ * DIVISION_SIDE_BOTTOM
+ """
+ divisionParent = self.GetParent()
+ for division in divisionParent.GetDivisions():
+ if side == DIVISION_SIDE_LEFT:
+ if division._rightSide == self:
+ success = division.AdjustRight(newPos, test)
+ if not success and test:
+ return false
+ elif side == DIVISION_SIDE_TOP:
+ if division._bottomSide == self:
+ success = division.AdjustBottom(newPos, test)
+ if not success and test:
+ return False
+ elif side == DIVISION_SIDE_RIGHT:
+ if division._leftSide == self:
+ success = division.AdjustLeft(newPos, test)
+ if not success and test:
+ return False
+ elif side == DIVISION_SIDE_BOTTOM:
+ if division._topSide == self:
+ success = division.AdjustTop(newPos, test)
+ if not success and test:
+ return False
+ return True
+
+ def EditEdge(self, side):
+ print "EditEdge() not implemented."
+
+ def PopupMenu(self, x, y):
+ menu = PopupDivisionMenu()
+ menu.SetClientData(self)
+ if self._leftSide:
+ menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, True)
+ else:
+ menu.Enable(DIVISION_MENU_EDIT_LEFT_EDGE, False)
+ if self._topSide:
+ menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, True)
+ else:
+ menu.Enable(DIVISION_MENU_EDIT_TOP_EDGE, False)
+
+ x1, y1 = self._canvas.GetViewStart()
+ unit_x, unit_y = self._canvas.GetScrollPixelsPerUnit()
+
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ mouse_x = dc.LogicalToDeviceX(x-x1 * unit_x)
+ mouse_y = dc.LogicalToDeviceY(y-y1 * unit_y)
+
+ self._canvas.PopupMenu(menu, (mouse_x, mouse_y))
+
+
--- /dev/null
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name: diagram.py
+# Purpose: Diagram class
+#
+# Author: Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created: 20040508
+# RCS-ID:
+# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# Licence: wxWindows license
+#----------------------------------------------------------------------------
+
+from __future__ import division
+
+import wx
+
+DEFAULT_MOUSE_TOLERANCE = 3
+
+
+
+class Diagram(object):
+ """Encapsulates an entire diagram, with methods for drawing. A diagram has
+ an associated ShapeCanvas.
+
+ Derived from:
+ Object
+ """
+ def __init__(self):
+ self._diagramCanvas = None
+ self._quickEditMode = False
+ self._snapToGrid = True
+ self._gridSpacing = 5.0
+ self._shapeList = []
+ self._mouseTolerance = DEFAULT_MOUSE_TOLERANCE
+
+ def Redraw(self, dc):
+ """Draw the shapes in the diagram on the specified device context."""
+ if self._shapeList:
+ if self.GetCanvas():
+ self.GetCanvas().SetCursor(wx.HOURGLASS_CURSOR)
+ for object in self._shapeList:
+ object.Draw(dc)
+ if self.GetCanvas():
+ self.GetCanvas().SetCursor(wx.STANDARD_CURSOR)
+
+ def Clear(self, dc):
+ """Clear the specified device context."""
+ dc.Clear()
+
+ def AddShape(self, object, addAfter = None):
+ """Adds a shape to the diagram. If addAfter is not None, the shape
+ will be added after addAfter.
+ """
+ if not object in self._shapeList:
+ if addAfter:
+ self._shapeList.insert(self._shapeList.index(addAfter) + 1, object)
+ else:
+ self._shapeList.append(object)
+
+ object.SetCanvas(self.GetCanvas())
+
+ def InsertShape(self, object):
+ """Insert a shape at the front of the shape list."""
+ self._shapeList.insert(0, object)
+
+ def RemoveShape(self, object):
+ """Remove the shape from the diagram (non-recursively) but do not
+ delete it.
+ """
+ if object in self._shapeList:
+ self._shapeList.remove(object)
+
+ def RemoveAllShapes(self):
+ """Remove all shapes from the diagram but do not delete the shapes."""
+ self._shapeList = []
+
+ def DeleteAllShapes(self):
+ """Remove and delete all shapes in the diagram."""
+ for shape in self._shapeList[:]:
+ if not shape.GetParent():
+ self.RemoveShape(shape)
+
+ def ShowAll(self, show):
+ """Call Show for each shape in the diagram."""
+ for shape in self._shapeList:
+ shape.Show()
+
+ def DrawOutLine(self, dc, x1, y1, x2, y2):
+ """Draw an outline rectangle on the current device context."""
+ dc.SetPen(wx.Pen(wx.Color(0, 0, 0), 1, wx.DOT))
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ dc.DrawLines([[x1, y1], [x2, y1], [x2, y2], [x1, y2], [x1, y1]])
+
+ def RecentreAll(self, dc):
+ """Make sure all text that should be centred, is centred."""
+ for shape in self._shapeList:
+ shape.Recentre(dc)
+
+ def SetCanvas(self, canvas):
+ """Set the canvas associated with this diagram."""
+ self._diagramCanvas = canvas
+
+ def GetCanvas(self):
+ """Return the shape canvas associated with this diagram."""
+ return self._diagramCanvas
+
+ def FindShape(self, id):
+ """Return the shape for the given identifier."""
+ for shape in self._shapeList:
+ if shape.GetId() == id:
+ return shape
+ return None
+
+ def Snap(self, x, y):
+ """'Snaps' the coordinate to the nearest grid position, if
+ snap-to-grid is on."""
+ if self._snapToGrid:
+ return self._gridSpacing * int(x / self._gridSpacing + 0.5), self._gridSpacing * int(y / self._gridSpacing + 0.5)
+ return x, y
+
+ def GetGridSpacing(self):
+ """Return the grid spacing."""
+ return self._gridSpacing
+
+ def GetSnapToGrid(self):
+ """Return snap-to-grid mode."""
+ return self._snapToGrid
+
+ def SetQuickEditMode(self, mode):
+ """Set quick-edit-mode on of off.
+
+ In this mode, refreshes are minimized, but the diagram may need
+ manual refreshing occasionally.
+ """
+ self._quickEditMode = mode
+
+ def GetQuickEditMode(self):
+ """Return quick edit mode."""
+ return self._quickEditMode
+
+ def SetMouseTolerance(self, tolerance):
+ """Set the tolerance within which a mouse move is ignored.
+
+ The default is 3 pixels.
+ """
+ self._mouseTolerance = tolerance
+
+ def GetMouseTolerance(self):
+ """Return the tolerance within which a mouse move is ignored."""
+ return self._mouseTolerance
+
+ def GetShapeList(self):
+ """Return the internal shape list."""
+ return self._shapeList
+
+ def GetCount(self):
+ """Return the number of shapes in the diagram."""
+ return len(self._shapeList)
--- /dev/null
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name: divided.py
+# Purpose: DividedShape class
+#
+# Author: Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created: 20040508
+# RCS-ID:
+# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# Licence: wxWindows license
+#----------------------------------------------------------------------------
+
+from __future__ import division
+
+import sys
+import wx
+
+from basic import ControlPoint, RectangleShape, Shape
+from oglmisc import *
+
+
+
+class DividedShapeControlPoint(ControlPoint):
+ def __init__(self, the_canvas, object, region, size, the_m_xoffset, the_m_yoffset, the_type):
+ ControlPoint.__init__(self, the_canvas, object, size, the_m_xoffset, the_m_yoffset, the_type)
+ self.regionId = region
+
+ # Implement resizing of divided object division
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ 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)
+
+ dividedObject = self._shape
+ x1 = dividedObject.GetX()-dividedObject.GetWidth() / 2
+ y1 = y
+ x2 = dividedObject.GetX() + dividedObject.GetWidth() / 2
+ y2 = y
+
+ dc.DrawLine(x1, y1, x2, y2)
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ 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)
+
+ dividedObject = self._shape
+
+ x1 = dividedObject.GetX()-dividedObject.GetWidth() / 2
+ y1 = y
+ x2 = dividedObject.GetX() + dividedObject.GetWidth() / 2
+ y2 = y
+
+ dc.DrawLine(x1, y1, x2, y2)
+ self._canvas.CaptureMouse()
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ dividedObject = self._shape
+ if not dividedObject.GetRegions()[self.regionId]:
+ return
+
+ thisRegion = dividedObject.GetRegions()[self.regionId]
+ nextRegion = None
+
+ dc.SetLogicalFunction(wx.COPY)
+
+ if self._canvas.HasCapture():
+ self._canvas.ReleaseMouse()
+
+ # Find the old top and bottom of this region,
+ # and calculate the new proportion for this region
+ # if legal.
+ currentY = dividedObject.GetY()-dividedObject.GetHeight() / 2
+ maxY = dividedObject.GetY() + dividedObject.GetHeight() / 2
+
+ # Save values
+ theRegionTop = 0
+ nextRegionBottom = 0
+
+ for i in range(len(dividedObject.GetRegions())):
+ region = dividedObject.GetRegions()[i]
+ proportion = region._regionProportionY
+ yy = currentY + dividedObject.GetHeight() * proportion
+ actualY = min(maxY, yy)
+
+ if region == thisRegion:
+ thisRegionTop = currentY
+
+ if i + 1<len(dividedObject.GetRegions()):
+ nextRegion = dividedObject.GetRegions()[i + 1]
+ if region == nextRegion:
+ nextRegionBottom = actualY
+
+ currentY = actualY
+
+ if not nextRegion:
+ return
+
+ # Check that we haven't gone above this region or below
+ # next region.
+ if y <= thisRegionTop or y >= nextRegionBottom:
+ return
+
+ dividedObject.EraseLinks(dc)
+
+ # Now calculate the new proportions of this region and the next region
+ thisProportion = (y-thisRegionTop) / dividedObject.GetHeight()
+ nextProportion = (nextRegionBottom-y) / dividedObject.GetHeight()
+
+ thisRegion.SetProportions(0, thisProportion)
+ nextRegion.SetProportions(0, nextProportion)
+ self._yoffset = y-dividedObject.GetY()
+
+ # Now reformat text
+ for i, region in enumerate(dividedObject.GetRegions()):
+ if region.GetText():
+ s = region.GetText()
+ dividedObject.FormatText(dc, s, i)
+
+ dividedObject.SetRegionSizes()
+ dividedObject.Draw(dc)
+ dividedObject.GetEventHandler().OnMoveLinks(dc)
+
+
+
+class DividedShape(RectangleShape):
+ """A DividedShape is a rectangle with a number of vertical divisions.
+ Each division may have its text formatted with independent characteristics,
+ and the size of each division relative to the whole image may be specified.
+
+ Derived from:
+ RectangleShape
+ """
+ def __init__(self, w, h):
+ RectangleShape.__init__(self, w, h)
+ self.ClearRegions()
+
+ def OnDraw(self, dc):
+ RectangleShape.OnDraw(self, dc)
+
+ def OnDrawContents(self, dc):
+ if self.GetRegions():
+ defaultProportion = 1 / len(self.GetRegions())
+ else:
+ defaultProportion = 0
+ currentY = self._ypos-self._height / 2
+ maxY = self._ypos + self._height / 2
+
+ leftX = self._xpos-self._width / 2
+ rightX = self._xpos + self._width / 2
+
+ if self._pen:
+ dc.SetPen(self._pen)
+
+ dc.SetTextForeground(self._textColour)
+
+ # For efficiency, don't do this under X - doesn't make
+ # any visible difference for our purposes.
+ if sys.platform[:3]=="win":
+ dc.SetTextBackground(self._brush.GetColour())
+
+ if self.GetDisableLabel():
+ return
+
+ xMargin = 2
+ yMargin = 2
+
+ dc.SetBackgroundMode(wx.TRANSPARENT)
+
+ for region in self.GetRegions():
+ dc.SetFont(region.GetFont())
+ dc.SetTextForeground(region.GetActualColourObject())
+
+ if region._regionProportionY<0:
+ proportion = defaultProportion
+ else:
+ proportion = region._regionProportionY
+
+ y = currentY + self._height * proportion
+ actualY = min(maxY, y)
+
+ centreX = self._xpos
+ centreY = currentY + (actualY-currentY) / 2
+
+ DrawFormattedText(dc, region._formattedText, centreX, centreY, self._width-2 * xMargin, actualY-currentY-2 * yMargin, region._formatMode)
+
+ if y <= maxY and region != self.GetRegions()[-1]:
+ regionPen = region.GetActualPen()
+ if regionPen:
+ dc.SetPen(regionPen)
+ dc.DrawLine(leftX, y, rightX, y)
+
+ currentY = actualY
+
+ def SetSize(self, w, h, recursive = True):
+ self.SetAttachmentSize(w, h)
+ self._width = w
+ self._height = h
+ self.SetRegionSizes()
+
+ def SetRegionSizes(self):
+ """Set all region sizes according to proportions and this object
+ total size.
+ """
+ if not self.GetRegions():
+ return
+
+ if self.GetRegions():
+ defaultProportion = 1 / len(self.GetRegions())
+ else:
+ defaultProportion = 0
+ currentY = self._ypos-self._height / 2
+ maxY = self._ypos + self._height / 2
+
+ for region in self.GetRegions():
+ if region._regionProportionY <= 0:
+ proportion = defaultProportion
+ else:
+ proportion = region._regionProportionY
+
+ sizeY = proportion * self._height
+ y = currentY + sizeY
+ actualY = min(maxY, y)
+
+ centreY = currentY + (actualY-currentY) / 2
+
+ region.SetSize(self._width, sizeY)
+ region.SetPosition(0, centreY-self._ypos)
+
+ currentY = actualY
+
+ # Attachment points correspond to regions in the divided box
+ def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
+ totalNumberAttachments = len(self.GetRegions()) * 2 + 2
+ if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE or attachment >= totalNumberAttachments:
+ return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs)
+
+ n = len(self.GetRegions())
+ isEnd = line and line.IsEnd(self)
+
+ left = self._xpos-self._width / 2
+ right = self._xpos + self._width / 2
+ top = self._ypos-self._height / 2
+ bottom = self._ypos + self._height / 2
+
+ # Zero is top, n + 1 is bottom
+ if attachment == 0:
+ y = top
+ if self._spaceAttachments:
+ if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ # Align line according to the next handle along
+ point = line.GetNextControlPoint(self)
+ if point.x<left:
+ x = left
+ elif point.x>right:
+ x = right
+ else:
+ x = point.x
+ else:
+ x = left + (nth + 1) * self._width / (no_arcs + 1)
+ else:
+ x = self._xpos
+ elif attachment == n + 1:
+ y = bottom
+ if self._spaceAttachments:
+ if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ # Align line according to the next handle along
+ point = line.GetNextControlPoint(self)
+ if point.x<left:
+ x = left
+ elif point.x>right:
+ x = right
+ else:
+ x = point.x
+ else:
+ x = left + (nth + 1) * self._width / (no_arcs + 1)
+ else:
+ x = self._xpos
+ else: # Left or right
+ isLeft = not attachment<(n + 1)
+ if isLeft:
+ i = totalNumberAttachments-attachment-1
+ else:
+ i = attachment-1
+
+ region = self.GetRegions()[i]
+ if region:
+ if isLeft:
+ x = left
+ else:
+ x = right
+
+ # Calculate top and bottom of region
+ top = self._ypos + region._y-region._height / 2
+ bottom = self._ypos + region._y + region._height / 2
+
+ # Assuming we can trust the absolute size and
+ # position of these regions
+ if self._spaceAttachments:
+ if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ # Align line according to the next handle along
+ point = line.GetNextControlPoint(self)
+ if point.y<bottom:
+ y = bottom
+ elif point.y>top:
+ y = top
+ else:
+ y = point.y
+ else:
+ y = top + (nth + 1) * region._height / (no_arcs + 1)
+ else:
+ y = self._ypos + region._y
+ else:
+ return False
+ return x, y
+
+ def GetNumberOfAttachments(self):
+ # There are two attachments for each region (left and right),
+ # plus one on the top and one on the bottom.
+ n = len(self.GetRegions()) * 2 + 2
+
+ maxN = n-1
+ for point in self._attachmentPoints:
+ if point._id>maxN:
+ maxN = point._id
+
+ return maxN + 1
+
+ def AttachmentIsValid(self, attachment):
+ totalNumberAttachments = len(self.GetRegions()) * 2 + 2
+ if attachment >= totalNumberAttachments:
+ return Shape.AttachmentIsValid(self, attachment)
+ else:
+ return attachment >= 0
+
+ def MakeControlPoints(self):
+ RectangleShape.MakeControlPoints(self)
+ self.MakeMandatoryControlPoints()
+
+ def MakeMandatoryControlPoints(self):
+ currentY = self.GetY()-self._height / 2
+ maxY = self.GetY() + self._height / 2
+
+ for i, region in enumerate(self.GetRegions()):
+ proportion = region._regionProportionY
+
+ y = currentY + self._height * proportion
+ actualY = min(maxY, y)
+
+ if region != self.GetRegions()[-1]:
+ controlPoint = DividedShapeControlPoint(self._canvas, self, i, CONTROL_POINT_SIZE, 0, actualY-self.GetY(), 0)
+ self._canvas.AddShape(controlPoint)
+ self._controlPoints.append(controlPoint)
+
+ currentY = actualY
+
+ def ResetControlPoints(self):
+ # May only have the region handles, (n - 1) of them
+ if len(self._controlPoints)>len(self.GetRegions())-1:
+ RectangleShape.ResetControlPoints(self)
+
+ self.ResetMandatoryControlPoints()
+
+ def ResetMandatoryControlPoints(self):
+ currentY = self.GetY()-self._height / 2
+ maxY = self.GetY() + self._height / 2
+
+ i = 0
+ for controlPoint in self._controlPoints:
+ if isinstance(controlPoint, DividedShapeControlPoint):
+ region = self.GetRegions()[i]
+ proportion = region._regionProportionY
+
+ y = currentY + self._height * proportion
+ actualY = min(maxY, y)
+
+ controlPoint._xoffset = 0
+ controlPoint._yoffset = actualY-self.GetY()
+
+ currentY = actualY
+
+ i += 1
+
+ def EditRegions(self):
+ """Edit the region colours and styles. Not implemented."""
+ print "EditRegions() is unimplemented"
+
+ def OnRightClick(self, x, y, keys = 0, attachment = 0):
+ if keys & KEY_CTRL:
+ self.EditRegions()
+ else:
+ RectangleShape.OnRightClick(self, x, y, keys, attachment)
--- /dev/null
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name: lines.py
+# Purpose: LineShape class
+#
+# Author: Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created: 20040508
+# RCS-ID:
+# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# Licence: wxWindows license
+#----------------------------------------------------------------------------
+
+from __future__ import division
+
+import sys
+
+from math import sqrt
+
+from basic import Shape, ShapeRegion, ControlPoint, RectangleShape
+from oglmisc import *
+
+# Line alignment flags
+# Vertical by default
+LINE_ALIGNMENT_HORIZ= 1
+LINE_ALIGNMENT_VERT= 0
+LINE_ALIGNMENT_TO_NEXT_HANDLE= 2
+LINE_ALIGNMENT_NONE= 0
+
+
+
+class LineControlPoint(ControlPoint):
+ def __init__(self, theCanvas = None, object = None, size = 0.0, x = 0.0, y = 0.0, the_type = 0):
+ ControlPoint.__init__(self, theCanvas, object, size, x, y, the_type)
+ self._xpos = x
+ self._ypos = y
+ self._type = the_type
+ self._point = None
+ self._originalPos = None
+
+ def OnDraw(self, dc):
+ RectangleShape.OnDraw(self, dc)
+
+ # Implement movement of Line point
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
+
+
+
+class ArrowHead(object):
+ def __init__(self, type = 0, end = 0, size = 0.0, dist = 0.0, name="",mf = None, arrowId=-1):
+ if isinstance(type, ArrowHead):
+ pass
+ else:
+ self._arrowType = type
+ self._arrowEnd = end
+ self._arrowSize = size
+ self._xOffset = dist
+ self._yOffset = 0.0
+ self._spacing = 5.0
+
+ self._arrowName = name
+ self._metaFile = mf
+ self._id = arrowId
+ if self._id==-1:
+ self._id = wx.NewId()
+
+ def _GetType(self):
+ return self._arrowType
+
+ def GetPosition(self):
+ return self._arrowEnd
+
+ def SetPosition(self, pos):
+ self._arrowEnd = pos
+
+ def GetXOffset(self):
+ return self._xOffset
+
+ def GetYOffset(self):
+ return self._yOffset
+
+ def GetSpacing(self):
+ return self._spacing
+
+ def GetSize(self):
+ return self._arrowSize
+
+ def SetSize(self, size):
+ self._arrowSize = size
+ if self._arrowType == ARROW_METAFILE and self._metaFile:
+ oldWidth = self._metaFile._width
+ if oldWidth == 0:
+ return
+
+ scale = size / oldWidth
+ if scale != 1:
+ self._metaFile.Scale(scale, scale)
+
+ def GetName(self):
+ return self._arrowName
+
+ def SetXOffset(self, x):
+ self._xOffset = x
+
+ def SetYOffset(self, y):
+ self._yOffset = y
+
+ def GetMetaFile(self):
+ return self._metaFile
+
+ def GetId(self):
+ return self._id
+
+ def GetArrowEnd(self):
+ return self._arrowEnd
+
+ def GetArrowSize(self):
+ return self._arrowSize
+
+ def SetSpacing(self, sp):
+ self._spacing = sp
+
+
+
+class LabelShape(RectangleShape):
+ def __init__(self, parent, region, w, h):
+ RectangleShape.__init__(self, w, h)
+ self._lineShape = parent
+ self._shapeRegion = region
+ self.SetPen(wx.ThePenList.FindOrCreatePen(wx.Colour(0, 0, 0), 1, wx.DOT))
+
+ def OnDraw(self, dc):
+ if self._lineShape and not self._lineShape.GetDrawHandles():
+ return
+
+ x1 = self._xpos-self._width / 2
+ y1 = self._ypos-self._height / 2
+
+ if self._pen:
+ if self._pen.GetWidth() == 0:
+ dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT))
+ else:
+ dc.SetPen(self._pen)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ if self._cornerRadius>0:
+ dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
+ else:
+ dc.DrawRectangle(x1, y1, self._width, self._height)
+
+ def OnDrawContents(self, dc):
+ pass
+
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ RectangleShape.OnDragLeft(self, draw, x, y, keys, attachment)
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ RectangleShape.OnBeginDragLeft(self, x, y, keys, attachment)
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ RectangleShape.OnEndDragLeft(self, x, y, keys, attachment)
+
+ def OnMovePre(self, dc, x, y, old_x, old_y, display):
+ return self._lineShape.OnLabelMovePre(dc, self, x, y, old_x, old_y, display)
+
+ # Divert left and right clicks to line object
+ def OnLeftClick(self, x, y, keys = 0, attachment = 0):
+ self._lineShape.GetEventHandler().OnLeftClick(x, y, keys, attachment)
+
+ def OnRightClick(self, x, y, keys = 0, attachment = 0):
+ self._lineShape.GetEventHandler().OnRightClick(x, y, keys, attachment)
+
+
+
+class LineShape(Shape):
+ """LineShape may be attached to two nodes;
+ it may be segmented, in which case a control point is drawn for each joint.
+
+ A wxLineShape may have arrows at the beginning, end and centre.
+
+ Derived from:
+ Shape
+ """
+ def __init__(self):
+ Shape.__init__(self)
+
+ self._sensitivity = OP_CLICK_LEFT | OP_CLICK_RIGHT
+ self._draggable = False
+ self._attachmentTo = 0
+ self._attachmentFrom = 0
+ self._from = None
+ self._to = None
+ self._erasing = False
+ self._arrowSpacing = 5.0
+ self._ignoreArrowOffsets = False
+ self._isSpline = False
+ self._maintainStraightLines = False
+ self._alignmentStart = 0
+ self._alignmentEnd = 0
+
+ self._lineControlPoints = None
+
+ # Clear any existing regions (created in an earlier constructor)
+ # and make the three line regions.
+ self.ClearRegions()
+ for name in ["Middle","Start","End"]:
+ newRegion = ShapeRegion()
+ newRegion.SetName(name)
+ newRegion.SetSize(150, 50)
+ self._regions.append(newRegion)
+
+ self._labelObjects = [None, None, None]
+ self._lineOrientations = []
+ self._lineControlPoints = []
+ self._arcArrows = []
+
+ def __del__(self):
+ if self._lineControlPoints:
+ self.ClearPointList(self._lineControlPoints)
+ self._lineControlPoints = []
+ for i in range(3):
+ if self._labelObjects[i]:
+ self._labelObjects[i].Select(False)
+ self._labelObjects[i].RemoveFromCanvas(self._canvas)
+ self._labelObjects = []
+ self.ClearArrowsAtPosition(-1)
+
+ def GetFrom(self):
+ """Return the 'from' object."""
+ return self._from
+
+ def GetTo(self):
+ """Return the 'to' object."""
+ return self._to
+
+ def GetAttachmentFrom(self):
+ """Return the attachment point on the 'from' node."""
+ return self._attachmentFrom
+
+ def GetAttachmentTo(self):
+ """Return the attachment point on the 'to' node."""
+ return self._attachmentTo
+
+ def GetLineControlPoints(self):
+ return self._lineControlPoints
+
+ def SetSpline(self, spline):
+ """Specifies whether a spline is to be drawn through the control points."""
+ self._isSpline = spline
+
+ def IsSpline(self):
+ """TRUE if a spline is drawn through the control points."""
+ return self._isSpline
+
+ def SetAttachmentFrom(self, attach):
+ """Set the 'from' shape attachment."""
+ self._attachmentFrom = attach
+
+ def SetAttachmentTo(self, attach):
+ """Set the 'to' shape attachment."""
+ self._attachmentTo = attach
+
+ # This is really to distinguish between lines and other images.
+ # For lines, want to pass drag to canvas, since lines tend to prevent
+ # dragging on a canvas (they get in the way.)
+ def Draggable(self):
+ return False
+
+ def SetIgnoreOffsets(self, ignore):
+ """Set whether to ignore offsets from the end of the line when drawing."""
+ self._ignoreArrowOffsets = ignore
+
+ def GetArrows(self):
+ return self._arcArrows
+
+ def GetAlignmentStart(self):
+ return self._alignmentStart
+
+ def GetAlignmentEnd(self):
+ return self._alignmentEnd
+
+ def IsEnd(self, nodeObject):
+ """TRUE if shape is at the end of the line."""
+ return self._to == nodeObject
+
+ def MakeLineControlPoints(self, n):
+ """Make a given number of control points (minimum of two)."""
+ if self._lineControlPoints:
+ self.ClearPointList(self._lineControlPoints)
+ self._lineControlPoints = []
+
+ for _ in range(n):
+ point = wx.RealPoint(-999,-999)
+ self._lineControlPoints.append(point)
+
+ def InsertLineControlPoint(self, dc = None):
+ """Insert a control point at an arbitrary position."""
+ if dc:
+ self.Erase(dc)
+
+ last_point = self._lineControlPoints[-1]
+ second_last_point = self._lineControlPoints[-2]
+
+ line_x = (last_point[0] + second_last_point[0]) / 2
+ line_y = (last_point[1] + second_last_point[1]) / 2
+
+ point = wx.RealPoint(line_x, line_y)
+ self._lineControlPoints.insert(len(self._lineControlPoints), point)
+
+ def DeleteLineControlPoint(self):
+ """Delete an arbitary point on the line."""
+ if len(self._lineControlPoints)<3:
+ return False
+
+ del self._lineControlPoints[-2]
+ return True
+
+ def Initialise(self):
+ """Initialise the line object."""
+ if self._lineControlPoints:
+ # Just move the first and last control points
+ first_point = self._lineControlPoints[0]
+ last_point = self._lineControlPoints[-1]
+
+ # If any of the line points are at -999, we must
+ # initialize them by placing them half way between the first
+ # and the last.
+
+ for point in self._lineControlPoints[1:]:
+ if point[0]==-999:
+ if first_point[0]<last_point[0]:
+ x1 = first_point[0]
+ x2 = last_point[0]
+ else:
+ x2 = first_point[0]
+ x1 = last_point[0]
+ if first_point[1]<last_point[1]:
+ y1 = first_point[1]
+ y2 = last_point[1]
+ else:
+ y2 = first_point[1]
+ y1 = last_point[1]
+ point[0] = (x2-x1) / 2 + x1
+ point[1] = (y2-y1) / 2 + y1
+
+ def FormatText(self, dc, s, i):
+ """Format a text string according to the region size, adding
+ strings with positions to region text list.
+ """
+ self.ClearText(i)
+
+ if len(self._regions) == 0 or i >= len(self._regions):
+ return
+
+ region = self._regions[i]
+ region.SetText(s)
+ dc.SetFont(region.GetFont())
+
+ w, h = region.GetSize()
+ # Initialize the size if zero
+ if (w == 0 or h == 0) and s:
+ w, h = 100, 50
+ region.SetSize(w, h)
+
+ string_list = FormatText(dc, s, w-5, h-5, region.GetFormatMode())
+ for s in string_list:
+ line = ShapeTextLine(0.0, 0.0, s)
+ region.GetFormattedText().append(line)
+
+ actualW = w
+ actualH = h
+ if region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS:
+ actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText(), self._xpos, self._ypos, w, h)
+ if actualW != w or actualH != h:
+ xx, yy = self.GetLabelPosition(i)
+ self.EraseRegion(dc, region, xx, yy)
+ if len(self._labelObjects)<i:
+ self._labelObjects[i].Select(False, dc)
+ self._labelObjects[i].Erase(dc)
+ self._labelObjects[i].SetSize(actualW, actualH)
+
+ region.SetSize(actualW, actualH)
+
+ if len(self._labelObjects)<i:
+ self._labelObjects[i].Select(True, dc)
+ self._labelObjects[i].Draw(dc)
+
+ CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW, actualH, region.GetFormatMode())
+ self._formatted = True
+
+ def DrawRegion(self, dc, region, x, y):
+ """Format one region at this position."""
+ if self.GetDisableLabel():
+ return
+
+ w, h = region.GetSize()
+
+ # Get offset from x, y
+ xx, yy = region.GetPosition()
+
+ xp = xx + x
+ yp = yy + y
+
+ # First, clear a rectangle for the text IF there is any
+ if len(region.GetFormattedText()):
+ dc.SetPen(self.GetBackgroundPen())
+ dc.SetBrush(self.GetBackgroundBrush())
+
+ # Now draw the text
+ if region.GetFont():
+ dc.SetFont(region.GetFont())
+ dc.DrawRectangle(xp-w / 2, yp-h / 2, w, h)
+
+ if self._pen:
+ dc.SetPen(self._pen)
+ dc.SetTextForeground(region.GetActualColourObject())
+
+ DrawFormattedText(dc, region.GetFormattedText(), xp, yp, w, h, region.GetFormatMode())
+
+ def EraseRegion(self, dc, region, x, y):
+ """Erase one region at this position."""
+ if self.GetDisableLabel():
+ return
+
+ w, h = region.GetSize()
+
+ # Get offset from x, y
+ xx, yy = region.GetPosition()
+
+ xp = xx + x
+ yp = yy + y
+
+ if region.GetFormattedText():
+ dc.SetPen(self.GetBackgroundPen())
+ dc.SetBrush(self.GetBackgroundBrush())
+
+ dc.DrawRectangle(xp-w / 2, yp-h / 2, w, h)
+
+ def GetLabelPosition(self, position):
+ """Get the reference point for a label.
+
+ Region x and y are offsets from this.
+ position is 0 (middle), 1 (start), 2 (end).
+ """
+ if position == 0:
+ # Want to take the middle section for the label
+ half_way = int(len(self._lineControlPoints) / 2)
+
+ # Find middle of this line
+ point = self._lineControlPoints[half_way-1]
+ next_point = self._lineControlPoints[half_way]
+
+ dx = next_point[0]-point[0]
+ dy = next_point[1]-point[1]
+
+ return point[0] + dx / 2, point[1] + dy / 2
+ elif position == 1:
+ return self._lineControlPoints[0][0], self._lineControlPoints[0][1]
+ elif position == 2:
+ return self._lineControlPoints[-1][0], self._lineControlPoints[-1][1]
+
+ def Straighten(self, dc = None):
+ """Straighten verticals and horizontals."""
+ if len(self._lineControlPoints)<3:
+ return
+
+ if dc:
+ self.Erase(dc)
+
+ GraphicsStraightenLine(self._lineControlPoints[-1], self._lineControlPoints[-2])
+
+ for i in range(len(self._lineControlPoints)-2):
+ GraphicsStraightenLine(self._lineControlPoints[i], self._lineControlPoints[i + 1])
+
+ if dc:
+ self.Draw(dc)
+
+ def Unlink(self):
+ """Unlink the line from the nodes at either end."""
+ if self._to:
+ self._to.GetLines().remove(self)
+ if self._from:
+ self._from.GetLines().remove(self)
+ self._to = None
+ self._from = None
+
+ def SetEnds(self, x1, y1, x2, y2):
+ """Set the end positions of the line."""
+ # Find centre point
+ first_point = self._lineControlPoints[0]
+ last_point = self._lineControlPoints[-1]
+
+ first_point[0] = x1
+ first_point[1] = y1
+ last_point[0] = x2
+ last_point[1] = y2
+
+ self._xpos = (x1 + x2) / 2
+ self._ypos = (y1 + y2) / 2
+
+ # Get absolute positions of ends
+ def GetEnds(self):
+ """Get the visible endpoints of the lines for drawing between two objects."""
+ first_point = self._lineControlPoints[0]
+ last_point = self._lineControlPoints[-1]
+
+ return (first_point[0], first_point[1]), (last_point[0], last_point[1])
+
+ def SetAttachments(self, from_attach, to_attach):
+ """Specify which object attachment points should be used at each end
+ of the line.
+ """
+ self._attachmentFrom = from_attach
+ self._attachmentTo = to_attach
+
+ def HitTest(self, x, y):
+ if not self._lineControlPoints:
+ return False
+
+ # Look at label regions in case mouse is over a label
+ inLabelRegion = False
+ for i in range(3):
+ if self._regions[i]:
+ region = self._regions[i]
+ if len(region._formattedText):
+ xp, yp = self.GetLabelPosition(i)
+ # Offset region from default label position
+ cx, cy = region.GetPosition()
+ cw, ch = region.GetSize()
+ cx += xp
+ cy += yp
+
+ rLeft = cx-cw / 2
+ rTop = cy-ch / 2
+ rRight = cx + cw / 2
+ rBottom = cy + ch / 2
+ if x>rLeft and x<rRight and y>rTop and y<rBottom:
+ inLabelRegion = True
+ break
+
+ for i in range(len(self._lineControlPoints)-1):
+ point1 = self._lineControlPoints[i]
+ point2 = self._lineControlPoints[i + 1]
+
+ # For inaccurate mousing allow 8 pixel corridor
+ extra = 4
+
+ dx = point2[0]-point1[0]
+ dy = point2[1]-point1[1]
+
+ seg_len = sqrt(dx * dx + dy * dy)
+ if dy == 0 or dx == 0:
+ return False
+ distance_from_seg = seg_len * ((x-point1[0]) * dy-(y-point1[1]) * dx) / (dy * dy + dx * dx)
+ distance_from_prev = seg_len * ((y-point1[1]) * dy + (x-point1[0]) * dx) / (dy * dy + dx * dx)
+
+ if abs(distance_from_seg)<extra and distance_from_prev >= 0 and distance_from_prev <= seg_len or inLabelRegion:
+ return 0, distance_from_seg
+
+ return False
+
+ def DrawArrows(self, dc):
+ """Draw all arrows."""
+ # Distance along line of each arrow: space them out evenly
+ startArrowPos = 0.0
+ endArrowPos = 0.0
+ middleArrowPos = 0.0
+
+ for arrow in self._arcArrows:
+ ah = arrow.GetArrowEnd()
+ if ah == ARROW_POSITION_START:
+ if arrow.GetXOffset() and not self._ignoreArrowOffsets:
+ # If specified, x offset is proportional to line length
+ self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
+ else:
+ self.DrawArrow(dc, arrow, startArrowPos, False)
+ startArrowPos += arrow.GetSize() + arrow.GetSpacing()
+ elif ah == ARROW_POSITION_END:
+ if arrow.GetXOffset() and not self._ignoreArrowOffsets:
+ self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
+ else:
+ self.DrawArrow(dc, arrow, endArrowPos, False)
+ endArrowPos += arrow.GetSize() + arrow.GetSpacing()
+ elif ah == ARROW_POSITION_MIDDLE:
+ arrow.SetXOffset(middleArrowPos)
+ if arrow.GetXOffset() and not self._ignoreArrowOffsets:
+ self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
+ else:
+ self.DrawArrow(dc, arrow, middleArrowPos, False)
+ middleArrowPos += arrow.GetSize() + arrow.GetSpacing()
+
+ def DrawArrow(self, dc, arrow, XOffset, proportionalOffset):
+ """Draw the given arrowhead (or annotation)."""
+ first_line_point = self._lineControlPoints[0]
+ second_line_point = self._lineControlPoints[1]
+
+ last_line_point = self._lineControlPoints[-1]
+ second_last_line_point = self._lineControlPoints[-2]
+
+ # Position of start point of line, at the end of which we draw the arrow
+ startPositionX, startPositionY = 0.0, 0.0
+
+ ap = arrow.GetPosition()
+ if ap == ARROW_POSITION_START:
+ # If we're using a proportional offset, calculate just where this
+ # will be on the line.
+ realOffset = XOffset
+ if proportionalOffset:
+ totalLength = sqrt((second_line_point[0]-first_line_point[0]) * (second_line_point[0]-first_line_point[0]) + (second_line_point[1]-first_line_point[1]) * (second_line_point[1]-first_line_point[1]))
+ realOffset = XOffset * totalLength
+
+ positionOnLineX, positionOnLineY = GetPointOnLine(second_line_point[0], second_line_point[1], first_line_point[0], first_line_point[1], realOffset)
+
+ startPositionX = second_line_point[0]
+ startPositionY = second_line_point[1]
+ elif ap == ARROW_POSITION_END:
+ # If we're using a proportional offset, calculate just where this
+ # will be on the line.
+ realOffset = XOffset
+ if proportionalOffset:
+ totalLength = sqrt((second_last_line_point[0]-last_line_point[0]) * (second_last_line_point[0]-last_line_point[0]) + (second_last_line_point[1]-last_line_point[1]) * (second_last_line_point[1]-last_line_point[1]));
+ realOffset = XOffset * totalLength
+
+ positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], last_line_point[0], last_line_point[1], realOffset)
+
+ startPositionX = second_last_line_point[0]
+ startPositionY = second_last_line_point[1]
+ elif ap == ARROW_POSITION_MIDDLE:
+ # Choose a point half way between the last and penultimate points
+ x = (last_line_point[0] + second_last_line_point[0]) / 2
+ y = (last_line_point[1] + second_last_line_point[1]) / 2
+
+ # If we're using a proportional offset, calculate just where this
+ # will be on the line.
+ realOffset = XOffset
+ if proportionalOffset:
+ totalLength = sqrt((second_last_line_point[0]-x) * (second_last_line_point[0]-x) + (second_last_line_point[1]-y) * (second_last_line_point[1]-y));
+ realOffset = XOffset * totalLength
+
+ positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], x, y, realOffset)
+ startPositionX = second_last_line_point[0]
+ startPositionY = second_last_line_point[1]
+
+ # Add yOffset to arrow, if any
+
+ # The translation that the y offset may give
+ deltaX = 0.0
+ deltaY = 0.0
+ if arrow.GetYOffset and not self._ignoreArrowOffsets:
+ # |(x4, y4)
+ # |d
+ # |
+ # (x1, y1)--------------(x3, y3)------------------(x2, y2)
+ # x4 = x3 - d * sin(theta)
+ # y4 = y3 + d * cos(theta)
+ #
+ # Where theta = tan(-1) of (y3-y1) / (x3-x1)
+ x1 = startPositionX
+ y1 = startPositionY
+ x3 = positionOnLineX
+ y3 = positionOnLineY
+ d=-arrow.GetYOffset() # Negate so +offset is above line
+
+ if x3 == x1:
+ theta = pi / 2
+ else:
+ theta = atan((y3-y1) / (x3-x1))
+
+ x4 = x3-d * sin(theta)
+ y4 = y3 + d * cos(theta)
+
+ deltaX = x4-positionOnLineX
+ deltaY = y4-positionOnLineY
+
+ at = arrow._GetType()
+ if at == ARROW_ARROW:
+ arrowLength = arrow.GetSize()
+ arrowWidth = arrowLength / 3
+
+ tip_x, tip_y, side1_x, side1_y, side2_x, side2_y = GetArrowPoints(startPositionX + deltaX, startPositionY + deltaY, positionOnLineX + deltaX, positionOnLineY + deltaY, arrowLength, arrowWidth)
+
+ points = [[tip_x, tip_y],
+ [side1_x, side1_y],
+ [side2_x, side2_y],
+ [tip_x, tip_y]]
+
+ dc.SetPen(self._pen)
+ dc.SetBrush(self._brush)
+ dc.DrawPolygon(points)
+ elif at in [ARROW_HOLLOW_CIRCLE, ARROW_FILLED_CIRCLE]:
+ # Find point on line of centre of circle, which is a radius away
+ # from the end position
+ diameter = arrow.GetSize()
+ x, y = GetPointOnLine(startPositionX + deltaX, startPositionY + deltaY,
+ positionOnLineX + deltaX, positionOnLineY + deltaY,
+ diameter / 2)
+ x1 = x-diameter / 2
+ y1 = y-diameter / 2
+ dc.SetPen(self._pen)
+ if arrow._GetType() == ARROW_HOLLOW_CIRCLE:
+ dc.SetBrush(self.GetBackgroundBrush())
+ else:
+ dc.SetBrush(self._brush)
+
+ dc.DrawEllipse(x1, y1, diameter, diameter)
+ elif at == ARROW_SINGLE_OBLIQUE:
+ pass
+ elif at == ARROW_METAFILE:
+ if arrow.GetMetaFile():
+ # Find point on line of centre of object, which is a half-width away
+ # from the end position
+ #
+ # width
+ # <-- start pos <-----><-- positionOnLineX
+ # _____
+ # --------------| x | <-- e.g. rectangular arrowhead
+ # -----
+ #
+ x, y = GetPointOnLine(startPositionX, startPositionY,
+ positionOnLineX, positionOnLineY,
+ arrow.GetMetaFile()._width / 2)
+ # Calculate theta for rotating the metafile.
+ #
+ # |
+ # | o(x2, y2) 'o' represents the arrowhead.
+ # | /
+ # | /
+ # | /theta
+ # | /(x1, y1)
+ # |______________________
+ #
+ theta = 0.0
+ x1 = startPositionX
+ y1 = startPositionY
+ x2 = positionOnLineX
+ y2 = positionOnLineY
+
+ if x1 == x2 and y1 == y2:
+ theta = 0.0
+ elif x1 == x2 and y1>y2:
+ theta = 3.0 * pi / 2
+ elif x1 == x2 and y2>y1:
+ theta = pi / 2
+ elif x2>x1 and y2 >= y1:
+ theta = atan((y2-y1) / (x2-x1))
+ elif x2<x1:
+ theta = pi + atan((y2-y1) / (x2-x1))
+ elif x2>x1 and y2<y1:
+ theta = 2 * pi + atan((y2-y1) / (x2-x1))
+ else:
+ raise "Unknown arrowhead rotation case"
+
+ # Rotate about the centre of the object, then place
+ # the object on the line.
+ if arrow.GetMetaFile().GetRotateable():
+ arrow.GetMetaFile().Rotate(0.0, 0.0, theta)
+
+ if self._erasing:
+ # If erasing, just draw a rectangle
+ minX, minY, maxX, maxY = arrow.GetMetaFile().GetBounds()
+ # Make erasing rectangle slightly bigger or you get droppings
+ extraPixels = 4
+ dc.DrawRectangle(deltaX + x + minX-extraPixels / 2, deltaY + y + minY-extraPixels / 2, maxX-minX + extraPixels, maxY-minY + extraPixels)
+ else:
+ arrow.GetMetaFile().Draw(dc, x + deltaX, y + deltaY)
+
+ def OnErase(self, dc):
+ old_pen = self._pen
+ old_brush = self._brush
+
+ bg_pen = self.GetBackgroundPen()
+ bg_brush = self.GetBackgroundBrush()
+ self.SetPen(bg_pen)
+ self.SetBrush(bg_brush)
+
+ bound_x, bound_y = self.GetBoundingBoxMax()
+ if self._font:
+ dc.SetFont(self._font)
+
+ # Undraw text regions
+ for i in range(3):
+ if self._regions[i]:
+ x, y = self.GetLabelPosition(i)
+ self.EraseRegion(dc, self._regions[i], x, y)
+
+ # Undraw line
+ dc.SetPen(self.GetBackgroundPen())
+ dc.SetBrush(self.GetBackgroundBrush())
+
+ # Drawing over the line only seems to work if the line has a thickness
+ # of 1.
+ if old_pen and old_pen.GetWidth()>1:
+ dc.DrawRectangle(self._xpos-bound_x / 2-2, self._ypos-bound_y / 2-2,
+ bound_x + 4, bound_y + 4)
+ else:
+ self._erasing = True
+ self.GetEventHandler().OnDraw(dc)
+ self.GetEventHandler().OnEraseControlPoints(dc)
+ self._erasing = False
+
+ if old_pen:
+ self.SetPen(old_pen)
+ if old_brush:
+ self.SetBrush(old_brush)
+
+ def GetBoundingBoxMin(self):
+ x1, y1 = 10000, 10000
+ x2, y2=-10000,-10000
+
+ for point in self._lineControlPoints:
+ if point[0]<x1:
+ x1 = point[0]
+ if point[1]<y1:
+ y1 = point[1]
+ if point[0]>x2:
+ x2 = point[0]
+ if point[1]>y2:
+ y2 = point[1]
+
+ return x2-x1, y2-y1
+
+ # For a node image of interest, finds the position of this arc
+ # amongst all the arcs which are attached to THIS SIDE of the node image,
+ # and the number of same.
+ def FindNth(self, image, incoming):
+ """Find the position of the line on the given object.
+
+ Specify whether incoming or outgoing lines are being considered
+ with incoming.
+ """
+ n=-1
+ num = 0
+
+ if image == self._to:
+ this_attachment = self._attachmentTo
+ else:
+ this_attachment = self._attachmentFrom
+
+ # Find number of lines going into / out of this particular attachment point
+ for line in image.GetLines():
+ if line._from == image:
+ # This is the nth line attached to 'image'
+ if line == self and not incoming:
+ n = num
+
+ # Increment num count if this is the same side (attachment number)
+ if line._attachmentFrom == this_attachment:
+ num += 1
+
+ if line._to == image:
+ # This is the nth line attached to 'image'
+ if line == self and incoming:
+ n = num
+
+ # Increment num count if this is the same side (attachment number)
+ if line._attachmentTo == this_attachment:
+ num += 1
+
+ return n, num
+
+ def OnDrawOutline(self, dc, x, y, w, h):
+ old_pen = self._pen
+ old_brush = self._brush
+
+ dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
+ self.SetPen(dottedPen)
+ self.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ self.GetEventHandler().OnDraw(dc)
+
+ if old_pen:
+ self.SetPen(old_pen)
+ else:
+ self.SetPen(None)
+ if old_brush:
+ self.SetBrush(old_brush)
+ else:
+ self.SetBrush(None)
+
+ def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
+ x_offset = x-old_x
+ y_offset = y-old_y
+
+ if self._lineControlPoints and not (x_offset == 0 and y_offset == 0):
+ for point in self._lineControlPoints:
+ point[0] += x_offset
+ point[1] += y_offset
+
+ # Move temporary label rectangles if necessary
+ for i in range(3):
+ if self._labelObjects[i]:
+ self._labelObjects[i].Erase(dc)
+ xp, yp = self.GetLabelPosition(i)
+ if i<len(self._regions):
+ xr, yr = self._regions[i].GetPosition()
+ else:
+ xr, yr = 0, 0
+ self._labelObjects[i].Move(dc, xp + xr, yp + yr)
+ return True
+
+ def OnMoveLink(self, dc, moveControlPoints = True):
+ """Called when a connected object has moved, to move the link to
+ correct position
+ """
+ if not self._from or not self._to:
+ return
+
+ if len(self._lineControlPoints)>2:
+ self.Initialise()
+
+ # Do each end - nothing in the middle. User has to move other points
+ # manually if necessary
+ end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints()
+
+ first = self._lineControlPoints[0]
+ last = self._lineControlPoints[-1]
+
+ oldX, oldY = self._xpos, self._ypos
+
+ self.SetEnds(end_x, end_y, other_end_x, other_end_y)
+
+ # Do a second time, because one may depend on the other
+ end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints()
+ self.SetEnds(end_x, end_y, other_end_x, other_end_y)
+
+ # Try to move control points with the arc
+ x_offset = self._xpos-oldX
+ y_offset = self._ypos-oldY
+
+ # Only move control points if it's a self link. And only works
+ # if attachment mode is ON
+ if self._from == self._to and self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE and moveControlPoints and self._lineControlPoints and not (x_offset == 0 and y_offset == 0):
+ for point in self._lineControlPoints[1:-1]:
+ point.x += x_offset
+ point.y += y_offset
+
+ self.Move(dc, self._xpos, self._ypos)
+
+ def FindLineEndPoints(self):
+ """Finds the x, y points at the two ends of the line.
+
+ This function can be used by e.g. line-routing routines to
+ get the actual points on the two node images where the lines will be
+ drawn to / from.
+ """
+ if not self._from or not self._to:
+ return
+
+ # Do each end - nothing in the middle. User has to move other points
+ # manually if necessary.
+ second_point = self._lineControlPoints[1]
+ second_last_point = self._lineControlPoints[-2]
+
+ if len(self._lineControlPoints)>2:
+ if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
+ nth, no_arcs = self.FindNth(self._from, False) # Not incoming
+ end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
+ else:
+ end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), second_point[0], second_point[1])
+
+ if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
+ nth, no_arch = self.FindNth(self._to, True) # Incoming
+ other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arch, self)
+ else:
+ other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), second_last_point[0], second_last_point[1])
+ else:
+ fromX = self._from.GetX()
+ fromY = self._from.GetY()
+ toX = self._to.GetX()
+ toY = self._to.GetY()
+
+ if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
+ nth, no_arcs = self.FindNth(self._from, False)
+ end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
+ fromX = end_x
+ fromY = end_y
+
+ if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
+ nth, no_arcs = self.FindNth(self._to, True)
+ other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arcs, self)
+ toX = other_end_x
+ toY = other_end_y
+
+ if self._from.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
+ end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), toX, toY)
+
+ if self._to.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
+ other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), fromX, fromY)
+
+ #print type(self._from), type(self._to), end_x, end_y, other_end_x, other_end_y
+ return end_x, end_y, other_end_x, other_end_y
+
+ def OnDraw(self, dc):
+ if not self._lineControlPoints:
+ return
+
+ if self._pen:
+ dc.SetPen(self._pen)
+ if self._brush:
+ dc.SetBrush(self._brush)
+
+ points = []
+ for point in self._lineControlPoints:
+ points.append(wx.Point(point.x, point.y))
+
+ #print points
+ if self._isSpline:
+ dc.DrawSpline(points)
+ else:
+ dc.DrawLines(points)
+
+ if sys.platform[:3]=="win":
+ # For some reason, last point isn't drawn under Windows
+ dc.DrawPoint(points[-1])
+
+ # Problem with pen - if not a solid pen, does strange things
+ # to the arrowhead. So make (get) a new pen that's solid.
+ if self._pen and self._pen.GetStyle() != wx.SOLID:
+ solid_pen = wx.ThePenList().FindOrCreatePen(self._pen.GetColour(), 1, wx.SOLID)
+ if solid_pen:
+ dc.SetPen(solid_pen)
+
+ self.DrawArrows(dc)
+
+ def OnDrawControlPoints(self, dc):
+ if not self._drawHandles:
+ return
+
+ # Draw temporary label rectangles if necessary
+ for i in range(3):
+ if self._labelObjects[i]:
+ self._labelObjects[i].Draw(dc)
+
+ Shape.OnDrawControlPoints(self, dc)
+
+ def OnEraseControlPoints(self, dc):
+ # Erase temporary label rectangles if necessary
+
+ for i in range(3):
+ if self._labelObjects[i]:
+ self._labelObjects[i].Erase(dc)
+
+ Shape.OnEraseControlPoints(self, dc)
+
+ def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
+ pass
+
+ def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
+ pass
+
+ def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
+ pass
+
+ def OnDrawContents(self, dc):
+ if self.GetDisableLabel():
+ return
+
+ for i in range(3):
+ if self._regions[i]:
+ x, y = self.GetLabelPosition(i)
+ self.DrawRegion(dc, self._regions[i], x, y)
+
+ def SetTo(self, object):
+ """Set the 'to' object for the line."""
+ self._to = object
+
+ def SetFrom(self, object):
+ """Set the 'from' object for the line."""
+ self._from = object
+
+ def MakeControlPoints(self):
+ """Make handle control points."""
+ if self._canvas and self._lineControlPoints:
+ first = self._lineControlPoints[0]
+ last = self._lineControlPoints[-1]
+
+ control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, first[0], first[1], CONTROL_POINT_ENDPOINT_FROM)
+ control._point = first
+ self._canvas.AddShape(control)
+ self._controlPoints.Append(control)
+
+ for point in self._lineControlPoints[1:-1]:
+ control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point[0], point[1], CONTROL_POINT_LINE)
+ control._point = point
+ self._canvas.AddShape(control)
+ self._controlPoints.Append(control)
+
+ control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, last[0], last[1], CONTROL_POINT_ENDPOINT_TO)
+ control._point = last
+ self._canvas.AddShape(control)
+ self._controlPoints.Append(control)
+
+ def ResetControlPoints(self):
+ if self._canvas and self._lineControlPoints:
+ for i in range(min(len(self._controlPoints), len(self._lineControlPoints))):
+ point = self._lineControlPoints[i]
+ control = self._controlPoints[i]
+ control.SetX(point[0])
+ control.SetY(point[1])
+
+ # Override select, to create / delete temporary label-moving objects
+ def Select(self, select, dc = None):
+ Shape.Select(self, select, dc)
+ if select:
+ for i in range(3):
+ if self._regions[i]:
+ region = self._regions[i]
+ if region._formattedText:
+ w, h = region.GetSize()
+ x, y = region.GetPosition()
+ xx, yy = self.GetLabelPosition(i)
+
+ if self._labelObjects[i]:
+ self._labelObjects[i].Select(False)
+ self._labelObjects[i].RemoveFromCanvas(self._canvas)
+
+ self._labelObjects[i] = self.OnCreateLabelShape(self, region, w, h)
+ self._labelObjects[i].AddToCanvas(self._canvas)
+ self._labelObjects[i].Show(True)
+ if dc:
+ self._labelObjects[i].Move(dc, x + xx, y + yy)
+ self._labelObjects[i].Select(True, dc)
+ else:
+ for i in range(3):
+ if self._labelObjects[i]:
+ self._labelObjects[i].Select(False, dc)
+ self._labelObjects[i].Erase(dc)
+ self._labelObjects[i].RemoveFromCanvas(self._canvas)
+ self._labelObjects[i] = None
+
+ # Control points ('handles') redirect control to the actual shape, to
+ # make it easier to override sizing behaviour.
+ def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
+ 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)
+
+ if pt._type == CONTROL_POINT_LINE:
+ x, y = self._canvas.Snap()
+
+ pt.SetX(x)
+ pt.SetY(y)
+ pt._point[0] = x
+ pt._point[1] = y
+
+ old_pen = self.GetPen()
+ old_brush = self.GetBrush()
+
+ self.SetPen(dottedPen)
+ self.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ self.GetEventHandler().OnMoveLink(dc, False)
+
+ self.SetPen(old_pen)
+ self.SetBrush(old_brush)
+
+ def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ if pt._type == CONTROL_POINT_LINE:
+ pt._originalPos = pt._point
+ x, y = self._canvas.Snap()
+
+ self.Erase(dc)
+
+ # Redraw start and end objects because we've left holes
+ # when erasing the line
+ self.GetFrom().OnDraw(dc)
+ self.GetFrom().OnDrawContents(dc)
+ self.GetTo().OnDraw(dc)
+ self.GetTo().OnDrawContents(dc)
+
+ self.SetDisableLabel(True)
+ dc.SetLogicalFunction(OGLRBLF)
+
+ pt._xpos = x
+ pt._ypos = y
+ pt._point[0] = x
+ pt._point[1] = y
+
+ old_pen = self.GetPen()
+ old_brush = self.GetBrush()
+
+ dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
+ self.SetPen(dottedPen)
+ self.SetBrush(wx.TRANSPARENT_BRUSH)
+
+ self.GetEventHandler().OnMoveLink(dc, False)
+
+ self.SetPen(old_pen)
+ self.SetBrush(old_brush)
+
+ if pt._type == CONTROL_POINT_ENDPOINT_FROM or pt._type == CONTROL_POINT_ENDPOINT_TO:
+ self._canvas.SetCursor(wx.Cursor(wx.CURSOR_BULLSEYE))
+ pt._oldCursor = wx.STANDARD_CURSOR
+
+ def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
+ dc = wx.ClientDC(self.GetCanvas())
+ self.GetCanvas().PrepareDC(dc)
+
+ self.SetDisableLabel(False)
+
+ if pt._type == CONTROL_POINT_LINE:
+ x, y = self._canvas.Snap()
+
+ rpt = wx.RealPoint(x, y)
+
+ # Move the control point back to where it was;
+ # MoveControlPoint will move it to the new position
+ # if it decides it wants. We only moved the position
+ # during user feedback so we could redraw the line
+ # as it changed shape.
+ pt._xpos = pt._originalPos[0]
+ pt._ypos = pt._originalPos[1]
+ pt._point[0] = pt._originalPos[0]
+ pt._point[1] = pt._originalPos[1]
+
+ self.OnMoveMiddleControlPoint(dc, pt, rpt)
+
+ if pt._type == CONTROL_POINT_ENDPOINT_FROM:
+ if pt._oldCursor:
+ self._canvas.SetCursor(pt._oldCursor)
+
+ if self.GetFrom():
+ self.GetFrom().MoveLineToNewAttachment(dc, self, x, y)
+
+ if pt._type == CONTROL_POINT_ENDPOINT_TO:
+ if pt._oldCursor:
+ self._canvas.SetCursor(pt._oldCursor)
+
+ if self.GetTo():
+ self.GetTo().MoveLineToNewAttachment(dc, self, x, y)
+
+ # This is called only when a non-end control point is moved
+ def OnMoveMiddleControlPoint(self, dc, lpt, pt):
+ lpt._xpos = pt[0]
+ lpt._ypos = pt[1]
+
+ lpt._point[0] = pt[0]
+ lpt._point[1] = pt[1]
+
+ self.GetEventHandler().OnMoveLink(dc)
+
+ return True
+
+ def AddArrow(self, type, end = ARROW_POSITION_END, size = 10.0, xOffset = 0.0, name="",mf = None, arrowId=-1):
+ """Add an arrow (or annotation) to the line.
+
+ type may currently be one of:
+
+ ARROW_HOLLOW_CIRCLE
+ Hollow circle.
+ ARROW_FILLED_CIRCLE
+ Filled circle.
+ ARROW_ARROW
+ Conventional arrowhead.
+ ARROW_SINGLE_OBLIQUE
+ Single oblique stroke.
+ ARROW_DOUBLE_OBLIQUE
+ Double oblique stroke.
+ ARROW_DOUBLE_METAFILE
+ Custom arrowhead.
+
+ end may currently be one of:
+
+ ARROW_POSITION_END
+ Arrow appears at the end.
+ ARROW_POSITION_START
+ Arrow appears at the start.
+
+ arrowSize specifies the length of the arrow.
+
+ xOffset specifies the offset from the end of the line.
+
+ name specifies a name for the arrow.
+
+ mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows
+ metafile.
+
+ arrowId is the id for the arrow.
+ """
+ arrow = ArrowHead(type, end, size, xOffset, name, mf, arrowId)
+ self._arcArrows.append(arrow)
+ return arrow
+
+ # Add arrowhead at a particular position in the arrowhead list
+ def AddArrowOrdered(self, arrow, referenceList, end):
+ """Add an arrowhead in the position indicated by the reference list
+ of arrowheads, which contains all legal arrowheads for this line, in
+ the correct order. E.g.
+
+ Reference list: a b c d e
+ Current line list: a d
+
+ Add c, then line list is: a c d.
+
+ If no legal arrowhead position, return FALSE. Assume reference list
+ is for one end only, since it potentially defines the ordering for
+ any one of the 3 positions. So we don't check the reference list for
+ arrowhead position.
+ """
+ if not referenceList:
+ return False
+
+ targetName = arrow.GetName()
+
+ # First check whether we need to insert in front of list,
+ # because this arrowhead is the first in the reference
+ # list and should therefore be first in the current list.
+ refArrow = referenceList[0]
+ if refArrow.GetName() == targetName:
+ self._arcArrows.insert(0, arrow)
+ return True
+
+ i1 = i2 = 0
+ while i1<len(referenceList) and i2<len(self._arcArrows):
+ refArrow = referenceList[i1]
+ currArrow = self._arcArrows[i2]
+
+ # Matching: advance current arrow pointer
+ if currArrow.GetArrowEnd() == end and currArrow.GetName() == refArrow.GetName():
+ i2 += 1
+
+ # Check if we're at the correct position in the
+ # reference list
+ if targetName == refArrow.GetName():
+ if i2<len(self._arcArrows):
+ self._arcArrows.insert(i2, arrow)
+ else:
+ self._arcArrows.append(arrow)
+ return True
+ i1 += 1
+
+ self._arcArrows.append(arrow)
+ return True
+
+ def ClearArrowsAtPosition(self, end):
+ """Delete the arrows at the specified position, or at any position
+ if position is -1.
+ """
+ if end==-1:
+ self._arcArrows = []
+ return
+
+ for arrow in self._arcArrows:
+ if arrow.GetArrowEnd() == end:
+ self._arcArrows.remove(arrow)
+
+ def ClearArrow(self, name):
+ """Delete the arrow with the given name."""
+ for arrow in self._arcArrows:
+ if arrow.GetName() == name:
+ self._arcArrows.remove(arrow)
+ return True
+ return False
+
+ def FindArrowHead(self, position, name):
+ """Find arrowhead by position and name.
+
+ if position is -1, matches any position.
+ """
+ for arrow in self._arcArrows:
+ if (position==-1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
+ return arow
+
+ return None
+
+ def FindArrowHeadId(self, arrowId):
+ """Find arrowhead by id."""
+ for arrow in self._arcArrows:
+ if arrowId == arrow.GetId():
+ return arrow
+
+ return None
+
+ def DeleteArrowHead(self, position, name):
+ """Delete arrowhead by position and name.
+
+ if position is -1, matches any position.
+ """
+ for arrow in self._arcArrows:
+ if (position==-1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
+ self._arcArrows.remove(arrow)
+ return True
+ return False
+
+ def DeleteArrowHeadId(self, id):
+ """Delete arrowhead by id."""
+ for arrow in self._arcArrows:
+ if arrowId == arrow.GetId():
+ self._arcArrows.remove(arrow)
+ return True
+ return False
+
+ # Calculate the minimum width a line
+ # occupies, for the purposes of drawing lines in tools.
+ def FindMinimumWidth(self):
+ """Find the horizontal width for drawing a line with arrows in
+ minimum space. Assume arrows at end only.
+ """
+ minWidth = 0.0
+ for arrowHead in self._arcArrows:
+ minWidth += arrowHead.GetSize()
+ if arrowHead != self._arcArrows[-1]:
+ minWidth += arrowHead + GetSpacing
+
+ # We have ABSOLUTE minimum now. So
+ # scale it to give it reasonable aesthetics
+ # when drawing with line.
+ if minWidth>0:
+ minWidth = minWidth * 1.4
+ else:
+ minWidth = 20.0
+
+ self.SetEnds(0.0, 0.0, minWidth, 0.0)
+ self.Initialise()
+
+ return minWidth
+
+ def FindLinePosition(self, x, y):
+ """Find which position we're talking about at this x, y.
+
+ Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END.
+ """
+ startX, startY, endX, endY = self.GetEnds()
+
+ # Find distances from centre, start and end. The smallest wins
+ centreDistance = sqrt((x-self._xpos) * (x-self._xpos) + (y-self._ypos) * (y-self._ypos))
+ startDistance = sqrt((x-startX) * (x-startX) + (y-startY) * (y-startY))
+ endDistance = sqrt((x-endX) * (x-endX) + (y-endY) * (y-endY))
+
+ if centreDistance<startDistance and centreDistance<endDistance:
+ return ARROW_POSITION_MIDDLE
+ elif startDistance<endDistance:
+ return ARROW_POSITION_START
+ else:
+ return ARROW_POSITION_END
+
+ def SetAlignmentOrientation(self, isEnd, isHoriz):
+ if isEnd:
+ if isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
+ self._alignmentEnd != LINE_ALIGNMENT_HORIZ
+ elif not isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
+ self._alignmentEnd -= LINE_ALIGNMENT_HORIZ
+ else:
+ if isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
+ self._alignmentStart != LINE_ALIGNMENT_HORIZ
+ elif not isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
+ self._alignmentStart -= LINE_ALIGNMENT_HORIZ
+
+ def SetAlignmentType(self, isEnd, alignType):
+ if isEnd:
+ if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ if self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ self._alignmentEnd |= LINE_ALIGNMENT_TO_NEXT_HANDLE
+ elif self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ self._alignmentEnd -= LINE_ALIGNMENT_TO_NEXT_HANDLE
+ else:
+ if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ if self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ self._alignmentStart |= LINE_ALIGNMENT_TO_NEXT_HANDLE
+ elif self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
+ self._alignmentStart -= LINE_ALIGNMENT_TO_NEXT_HANDLE
+
+ def GetAlignmentOrientation(self, isEnd):
+ if isEnd:
+ return self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
+ else:
+ return self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
+
+ def GetAlignmentType(self, isEnd):
+ if isEnd:
+ return self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE
+ else:
+ return self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE
+
+ def GetNextControlPoint(self, shape):
+ """Find the next control point in the line after the start / end point,
+ depending on whether the shape is at the start or end.
+ """
+ n = len(self._lineControlPoints)
+ if self._to == shape:
+ # Must be END of line, so we want (n - 1)th control point.
+ # But indexing ends at n-1, so subtract 2.
+ nn = n-2
+ else:
+ nn = 1
+ if nn<len(self._lineControlPoints):
+ return self._lineControlPoints[nn]
+ return None
+
+ def OnCreateLabelShape(self, parent, region, w, h):
+ return LabelShape(parent, region, w, h)
+
+
+ def OnLabelMovePre(self, dc, labelShape, x, y, old_x, old_y, display):
+ labelShape._shapeRegion.SetSize(labelShape.GetWidth(), labelShape.GetHeight())
+
+ # Find position in line's region list
+ i = 0
+ for region in self.GetRegions():
+ if labelShape._shapeRegion == region:
+ self.GetRegions().remove(region)
+ else:
+ i += 1
+
+ xx, yy = self.GetLabelPosition(i)
+ # Set the region's offset, relative to the default position for
+ # each region.
+ labelShape._shapeRegion.SetPosition(x-xx, y-yy)
+ labelShape.SetX(x)
+ labelShape.SetY(y)
+
+ # Need to reformat to fit region
+ if labelShape._shapeRegion.GetText():
+ s = labelShape._shapeRegion.GetText()
+ labelShape.FormatText(dc, s, i)
+ self.DrawRegion(dc, labelShape._shapeRegion, xx, yy)
+ return True
+
--- /dev/null
+# -*- coding: iso-8859-1 -*-
+#----------------------------------------------------------------------------
+# Name: oglmisc.py
+# Purpose: Miscellaneous OGL support functions
+#
+# Author: Pierre Hjälm (from C++ original by Julian Smart)
+#
+# Created: 20040508
+# RCS-ID:
+# Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
+# Licence: wxWindows license
+#----------------------------------------------------------------------------
+
+from __future__ import division
+from math import *
+
+import wx
+
+# Control point types
+# Rectangle and most other shapes
+CONTROL_POINT_VERTICAL = 1
+CONTROL_POINT_HORIZONTAL = 2
+CONTROL_POINT_DIAGONAL = 3
+
+# Line
+CONTROL_POINT_ENDPOINT_TO = 4
+CONTROL_POINT_ENDPOINT_FROM = 5
+CONTROL_POINT_LINE = 6
+
+# Types of formatting: can be combined in a bit list
+FORMAT_NONE = 0 # Left justification
+FORMAT_CENTRE_HORIZ = 1 # Centre horizontally
+FORMAT_CENTRE_VERT = 2 # Centre vertically
+FORMAT_SIZE_TO_CONTENTS = 4 # Resize shape to contents
+
+# Attachment modes
+ATTACHMENT_MODE_NONE, ATTACHMENT_MODE_EDGE, ATTACHMENT_MODE_BRANCHING = 0, 1, 2
+
+# Shadow mode
+SHADOW_NONE, SHADOW_LEFT, SHADOW_RIGHT = 0, 1, 2
+
+OP_CLICK_LEFT, OP_CLICK_RIGHT, OP_DRAG_LEFT, OP_DRAG_RIGHT = 1, 2, 4, 8
+OP_ALL = OP_CLICK_LEFT | OP_CLICK_RIGHT | OP_DRAG_LEFT | OP_DRAG_RIGHT
+
+# Sub-modes for branching attachment mode
+BRANCHING_ATTACHMENT_NORMAL = 1
+BRANCHING_ATTACHMENT_BLOB = 2
+
+# logical function to use when drawing rubberband boxes, etc.
+OGLRBLF = wx.INVERT
+
+CONTROL_POINT_SIZE = 6
+
+# Types of arrowhead
+# (i) Built-in
+ARROW_HOLLOW_CIRCLE = 1
+ARROW_FILLED_CIRCLE = 2
+ARROW_ARROW = 3
+ARROW_SINGLE_OBLIQUE = 4
+ARROW_DOUBLE_OBLIQUE = 5
+# (ii) Custom
+ARROW_METAFILE = 20
+
+# Position of arrow on line
+ARROW_POSITION_START = 0
+ARROW_POSITION_END = 1
+ARROW_POSITION_MIDDLE = 2
+
+# Line alignment flags
+# Vertical by default
+LINE_ALIGNMENT_HORIZ = 1
+LINE_ALIGNMENT_VERT = 0
+LINE_ALIGNMENT_TO_NEXT_HANDLE = 2
+LINE_ALIGNMENT_NONE = 0
+
+
+
+# Format a string to a list of strings that fit in the given box.
+# Interpret %n and 10 or 13 as a new line.
+def FormatText(dc, text, width, height, formatMode):
+ i = 0
+ word=""
+ word_list = []
+ end_word = False
+ new_line = False
+ while i<len(text):
+ if text[i]=="%":
+ i += 1
+ if i == len(text):
+ word+="%"
+ else:
+ if text[i]=="n":
+ new_line = True
+ end_word = True
+ i += 1
+ else:
+ word+="%"+text[i]
+ i += 1
+ elif text[i] in ["\012","\015"]:
+ new_line = True
+ end_word = True
+ i += 1
+ elif text[i]==" ":
+ end_word = True
+ i += 1
+ else:
+ word += text[i]
+ i += 1
+
+ if i == len(text):
+ end_word = True
+
+ if end_word:
+ word_list.append(word)
+ word=""
+ end_word = False
+ if new_line:
+ word_list.append(None)
+ new_line = False
+
+ # Now, make a list of strings which can fit in the box
+ string_list = []
+ buffer=""
+ for s in word_list:
+ oldBuffer = buffer
+ if s is None:
+ # FORCE NEW LINE
+ if len(buffer)>0:
+ string_list.append(buffer)
+ buffer=""
+ else:
+ if len(buffer):
+ buffer+=" "
+ buffer += s
+ x, y = dc.GetTextExtent(buffer)
+
+ # Don't fit within the bounding box if we're fitting
+ # shape to contents
+ if (x>width) and not (formatMode & FORMAT_SIZE_TO_CONTENTS):
+ # Deal with first word being wider than box
+ if len(oldBuffer):
+ string_list.append(oldBuffer)
+ buffer = s
+ if len(buffer):
+ string_list.append(buffer)
+
+ return string_list
+
+
+
+def GetCentredTextExtent(dc, text_list, xpos = 0, ypos = 0, width = 0, height = 0):
+ if not text_list:
+ return 0, 0
+
+ max_width = 0
+ for line in text_list:
+ current_width, char_height = dc.GetTextExtent(line)
+ if current_width>max_width:
+ max_width = current_width
+
+ return max_width, len(text_list) * char_height
+
+
+
+def CentreText(dc, text_list, xpos, ypos, width, height, formatMode):
+ if not text_list:
+ return
+
+ # First, get maximum dimensions of box enclosing text
+ char_height = 0
+ max_width = 0
+ current_width = 0
+
+ # Store text extents for speed
+ widths = []
+ for line in text_list:
+ current_width, char_height = dc.GetTextExtent(line.GetText())
+ widths.append(current_width)
+ if current_width>max_width:
+ max_width = current_width
+
+ max_height = len(text_list) * char_height
+
+ if formatMode & FORMAT_CENTRE_VERT:
+ if max_height<height:
+ yoffset = ypos - height / 2 + (height - max_height) / 2
+ else:
+ yoffset = ypos - height / 2
+ yOffset = ypos
+ else:
+ yoffset = 0.0
+ yOffset = 0.0
+
+ if formatMode & FORMAT_CENTRE_HORIZ:
+ xoffset = xpos - width / 2
+ xOffset = xpos
+ else:
+ xoffset = 0.0
+ xOffset = 0.0
+
+ for i, line in enumerate(text_list):
+ if formatMode & FORMAT_CENTRE_HORIZ and widths[i]<width:
+ x = (width - widths[i]) / 2 + xoffset
+ else:
+ x = xoffset
+ y = i * char_height + yoffset
+
+ line.SetX(x - xOffset)
+ line.SetY(y - yOffset)
+
+
+
+def DrawFormattedText(dc, text_list, xpos, ypos, width, height, formatMode):
+ if formatMode & FORMAT_CENTRE_HORIZ:
+ xoffset = xpos
+ else:
+ xoffset = xpos - width / 2
+
+ if formatMode & FORMAT_CENTRE_VERT:
+ yoffset = ypos
+ else:
+ yoffset = ypos - height / 2
+
+ # +1 to allow for rounding errors
+ dc.SetClippingRegion(xpos - width / 2, ypos - height / 2, width + 1, height + 1)
+
+ for line in text_list:
+ dc.DrawText(line.GetText(), xoffset + line.GetX(), yoffset + line.GetY())
+
+ dc.DestroyClippingRegion()
+
+
+
+def RoughlyEqual(val1, val2, tol = 0.00001):
+ return val1<(val2 + tol) and val1>(val2 - tol) and \
+ val2<(val1 + tol) and val2>(val1 - tol)
+
+
+
+def FindEndForBox(width, height, x1, y1, x2, y2):
+ xvec = [x1 - width / 2, x1 - width / 2, x1 + width / 2, x1 + width / 2, x1 - width / 2]
+ yvec = [y1 - height / 2, y1 + height / 2, y1 + height / 2, y1 - height / 2, y1 - height / 2]
+
+ return FindEndForPolyline(xvec, yvec, x2, y2, x1, y1)
+
+
+
+def CheckLineIntersection(x1, y1, x2, y2, x3, y3, x4, y4):
+ denominator_term = (y4 - y3) * (x2 - x1) - (y2 - y1) * (x4 - x3)
+ numerator_term = (x3 - x1) * (y4 - y3) + (x4 - x3) * (y1 - y3)
+
+ length_ratio = 1.0
+ k_line = 1.0
+
+ # Check for parallel lines
+ if denominator_term<0.005 and denominator_term>-0.005:
+ line_constant=-1.0
+ else:
+ line_constant = float(numerator_term) / denominator_term
+
+ # Check for intersection
+ if line_constant<1.0 and line_constant>0.0:
+ # Now must check that other line hits
+ if (y4 - y3)<0.005 and (y4 - y3)>-0.005:
+ k_line = (x1 - x3 + line_constant * (x2 - x1)) / (x4 - x3)
+ else:
+ k_line = (y1 - y3 + line_constant * (y2 - y1)) / (y4 - y3)
+ if k_line >= 0 and k_line<1:
+ length_ratio = line_constant
+ else:
+ k_line = 1
+
+ return length_ratio, k_line
+
+
+
+def FindEndForPolyline(xvec, yvec, x1, y1, x2, y2):
+ lastx = xvec[0]
+ lasty = yvec[0]
+
+ min_ratio = 1.0
+
+ for i in range(1, len(xvec)):
+ line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[i], yvec[i])
+ lastx = xvec[i]
+ lasty = yvec[i]
+
+ if line_ratio<min_ratio:
+ min_ratio = line_ratio
+
+ # Do last (implicit) line if last and first doubles are not identical
+ if not (xvec[0] == lastx and yvec[0] == lasty):
+ line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[0], yvec[0])
+ if line_ratio<min_ratio:
+ min_ratio = line_ratio
+
+ return x1 + (x2 - x1) * min_ratio, y1 + (y2 - y1) * min_ratio
+
+
+
+def PolylineHitTest(xvec, yvec, x1, y1, x2, y2):
+ isAHit = False
+ lastx = xvec[0]
+ lasty = yvec[0]
+
+ min_ratio = 1.0
+
+ for i in range(1, len(xvec)):
+ line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[i], yvec[i])
+ if line_ratio != 1.0:
+ isAHit = True
+ lastx = xvec[i]
+ lasty = yvec[i]
+
+ if line_ratio<min_ratio:
+ min_ratio = line_ratio
+
+ # Do last (implicit) line if last and first doubles are not identical
+ if not (xvec[0] == lastx and yvec[0] == lasty):
+ line_ratio, other_ratio = CheckLineIntersection(x1, y1, x2, y2, lastx, lasty, xvec[0], yvec[0])
+ if line_ratio != 1.0:
+ isAHit = True
+
+ return isAHit
+
+
+
+def GraphicsStraightenLine(point1, point2):
+ dx = point2[0] - point1[0]
+ dy = point2[1] - point1[1]
+
+ if dx == 0:
+ return
+ elif abs(dy / dx)>1:
+ point2[0] = point1[0]
+ else:
+ point2[1] = point1[0]
+
+
+
+def GetPointOnLine(x1, y1, x2, y2, length):
+ l = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
+ if l<0.01:
+ l = 0.01
+
+ i_bar = (x2 - x1) / l
+ j_bar = (y2 - y1) / l
+
+ return -length * i_bar + x2,-length * j_bar + y2
+
+
+
+def GetArrowPoints(x1, y1, x2, y2, length, width):
+ l = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
+
+ if l<0.01:
+ l = 0.01
+
+ i_bar = (x2 - x1) / l
+ j_bar = (y2 - y1) / l
+
+ x3=-length * i_bar + x2
+ y3=-length * j_bar + y2
+
+ return x2, y2, width*-j_bar + x3, width * i_bar + y3,-width*-j_bar + x3,-width * i_bar + y3
+
+
+
+def DrawArcToEllipse(x1, y1, width1, height1, x2, y2, x3, y3):
+ a1 = width1 / 2
+ b1 = height1 / 2
+
+ # Check that x2 != x3
+ if abs(x2 - x3)<0.05:
+ x4 = x2
+ if y3>y2:
+ y4 = y1 - sqrt((b1 * b1 - (((x2 - x1) * (x2 - x1)) * (b1 * b1) / (a1 * a1))))
+ else:
+ y4 = y1 + sqrt((b1 * b1 - (((x2 - x1) * (x2 - x1)) * (b1 * b1) / (a1 * a1))))
+ return x4, y4
+
+ # Calculate the x and y coordinates of the point where arc intersects ellipse
+ A = (1 / (a1 * a1))
+ B = ((y3 - y2) * (y3 - y2)) / ((x3 - x2) * (x3 - x2) * b1 * b1)
+ C = (2 * (y3 - y2) * (y2 - y1)) / ((x3 - x2) * b1 * b1)
+ D = ((y2 - y1) * (y2 - y1)) / (b1 * b1)
+ E = (A + B)
+ F = (C - (2 * A * x1) - (2 * B * x2))
+ G = ((A * x1 * x1) + (B * x2 * x2) - (C * x2) + D - 1)
+ H = ((y3 - y2) / (x32 - x2))
+ K = ((F * F) - (4 * E * G))
+
+ if K >= 0:
+ # In this case the line intersects the ellipse, so calculate intersection
+ if x2 >= x1:
+ ellipse1_x = ((F*-1) + sqrt(K)) / (2 * E)
+ ellipse1_y = ((H * (ellipse1_x - x2)) + y2)
+ else:
+ ellipse1_x = (((F*-1) - sqrt(K)) / (2 * E))
+ ellipse1_y = ((H * (ellipse1_x - x2)) + y2)
+ else:
+ # in this case, arc does not intersect ellipse, so just draw arc
+ ellipse1_x = x3
+ ellipse1_y = y3
+
+ return ellipse1_x, ellipse1_y
+
+
+
+def FindEndForCircle(radius, x1, y1, x2, y2):
+ H = sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))
+
+ if H == 0:
+ return x1, y1
+ else:
+ return radius * (x2 - x1) / H + x1, radius * (y2 - y1) / H + y1