git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/branches/WX_2_8_BRANCH@44515 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
		
			
				
	
	
		
			3180 lines
		
	
	
		
			108 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			3180 lines
		
	
	
		
			108 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # -*- coding: iso-8859-1 -*-
 | |
| #----------------------------------------------------------------------------
 | |
| # Name:         basic.py
 | |
| # Purpose:      The basic OGL shapes
 | |
| #
 | |
| # Author:       Pierre Hjälm (from C++ original by Julian Smart)
 | |
| #
 | |
| # Created:      2004-05-08
 | |
| # RCS-ID:       $Id$
 | |
| # Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart
 | |
| # Licence:      wxWindows license
 | |
| #----------------------------------------------------------------------------
 | |
| 
 | |
| import wx
 | |
| import math
 | |
| 
 | |
| 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 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 OnDelete(self):
 | |
|         if self!=self.GetShape():
 | |
|             del self
 | |
|             
 | |
|     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 = BlackForegroundPen
 | |
|         self._brush = wx.WHITE_BRUSH
 | |
|         self._font = NormalFont
 | |
|         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(NormalFont)
 | |
|         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 Delete(self):
 | |
|         """
 | |
|         Fully disconnect this shape from parents, children, the
 | |
|         canvas, etc.
 | |
|         """
 | |
|         if self._parent:
 | |
|             self._parent.GetChildren().remove(self)
 | |
| 
 | |
|         for child in self.GetChildren():
 | |
|             child.Delete()
 | |
| 
 | |
|         self.ClearText()
 | |
|         self.ClearRegions()
 | |
|         self.ClearAttachments()
 | |
| 
 | |
|         self._handlerShape = None
 | |
|         
 | |
|         if self._canvas:
 | |
|             self.RemoveFromCanvas(self._canvas)
 | |
| 
 | |
|         if self.GetEventHandler():
 | |
|             self.GetEventHandler().OnDelete()
 | |
|         self._eventHandler = None
 | |
|         
 | |
|     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 GetShadowMode(self):
 | |
|         """Return the current shadow mode setting"""
 | |
|         return self._shadowMode
 | |
| 
 | |
|     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)
 | |
|         
 | |
|         self._canvas = None
 | |
|         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.0
 | |
|         top = self._ypos - height / 2.0
 | |
|         right = self._xpos + width / 2.0
 | |
|         bottom = self._ypos + height / 2.0
 | |
| 
 | |
|         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 = math.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 \
 | |
|            len(region.GetFormattedText()) 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].GetColour()
 | |
| 
 | |
|     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)
 | |
| 
 | |
|         for region in self._regions:
 | |
|             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.0 - 2
 | |
|         topLeftY = yp - maxY / 2.0 - 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.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[0] <= pt2[0]
 | |
|         elif physicalAttachment in [1, 3]:
 | |
|             return pt1[1] <= pt2[1]
 | |
| 
 | |
|         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.0, y - h / 2.0],
 | |
|                 [x + w / 2.0, y - h / 2.0],
 | |
|                 [x + w / 2.0, y + h / 2.0],
 | |
|                 [x - w / 2.0, y + h / 2.0],
 | |
|                 [x - w / 2.0, y - h / 2.0],
 | |
|                 ]
 | |
| 
 | |
|         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 = float(w) / width
 | |
|         if height == 0:
 | |
|             scaleY = 1.0
 | |
|         else:
 | |
|             scaleY = float(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.0
 | |
|         bottom = heightMin / 2.0 + (maxY - minY)
 | |
|         left = -widthMin / 2.0
 | |
|         right = widthMin / 2.0 + (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.0
 | |
|         bottom = heightMin / 2.0 + (maxY - minY)
 | |
|         left = -widthMin / 2.0
 | |
|         right = widthMin / 2.0 + (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)
 | |
|             control.Delete()
 | |
|             self._controlPoints.remove(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[0], pt[1]
 | |
|         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.0
 | |
|                 bottom = self._ypos - h / 2.0
 | |
|                 left = self._xpos - w / 2.0
 | |
|                 right = self._xpos + w / 2.0
 | |
| 
 | |
|                 # 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
 | |
| 
 | |
|     # 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.0)
 | |
|             else:
 | |
|                 x = (secondPoint[0] - firstPoint[0]) / 2.0 # 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.0)
 | |
|             else:
 | |
|                 y = (secondPoint[1] - firstPoint[1]) / 2.0 # 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[0] = self.GetX()
 | |
|             neck[1] = root[1] - self._branchNeckLength
 | |
| 
 | |
|             shoulder1[0] = root[0] - totalBranchLength / 2.0
 | |
|             shoulder2[0] = root[0] + totalBranchLength / 2.0
 | |
| 
 | |
|             shoulder1[1] = neck[1]
 | |
|             shoulder2[1] = neck[1]
 | |
|         elif physicalAttachment == 1:
 | |
|             neck[0] = root[0] + self._branchNeckLength
 | |
|             neck[1] = root[1]
 | |
|             
 | |
|             shoulder1[0] = neck[0]
 | |
|             shoulder2[0] = neck[0]
 | |
| 
 | |
|             shoulder1[1] = neck[1] - totalBranchLength / 2.0
 | |
|             shoulder1[1] = neck[1] + totalBranchLength / 2.0
 | |
|         elif physicalAttachment == 2:
 | |
|             neck[0] = self.GetX()
 | |
|             neck[1] = root[1] + self._branchNeckLength
 | |
| 
 | |
|             shoulder1[0] = root[0] - totalBranchLength / 2.0
 | |
|             shoulder2[0] = root[0] + totalBranchLength / 2.0
 | |
| 
 | |
|             shoulder1[1] = neck[1]
 | |
|             shoulder2[1] = neck[1]
 | |
|         elif physicalAttachment == 3:
 | |
|             neck[0] = root[0] - self._branchNeckLength
 | |
|             neck[1] = root[1]
 | |
| 
 | |
|             shoulder1[0] = neck[0]
 | |
|             shoulder2[0] = neck[0]
 | |
| 
 | |
|             shoulder1[1] = neck[1] - totalBranchLength / 2.0
 | |
|             shoulder2[1] = neck[1] + totalBranchLength / 2.0
 | |
|         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[1] = neck[1] - self._branchStemLength
 | |
|             pt[0] = shoulder1[0] + n * self._branchSpacing
 | |
| 
 | |
|             stemPt[0] = pt[0]
 | |
|             stemPt[1] = neck[1]
 | |
|         elif physicalAttachment == 2:
 | |
|             pt[1] = neck[1] + self._branchStemLength
 | |
|             pt[0] = shoulder1[0] + n * self._branchStemLength
 | |
|             
 | |
|             stemPt[0] = pt[0]
 | |
|             stemPt[1] = neck[1]
 | |
|         elif physicalAttachment == 1:
 | |
|             pt[0] = neck[0] + self._branchStemLength
 | |
|             pt[1] = shoulder1[1] + n * self._branchSpacing
 | |
| 
 | |
|             stemPt[0] = neck[0]
 | |
|             stemPt[1] = pt[1]
 | |
|         elif physicalAttachment == 3:
 | |
|             pt[0] = neck[0] - self._branchStemLength
 | |
|             pt[1] = shoulder1[1] + n * self._branchSpacing
 | |
| 
 | |
|             stemPt[0] = neck[0]
 | |
|             stemPt[1] = pt[1]
 | |
|         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[0] = self.GetX()
 | |
|             root[1] = self.GetY() - height / 2.0
 | |
|         elif physicalAttachment == 1:
 | |
|             root[0] = self.GetX() + width / 2.0
 | |
|             root[1] = self.GetY()
 | |
|         elif physicalAttachment == 2:
 | |
|             root[0] = self.GetX()
 | |
|             root[1] = self.GetY() + height / 2.0
 | |
|         elif physicalAttachment == 3:
 | |
|             root[0] = self.GetX() - width / 2.0
 | |
|             root[1] = 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[0], root[1], neck[0], neck[1])
 | |
| 
 | |
|         if count > 1:
 | |
|             # Draw shoulder-to-shoulder line
 | |
|             dc.DrawLine(shoulder1[0], shoulder1[1], shoulder2[0], shoulder2[1])
 | |
|         # Draw all the little branches
 | |
|         for i in range(count):
 | |
|             pt, stemPt = self.GetBranchingAttachmentPoint(attachment, i)
 | |
|             dc.DrawLine(stemPt[0], stemPt[1], pt[0], pt[1])
 | |
| 
 | |
|             if self.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB and count > 1:
 | |
|                 blobSize = 6.0
 | |
|                 dc.DrawEllipse(stemPt[0] - blobSize / 2.0, stemPt[1] - blobSize / 2.0, 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(), math.pi / 2.0):
 | |
|             i = physicalAttachment - 1
 | |
|         elif RoughlyEqual(self.GetRotation(), math.pi):
 | |
|             i = physicalAttachment - 2
 | |
|         elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0):
 | |
|             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(), math.pi / 2.0):
 | |
|             i = logicalAttachment + 1
 | |
|         elif RoughlyEqual(self.GetRotation(), math.pi):
 | |
|             i = logicalAttachment + 2
 | |
|         elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0):
 | |
|             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 * math.pi
 | |
|         elif self._rotation > 2 * math.pi:
 | |
|             self._rotation -= 2 * math.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) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth)
 | |
|                 if self.GetY() > pt._controlPointDragStartY:
 | |
|                     newY2 = newY1 + newH
 | |
|                 else:
 | |
|                     newY1 = newY2 - newH
 | |
| 
 | |
|             newWidth = float(newX2 - newX1)
 | |
|             newHeight = float(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.0
 | |
|             pt._controlPointDragPosY = newY1 + newHeight / 2.0
 | |
|             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().OnBeginSize(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.0
 | |
|         else:
 | |
|             pt._controlPointDragStartX = self.GetX() - bound_x / 2.0
 | |
| 
 | |
|         if pt.GetY() < self.GetY():
 | |
|             pt._controlPointDragStartY = self.GetY() + bound_y / 2.0
 | |
|         else:
 | |
|             pt._controlPointDragStartY = self.GetY() - bound_y / 2.0
 | |
| 
 | |
|         if pt._type == CONTROL_POINT_HORIZONTAL:
 | |
|             pt._controlPointDragStartY = self.GetY() - bound_y / 2.0
 | |
|         elif pt._type == CONTROL_POINT_VERTICAL:
 | |
|             pt._controlPointDragStartX = self.GetX() - bound_x / 2.0
 | |
| 
 | |
|         # 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 & KEY_SHIFT or self.GetMaintainAspectRatio()):
 | |
|                 newH = (newX2 - newX1) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth)
 | |
|                 if pt.GetY() > pt._controlPointDragStartY:
 | |
|                     newY2 = newY1 + newH
 | |
|                 else:
 | |
|                     newY1 = newY2 - newH
 | |
| 
 | |
|             newWidth = float(newX2 - newX1)
 | |
|             newHeight = float(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.0
 | |
|             pt._controlPointDragPosY = newY1 + newHeight / 2.0
 | |
|             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.0
 | |
|         y1 = self._ypos - self._height / 2.0
 | |
| 
 | |
|         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[0] < left:
 | |
|                 left = point[0]
 | |
|             if point[0] > right:
 | |
|                 right = point[0]
 | |
| 
 | |
|             if point[1] < top:
 | |
|                 top = point[1]
 | |
|             if point[1] > bottom:
 | |
|                 bottom = point[1]
 | |
| 
 | |
|         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[0] < left:
 | |
|                 left = point[0]
 | |
|             if point[0] > right:
 | |
|                 right = point[0]
 | |
| 
 | |
|             if point[1] < top:
 | |
|                 top = point[1]
 | |
|             if point[1] > bottom:
 | |
|                 bottom = point[1]
 | |
| 
 | |
|         bwidth = right - left
 | |
|         bheight = bottom - top
 | |
| 
 | |
|         newCentreX = left + bwidth / 2.0
 | |
|         newCentreY = top + bheight / 2.0
 | |
| 
 | |
|         for i in range(len(self._points)):
 | |
|             self._points[i] = self._points[i][0] - newCentreX, self._points[i][1] - 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[0] + self._xpos)
 | |
|             ypoints.append(point[1] + 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 = math.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(float(new_width) / self._originalWidth)
 | |
|         y_proportion = abs(float(new_height) / self._originalHeight)
 | |
| 
 | |
|         for i in range(max(len(self._points), len(self._originalPoints))):
 | |
|             self._points[i] = wx.Point(self._originalPoints[i][0] * x_proportion, 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[0], point[1])
 | |
|             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[0] - firstPoint[0]) / 2.0 + firstPoint[0]
 | |
|         y = (secondPoint[1] - firstPoint[1]) / 2.0 + firstPoint[1]
 | |
|         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[0] == 0:
 | |
|                     if y2 > y1 and point[1] > 0:
 | |
|                         return point[0] + self._xpos, point[1] + self._ypos
 | |
|                     elif y2 < y1 and point[1] < 0:
 | |
|                         return point[0] + self._xpos, point[1] + self._ypos
 | |
| 
 | |
|         xpoints = []
 | |
|         ypoints = []
 | |
|         for point in self._points:
 | |
|             xpoints.append(point[0] + self._xpos)
 | |
|             ypoints.append(point[1] + 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(float(w) / self._originalWidth)
 | |
|         y_proportion = abs(float(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[0], point[1])
 | |
|             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[0]
 | |
|             self._controlPoints[i]._yoffset = point[1]
 | |
|             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[0] + self._xpos, point[1] + 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 = math.sin(actualTheta)
 | |
|         cosTheta = math.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 i in range(len(self._points)):
 | |
|             x1, y1 = self._points[i]
 | |
| 
 | |
|             self._points[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
 | |
| 
 | |
|         for i in range(len(self._originalPoints)):
 | |
|             x1, y1 = self._originalPoints[i]
 | |
| 
 | |
|             self._originalPoints[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
 | |
| 
 | |
|         # Added by Pierre Hjälm. If we don't do this the outline will be
 | |
|         # the wrong size. Hopefully it won't have any ill effects.
 | |
|         self.UpdateOriginalPoints()
 | |
|         
 | |
|         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()[0], pt.GetNewSize()[1])
 | |
| 
 | |
|     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 = math.sqrt((x - self.GetX()) * (x - self.GetX()) + (y - self.GetY()) * (y - self.GetY()))
 | |
| 
 | |
|         pt._originalDistance = dist
 | |
|         pt._originalSize[0] = bound_x
 | |
|         pt._originalSize[1] = 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()[0], pt.GetNewSize()[1])
 | |
| 
 | |
|         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()[0], pt.GetNewSize()[1])
 | |
| 
 | |
|         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.0 + self._shadowOffsetX,
 | |
|                            self._ypos - self.GetHeight() / 2.0 + 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.0, self._ypos - self.GetHeight() / 2.0, 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.0
 | |
|             bottom = self._ypos - self._height / 2.0
 | |
|             left = self._xpos - self._width / 2.0
 | |
|             right = self._xpos + self._width / 2.0
 | |
| 
 | |
|             physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
 | |
| 
 | |
|             if physicalAttachment == 0:
 | |
|                 if self._spaceAttachments:
 | |
|                     x = left + (nth + 1) * self._width / (no_arcs + 1.0)
 | |
|                 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.0)
 | |
|                 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.0)
 | |
|                 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.0)
 | |
|                 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.0, 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.Pen(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 = math.sqrt((x - self._shape.GetX()) * (x - self._shape.GetX()) + (y - self._shape.GetY()) * (y - self._shape.GetY()))
 | |
| 
 | |
|         self._newSize[0] = dist / self._originalDistance * self._originalSize[0]
 | |
|         self._newSize[1] = dist / self._originalDistance * self._originalSize[1]
 | |
| 
 | |
|     # 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 *
 |