1 # -*- coding: iso-8859-1 -*- 
   2 #---------------------------------------------------------------------------- 
   4 # Purpose:      LineShape class 
   6 # Author:       Pierre Hjälm (from C++ original by Julian Smart) 
  10 # Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart 
  11 # Licence:      wxWindows license 
  12 #---------------------------------------------------------------------------- 
  17 from _basic 
import Shape
, ShapeRegion
, ControlPoint
, RectangleShape
 
  18 from _oglmisc 
import * 
  20 # Line alignment flags 
  22 LINE_ALIGNMENT_HORIZ 
=              1 
  23 LINE_ALIGNMENT_VERT 
=               0 
  24 LINE_ALIGNMENT_TO_NEXT_HANDLE 
=     2 
  25 LINE_ALIGNMENT_NONE 
=               0 
  29 class LineControlPoint(ControlPoint
): 
  30     def __init__(self
, theCanvas 
= None, object = None, size 
= 0.0, x 
= 0.0, y 
= 0.0, the_type 
= 0): 
  31         ControlPoint
.__init
__(self
, theCanvas
, object, size
, x
, y
, the_type
) 
  36         self
._originalPos 
= None 
  39         RectangleShape
.OnDraw(self
, dc
) 
  41     # Implement movement of Line point 
  42     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
  43         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
  45     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
  46         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
  48     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
  49         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
  53 class ArrowHead(object): 
  54     def __init__(self
, type = 0, end 
= 0, size 
= 0.0, dist 
= 0.0, name 
= "", mf 
= None, arrowId 
= -1): 
  55         if isinstance(type, ArrowHead
): 
  58             self
._arrowType 
= type 
  60             self
._arrowSize 
= size
 
  65             self
._arrowName 
= name
 
  72         return self
._arrowType
 
  74     def GetPosition(self
): 
  77     def SetPosition(self
, pos
): 
  90         return self
._arrowSize
 
  92     def SetSize(self
, size
): 
  93         self
._arrowSize 
= size
 
  94         if self
._arrowType 
== ARROW_METAFILE 
and self
._metaFile
: 
  95             oldWidth 
= self
._metaFile
._width
 
  99             scale 
= float(size
) / oldWidth
 
 101                 self
._metaFile
.Scale(scale
, scale
) 
 104         return self
._arrowName
 
 106     def SetXOffset(self
, x
): 
 109     def SetYOffset(self
, y
): 
 112     def GetMetaFile(self
): 
 113         return self
._metaFile
 
 118     def GetArrowEnd(self
): 
 119         return self
._arrowEnd
 
 121     def GetArrowSize(self
): 
 122         return self
._arrowSize
 
 124     def SetSpacing(self
, sp
): 
 129 class LabelShape(RectangleShape
): 
 130     def __init__(self
, parent
, region
, w
, h
): 
 131         RectangleShape
.__init
__(self
, w
, h
) 
 132         self
._lineShape 
= parent
 
 133         self
._shapeRegion 
= region
 
 134         self
.SetPen(wx
.ThePenList
.FindOrCreatePen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)) 
 136     def OnDraw(self
, dc
): 
 137         if self
._lineShape 
and not self
._lineShape
.GetDrawHandles(): 
 140         x1 
= self
._xpos 
- self
._width 
/ 2.0 
 141         y1 
= self
._ypos 
- self
._height 
/ 2.0 
 144             if self
._pen
.GetWidth() == 0: 
 145                 dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
)) 
 148         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 150         if self
._cornerRadius 
> 0: 
 151             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
 153             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
 155     def OnDrawContents(self
, dc
): 
 158     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 159         RectangleShape
.OnDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
 161     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 162         RectangleShape
.OnBeginDragLeft(self
, x
, y
, keys
, attachment
) 
 164     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 165         RectangleShape
.OnEndDragLeft(self
, x
, y
, keys
, attachment
) 
 167     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display
): 
 168         return self
._lineShape
.OnLabelMovePre(dc
, self
, x
, y
, old_x
, old_y
, display
) 
 170     # Divert left and right clicks to line object 
 171     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 172         self
._lineShape
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
 174     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 175         self
._lineShape
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
 179 class LineShape(Shape
): 
 180     """LineShape may be attached to two nodes; 
 181     it may be segmented, in which case a control point is drawn for each joint. 
 183     A wxLineShape may have arrows at the beginning, end and centre. 
 191         self
._sensitivity 
= OP_CLICK_LEFT | OP_CLICK_RIGHT
 
 192         self
._draggable 
= False 
 193         self
._attachmentTo 
= 0 
 194         self
._attachmentFrom 
= 0 
 197         self
._erasing 
= False 
 198         self
._arrowSpacing 
= 5.0 
 199         self
._ignoreArrowOffsets 
= False 
 200         self
._isSpline 
= False 
 201         self
._maintainStraightLines 
= False 
 202         self
._alignmentStart 
= 0 
 203         self
._alignmentEnd 
= 0 
 205         self
._lineControlPoints 
= None 
 207         # Clear any existing regions (created in an earlier constructor) 
 208         # and make the three line regions. 
 210         for name 
in ["Middle","Start","End"]: 
 211             newRegion 
= ShapeRegion() 
 212             newRegion
.SetName(name
) 
 213             newRegion
.SetSize(150, 50) 
 214             self
._regions
.append(newRegion
) 
 216         self
._labelObjects 
= [None, None, None] 
 217         self
._lineOrientations 
= [] 
 218         self
._lineControlPoints 
= [] 
 222         if self
._lineControlPoints
: 
 223             self
.ClearPointList(self
._lineControlPoints
) 
 224             self
._lineControlPoints 
= [] 
 226             if self
._labelObjects
[i
]: 
 227                 self
._labelObjects
[i
].Select(False) 
 228                 self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
 229         self
._labelObjects 
= [] 
 230         self
.ClearArrowsAtPosition(-1) 
 233         """Return the 'from' object.""" 
 237         """Return the 'to' object.""" 
 240     def GetAttachmentFrom(self
): 
 241         """Return the attachment point on the 'from' node.""" 
 242         return self
._attachmentFrom
 
 244     def GetAttachmentTo(self
): 
 245         """Return the attachment point on the 'to' node.""" 
 246         return self
._attachmentTo
 
 248     def GetLineControlPoints(self
): 
 249         return self
._lineControlPoints
 
 251     def SetSpline(self
, spline
): 
 252         """Specifies whether a spline is to be drawn through the control points.""" 
 253         self
._isSpline 
= spline
 
 256         """TRUE if a spline is drawn through the control points.""" 
 257         return self
._isSpline
 
 259     def SetAttachmentFrom(self
, attach
): 
 260         """Set the 'from' shape attachment.""" 
 261         self
._attachmentFrom 
= attach
 
 263     def SetAttachmentTo(self
, attach
): 
 264         """Set the 'to' shape attachment.""" 
 265         self
._attachmentTo 
= attach
 
 267     # This is really to distinguish between lines and other images. 
 268     # For lines, want to pass drag to canvas, since lines tend to prevent 
 269     # dragging on a canvas (they get in the way.) 
 273     def SetIgnoreOffsets(self
, ignore
): 
 274         """Set whether to ignore offsets from the end of the line when drawing.""" 
 275         self
._ignoreArrowOffsets 
= ignore
 
 278         return self
._arcArrows
 
 280     def GetAlignmentStart(self
): 
 281         return self
._alignmentStart
 
 283     def GetAlignmentEnd(self
): 
 284         return self
._alignmentEnd
 
 286     def IsEnd(self
, nodeObject
): 
 287         """TRUE if shape is at the end of the line.""" 
 288         return self
._to 
== nodeObject
 
 290     def MakeLineControlPoints(self
, n
): 
 291         """Make a given number of control points (minimum of two).""" 
 292         if self
._lineControlPoints
: 
 293             self
.ClearPointList(self
._lineControlPoints
) 
 294         self
._lineControlPoints 
= [] 
 297             point 
= wx
.RealPoint(-999, -999) 
 298             self
._lineControlPoints
.append(point
) 
 300     def InsertLineControlPoint(self
, dc 
= None): 
 301         """Insert a control point at an arbitrary position.""" 
 305         last_point 
= self
._lineControlPoints
[-1] 
 306         second_last_point 
= self
._lineControlPoints
[-2] 
 308         line_x 
= (last_point
[0] + second_last_point
[0]) / 2.0 
 309         line_y 
= (last_point
[1] + second_last_point
[1]) / 2.0 
 311         point 
= wx
.RealPoint(line_x
, line_y
) 
 312         self
._lineControlPoints
.insert(len(self
._lineControlPoints
), point
) 
 314     def DeleteLineControlPoint(self
): 
 315         """Delete an arbitary point on the line.""" 
 316         if len(self
._lineControlPoints
) < 3: 
 319         del self
._lineControlPoints
[-2] 
 322     def Initialise(self
): 
 323         """Initialise the line object.""" 
 324         if self
._lineControlPoints
: 
 325             # Just move the first and last control points 
 326             first_point 
= self
._lineControlPoints
[0] 
 327             last_point 
= self
._lineControlPoints
[-1] 
 329             # If any of the line points are at -999, we must 
 330             # initialize them by placing them half way between the first 
 333             for i 
in range(1,len(self
._lineControlPoints
)): 
 334                 point 
= self
._lineControlPoints
[i
] 
 336                     if first_point
[0] < last_point
[0]: 
 342                     if first_point
[1] < last_point
[1]: 
 348                     self
._lineControlPoints
[i
] = wx
.RealPoint((x2 
- x1
) / 2.0 + x1
, (y2 
- y1
) / 2.0 + y1
) 
 350     def FormatText(self
, dc
, s
, i
): 
 351         """Format a text string according to the region size, adding 
 352         strings with positions to region text list. 
 356         if len(self
._regions
) == 0 or i 
>= len(self
._regions
): 
 359         region 
= self
._regions
[i
] 
 361         dc
.SetFont(region
.GetFont()) 
 363         w
, h 
= region
.GetSize() 
 364         # Initialize the size if zero 
 365         if (w 
== 0 or h 
== 0) and s
: 
 369         string_list 
= FormatText(dc
, s
, w 
- 5, h 
- 5, region
.GetFormatMode()) 
 370         for s 
in string_list
: 
 371             line 
= ShapeTextLine(0.0, 0.0, s
) 
 372             region
.GetFormattedText().append(line
) 
 376         if region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
: 
 377             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w
, h
) 
 378             if actualW 
!= w 
or actualH 
!= h
: 
 379                 xx
, yy 
= self
.GetLabelPosition(i
) 
 380                 self
.EraseRegion(dc
, region
, xx
, yy
) 
 381                 if len(self
._labelObjects
) < i
: 
 382                     self
._labelObjects
[i
].Select(False, dc
) 
 383                     self
._labelObjects
[i
].Erase(dc
) 
 384                     self
._labelObjects
[i
].SetSize(actualW
, actualH
) 
 386                 region
.SetSize(actualW
, actualH
) 
 388                 if len(self
._labelObjects
) < i
: 
 389                     self
._labelObjects
[i
].Select(True, dc
) 
 390                     self
._labelObjects
[i
].Draw(dc
) 
 392         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW
, actualH
, region
.GetFormatMode()) 
 393         self
._formatted 
= True 
 395     def DrawRegion(self
, dc
, region
, x
, y
): 
 396         """Format one region at this position.""" 
 397         if self
.GetDisableLabel(): 
 400         w
, h 
= region
.GetSize() 
 402         # Get offset from x, y 
 403         xx
, yy 
= region
.GetPosition() 
 408         # First, clear a rectangle for the text IF there is any 
 409         if len(region
.GetFormattedText()): 
 410             dc
.SetPen(self
.GetBackgroundPen()) 
 411             dc
.SetBrush(self
.GetBackgroundBrush()) 
 415                 dc
.SetFont(region
.GetFont()) 
 416                 dc
.DrawRectangle(xp 
- w 
/ 2.0, yp 
- h 
/ 2.0, w
, h
) 
 420                 dc
.SetTextForeground(region
.GetActualColourObject()) 
 422                 DrawFormattedText(dc
, region
.GetFormattedText(), xp
, yp
, w
, h
, region
.GetFormatMode()) 
 424     def EraseRegion(self
, dc
, region
, x
, y
): 
 425         """Erase one region at this position.""" 
 426         if self
.GetDisableLabel(): 
 429         w
, h 
= region
.GetSize() 
 431         # Get offset from x, y 
 432         xx
, yy 
= region
.GetPosition() 
 437         if region
.GetFormattedText(): 
 438             dc
.SetPen(self
.GetBackgroundPen()) 
 439             dc
.SetBrush(self
.GetBackgroundBrush()) 
 441             dc
.DrawRectangle(xp 
- w 
/ 2.0, yp 
- h 
/ 2.0, w
, h
) 
 443     def GetLabelPosition(self
, position
): 
 444         """Get the reference point for a label. 
 446         Region x and y are offsets from this. 
 447         position is 0 (middle), 1 (start), 2 (end). 
 450             # Want to take the middle section for the label 
 451             half_way 
= int(len(self
._lineControlPoints
) / 2.0) 
 453             # Find middle of this line 
 454             point 
= self
._lineControlPoints
[half_way 
- 1] 
 455             next_point 
= self
._lineControlPoints
[half_way
] 
 457             dx 
= next_point
[0] - point
[0] 
 458             dy 
= next_point
[1] - point
[1] 
 460             return point
[0] + dx 
/ 2.0, point
[1] + dy 
/ 2.0 
 462             return self
._lineControlPoints
[0][0], self
._lineControlPoints
[0][1] 
 464             return self
._lineControlPoints
[-1][0], self
._lineControlPoints
[-1][1] 
 466     def Straighten(self
, dc 
= None): 
 467         """Straighten verticals and horizontals.""" 
 468         if len(self
._lineControlPoints
) < 3: 
 474         GraphicsStraightenLine(self
._lineControlPoints
[-1], self
._lineControlPoints
[-2]) 
 476         for i 
in range(len(self
._lineControlPoints
) - 2): 
 477             GraphicsStraightenLine(self
._lineControlPoints
[i
], self
._lineControlPoints
[i 
+ 1]) 
 483         """Unlink the line from the nodes at either end.""" 
 485             self
._to
.GetLines().remove(self
) 
 487             self
._from
.GetLines().remove(self
) 
 491     def SetEnds(self
, x1
, y1
, x2
, y2
): 
 492         """Set the end positions of the line.""" 
 493         self
._lineControlPoints
[0] = wx
.RealPoint(x1
, y1
) 
 494         self
._lineControlPoints
[-1] = wx
.RealPoint(x2
, y2
) 
 497         self
._xpos 
= (x1 
+ x2
) / 2.0 
 498         self
._ypos 
= (y1 
+ y2
) / 2.0 
 500     # Get absolute positions of ends 
 502         """Get the visible endpoints of the lines for drawing between two objects.""" 
 503         first_point 
= self
._lineControlPoints
[0] 
 504         last_point 
= self
._lineControlPoints
[-1] 
 506         return (first_point
[0], first_point
[1]), (last_point
[0], last_point
[1]) 
 508     def SetAttachments(self
, from_attach
, to_attach
): 
 509         """Specify which object attachment points should be used at each end 
 512         self
._attachmentFrom 
= from_attach
 
 513         self
._attachmentTo 
= to_attach
 
 515     def HitTest(self
, x
, y
): 
 516         if not self
._lineControlPoints
: 
 519         # Look at label regions in case mouse is over a label 
 520         inLabelRegion 
= False 
 523                 region 
= self
._regions
[i
] 
 524                 if len(region
._formattedText
): 
 525                     xp
, yp 
= self
.GetLabelPosition(i
) 
 526                     # Offset region from default label position 
 527                     cx
, cy 
= region
.GetPosition() 
 528                     cw
, ch 
= region
.GetSize()  
 532                     rLeft 
= cx 
- cw 
/ 2.0 
 534                     rRight 
= cx 
+ cw 
/ 2.0 
 535                     rBottom 
= cy 
+ ch 
/ 2.0 
 536                     if x 
> rLeft 
and x 
< rRight 
and y 
> rTop 
and y 
< rBottom
: 
 540         for i 
in range(len(self
._lineControlPoints
) - 1): 
 541             point1 
= self
._lineControlPoints
[i
] 
 542             point2 
= self
._lineControlPoints
[i 
+ 1] 
 544             # For inaccurate mousing allow 8 pixel corridor 
 547             dx 
= point2
[0] - point1
[0] 
 548             dy 
= point2
[1] - point1
[1] 
 550             seg_len 
= math
.sqrt(dx 
* dx 
+ dy 
* dy
) 
 551             if dy 
== 0 or dx 
== 0: 
 553             distance_from_seg 
= seg_len 
* float((x 
- point1
[0]) * dy 
- (y 
- point1
[1]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 554             distance_from_prev 
= seg_len 
* float((y 
- point1
[1]) * dy 
+ (x 
- point1
[0]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 556             if abs(distance_from_seg
) < extra 
and distance_from_prev 
>= 0 and distance_from_prev 
<= seg_len 
or inLabelRegion
: 
 557                 return 0, distance_from_seg
 
 561     def DrawArrows(self
, dc
): 
 562         """Draw all arrows.""" 
 563         # Distance along line of each arrow: space them out evenly 
 568         for arrow 
in self
._arcArrows
: 
 569             ah 
= arrow
.GetArrowEnd() 
 570             if ah 
== ARROW_POSITION_START
: 
 571                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 572                     # If specified, x offset is proportional to line length 
 573                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 575                     self
.DrawArrow(dc
, arrow
, startArrowPos
, False) 
 576                     startArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 577             elif ah 
== ARROW_POSITION_END
: 
 578                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 579                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 581                     self
.DrawArrow(dc
, arrow
, endArrowPos
, False) 
 582                     endArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 583             elif ah 
== ARROW_POSITION_MIDDLE
: 
 584                 arrow
.SetXOffset(middleArrowPos
) 
 585                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 586                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 588                     self
.DrawArrow(dc
, arrow
, middleArrowPos
, False) 
 589                     middleArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 591     def DrawArrow(self
, dc
, arrow
, XOffset
, proportionalOffset
): 
 592         """Draw the given arrowhead (or annotation).""" 
 593         first_line_point 
= self
._lineControlPoints
[0] 
 594         second_line_point 
= self
._lineControlPoints
[1] 
 596         last_line_point 
= self
._lineControlPoints
[-1] 
 597         second_last_line_point 
= self
._lineControlPoints
[-2] 
 599         # Position of start point of line, at the end of which we draw the arrow 
 600         startPositionX
, startPositionY 
= 0.0, 0.0 
 602         ap 
= arrow
.GetPosition() 
 603         if ap 
== ARROW_POSITION_START
: 
 604             # If we're using a proportional offset, calculate just where this 
 605             # will be on the line. 
 607             if proportionalOffset
: 
 608                 totalLength 
= math
.sqrt((second_line_point
[0] - first_line_point
[0]) * (second_line_point
[0] - first_line_point
[0]) + (second_line_point
[1] - first_line_point
[1]) * (second_line_point
[1] - first_line_point
[1])) 
 609                 realOffset 
= XOffset 
* totalLength
 
 611             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_line_point
[0], second_line_point
[1], first_line_point
[0], first_line_point
[1], realOffset
) 
 613             startPositionX 
= second_line_point
[0] 
 614             startPositionY 
= second_line_point
[1] 
 615         elif ap 
== ARROW_POSITION_END
: 
 616             # If we're using a proportional offset, calculate just where this 
 617             # will be on the line. 
 619             if proportionalOffset
: 
 620                 totalLength 
= math
.sqrt((second_last_line_point
[0] - last_line_point
[0]) * (second_last_line_point
[0] - last_line_point
[0]) + (second_last_line_point
[1] - last_line_point
[1]) * (second_last_line_point
[1] - last_line_point
[1])); 
 621                 realOffset 
= XOffset 
* totalLength
 
 623             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], last_line_point
[0], last_line_point
[1], realOffset
) 
 625             startPositionX 
= second_last_line_point
[0] 
 626             startPositionY 
= second_last_line_point
[1] 
 627         elif ap 
== ARROW_POSITION_MIDDLE
: 
 628             # Choose a point half way between the last and penultimate points 
 629             x 
= (last_line_point
[0] + second_last_line_point
[0]) / 2.0 
 630             y 
= (last_line_point
[1] + second_last_line_point
[1]) / 2.0 
 632             # If we're using a proportional offset, calculate just where this 
 633             # will be on the line. 
 635             if proportionalOffset
: 
 636                 totalLength 
= math
.sqrt((second_last_line_point
[0] - x
) * (second_last_line_point
[0] - x
) + (second_last_line_point
[1] - y
) * (second_last_line_point
[1] - y
)); 
 637                 realOffset 
= XOffset 
* totalLength
 
 639             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], x
, y
, realOffset
) 
 640             startPositionX 
= second_last_line_point
[0] 
 641             startPositionY 
= second_last_line_point
[1] 
 643         # Add yOffset to arrow, if any 
 645         # The translation that the y offset may give 
 648         if arrow
.GetYOffset 
and not self
._ignoreArrowOffsets
: 
 652             #   (x1, y1)--------------(x3, y3)------------------(x2, y2) 
 653             #   x4 = x3 - d * math.sin(theta) 
 654             #   y4 = y3 + d * math.cos(theta) 
 656             #   Where theta = math.tan(-1) of (y3-y1) / (x3-x1) 
 659             x3 
= float(positionOnLineX
) 
 660             y3 
= float(positionOnLineY
) 
 661             d 
= -arrow
.GetYOffset() # Negate so +offset is above line 
 664                 theta 
= math
.pi 
/ 2.0 
 666                 theta 
= math
.atan((y3 
- y1
) / (x3 
- x1
)) 
 668             x4 
= x3 
- d 
* math
.sin(theta
) 
 669             y4 
= y3 
+ d 
* math
.cos(theta
) 
 671             deltaX 
= x4 
- positionOnLineX
 
 672             deltaY 
= y4 
- positionOnLineY
 
 674         at 
= arrow
._GetType
() 
 675         if at 
== ARROW_ARROW
: 
 676             arrowLength 
= arrow
.GetSize() 
 677             arrowWidth 
= arrowLength 
/ 3.0 
 679             tip_x
, tip_y
, side1_x
, side1_y
, side2_x
, side2_y 
= GetArrowPoints(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, arrowLength
, arrowWidth
) 
 681             points 
= [[tip_x
, tip_y
], 
 687             dc
.SetBrush(self
._brush
) 
 688             dc
.DrawPolygon(points
) 
 689         elif at 
in [ARROW_HOLLOW_CIRCLE
, ARROW_FILLED_CIRCLE
]: 
 690             # Find point on line of centre of circle, which is a radius away 
 691             # from the end position 
 692             diameter 
= arrow
.GetSize() 
 693             x
, y 
= GetPointOnLine(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, 
 694                                positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, 
 696             x1 
= x 
- diameter 
/ 2.0 
 697             y1 
= y 
- diameter 
/ 2.0 
 699             if arrow
._GetType
() == ARROW_HOLLOW_CIRCLE
: 
 700                 dc
.SetBrush(self
.GetBackgroundBrush()) 
 702                 dc
.SetBrush(self
._brush
) 
 704             dc
.DrawEllipse(x1
, y1
, diameter
, diameter
) 
 705         elif at 
== ARROW_SINGLE_OBLIQUE
: 
 707         elif at 
== ARROW_METAFILE
: 
 708             if arrow
.GetMetaFile(): 
 709                 # Find point on line of centre of object, which is a half-width away 
 710                 # from the end position 
 713                 #  <-- start pos  <-----><-- positionOnLineX 
 715                 #  --------------|  x  | <-- e.g. rectangular arrowhead 
 718                 x
, y 
= GetPointOnLine(startPositionX
, startPositionY
, 
 719                                    positionOnLineX
, positionOnLineY
, 
 720                                    arrow
.GetMetaFile()._width 
/ 2.0) 
 721                 # Calculate theta for rotating the metafile. 
 724                 # |     o(x2, y2)   'o' represents the arrowhead. 
 729                 # |______________________ 
 734                 x2 
= float(positionOnLineX
) 
 735                 y2 
= float(positionOnLineY
) 
 737                 if x1 
== x2 
and y1 
== y2
: 
 739                 elif x1 
== x2 
and y1 
> y2
: 
 740                     theta 
= 3.0 * math
.pi 
/ 2.0 
 741                 elif x1 
== x2 
and y2 
> y1
: 
 742                     theta 
= math
.pi 
/ 2.0 
 743                 elif x2 
> x1 
and y2 
>= y1
: 
 744                     theta 
= math
.atan((y2 
- y1
) / (x2 
- x1
)) 
 746                     theta 
= math
.pi 
+ math
.atan((y2 
- y1
) / (x2 
- x1
)) 
 747                 elif x2 
> x1 
and y2 
< y1
: 
 748                     theta 
= 2 * math
.pi 
+ math
.atan((y2 
- y1
) / (x2 
- x1
)) 
 750                     raise "Unknown arrowhead rotation case" 
 752                 # Rotate about the centre of the object, then place 
 753                 # the object on the line. 
 754                 if arrow
.GetMetaFile().GetRotateable(): 
 755                     arrow
.GetMetaFile().Rotate(0.0, 0.0, theta
) 
 758                     # If erasing, just draw a rectangle 
 759                     minX
, minY
, maxX
, maxY 
= arrow
.GetMetaFile().GetBounds() 
 760                     # Make erasing rectangle slightly bigger or you get droppings 
 762                     dc
.DrawRectangle(deltaX 
+ x 
+ minX 
- extraPixels 
/ 2.0, deltaY 
+ y 
+ minY 
- extraPixels 
/ 2.0, maxX 
- minX 
+ extraPixels
, maxY 
- minY 
+ extraPixels
) 
 764                     arrow
.GetMetaFile().Draw(dc
, x 
+ deltaX
, y 
+ deltaY
) 
 766     def OnErase(self
, dc
): 
 768         old_brush 
= self
._brush
 
 770         bg_pen 
= self
.GetBackgroundPen() 
 771         bg_brush 
= self
.GetBackgroundBrush() 
 773         self
.SetBrush(bg_brush
) 
 775         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
 777             dc
.SetFont(self
._font
) 
 779         # Undraw text regions 
 782                 x
, y 
= self
.GetLabelPosition(i
) 
 783                 self
.EraseRegion(dc
, self
._regions
[i
], x
, y
) 
 786         dc
.SetPen(self
.GetBackgroundPen()) 
 787         dc
.SetBrush(self
.GetBackgroundBrush()) 
 789         # Drawing over the line only seems to work if the line has a thickness 
 791         if old_pen 
and old_pen
.GetWidth() > 1: 
 792             dc
.DrawRectangle(self
._xpos 
- bound_x 
/ 2.0 - 2, self
._ypos 
- bound_y 
/ 2.0 - 2, 
 793                              bound_x 
+ 4, bound_y 
+ 4) 
 796             self
.GetEventHandler().OnDraw(dc
) 
 797             self
.GetEventHandler().OnEraseControlPoints(dc
) 
 798             self
._erasing 
= False 
 803             self
.SetBrush(old_brush
) 
 805     def GetBoundingBoxMin(self
): 
 806         x1
, y1 
= 10000, 10000 
 807         x2
, y2 
= -10000, -10000 
 809         for point 
in self
._lineControlPoints
: 
 819         return x2 
- x1
, y2 
- y1
 
 821     # For a node image of interest, finds the position of this arc 
 822     # amongst all the arcs which are attached to THIS SIDE of the node image, 
 823     # and the number of same. 
 824     def FindNth(self
, image
, incoming
): 
 825         """Find the position of the line on the given object. 
 827         Specify whether incoming or outgoing lines are being considered 
 833         if image 
== self
._to
: 
 834             this_attachment 
= self
._attachmentTo
 
 836             this_attachment 
= self
._attachmentFrom
 
 838         # Find number of lines going into / out of this particular attachment point 
 839         for line 
in image
.GetLines(): 
 840             if line
._from 
== image
: 
 841                 # This is the nth line attached to 'image' 
 842                 if line 
== self 
and not incoming
: 
 845                 # Increment num count if this is the same side (attachment number) 
 846                 if line
._attachmentFrom 
== this_attachment
: 
 849             if line
._to 
== image
: 
 850                 # This is the nth line attached to 'image' 
 851                 if line 
== self 
and incoming
: 
 854                 # Increment num count if this is the same side (attachment number) 
 855                 if line
._attachmentTo 
== this_attachment
: 
 860     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 862         old_brush 
= self
._brush
 
 864         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
 865         self
.SetPen(dottedPen
) 
 866         self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 868         self
.GetEventHandler().OnDraw(dc
) 
 875             self
.SetBrush(old_brush
) 
 879     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 883         if self
._lineControlPoints 
and not (x_offset 
== 0 and y_offset 
== 0): 
 884             for point 
in self
._lineControlPoints
: 
 888         # Move temporary label rectangles if necessary 
 890             if self
._labelObjects
[i
]: 
 891                 self
._labelObjects
[i
].Erase(dc
) 
 892                 xp
, yp 
= self
.GetLabelPosition(i
) 
 893                 if i 
< len(self
._regions
): 
 894                     xr
, yr 
= self
._regions
[i
].GetPosition() 
 897                 self
._labelObjects
[i
].Move(dc
, xp 
+ xr
, yp 
+ yr
) 
 900     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
 901         """Called when a connected object has moved, to move the link to 
 904         if not self
._from 
or not self
._to
: 
 907         if len(self
._lineControlPoints
) > 2: 
 910         # Do each end - nothing in the middle. User has to move other points 
 911         # manually if necessary 
 912         end_x
, end_y
, other_end_x
, other_end_y 
= self
.FindLineEndPoints() 
 914         oldX
, oldY 
= self
._xpos
, self
._ypos
 
 916         self
.SetEnds(end_x
, end_y
, other_end_x
, other_end_y
) 
 918         # Do a second time, because one may depend on the other 
 919         end_x
, end_y
, other_end_x
, other_end_y 
= self
.FindLineEndPoints() 
 920         self
.SetEnds(end_x
, end_y
, other_end_x
, other_end_y
) 
 922         # Try to move control points with the arc 
 923         x_offset 
= self
._xpos 
- oldX
 
 924         y_offset 
= self
._ypos 
- oldY
 
 926         # Only move control points if it's a self link. And only works 
 927         # if attachment mode is ON 
 928         if self
._from 
== self
._to 
and self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE 
and moveControlPoints 
and self
._lineControlPoints 
and not (x_offset 
== 0 and y_offset 
== 0): 
 929             for point 
in self
._lineControlPoints
[1:-1]: 
 933         self
.Move(dc
, self
._xpos
, self
._ypos
) 
 935     def FindLineEndPoints(self
): 
 936         """Finds the x, y points at the two ends of the line. 
 938         This function can be used by e.g. line-routing routines to 
 939         get the actual points on the two node images where the lines will be 
 942         if not self
._from 
or not self
._to
: 
 945         # Do each end - nothing in the middle. User has to move other points 
 946         # manually if necessary. 
 947         second_point 
= self
._lineControlPoints
[1] 
 948         second_last_point 
= self
._lineControlPoints
[-2] 
 950         if len(self
._lineControlPoints
) > 2: 
 951             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 952                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) # Not incoming 
 953                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 955                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), second_point
[0], second_point
[1]) 
 957             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 958                 nth
, no_arch 
= self
.FindNth(self
._to
, True) # Incoming 
 959                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arch
, self
) 
 961                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), second_last_point
[0], second_last_point
[1]) 
 963             fromX 
= self
._from
.GetX() 
 964             fromY 
= self
._from
.GetY() 
 965             toX 
= self
._to
.GetX() 
 966             toY 
= self
._to
.GetY() 
 968             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 969                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) 
 970                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 974             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 975                 nth
, no_arcs 
= self
.FindNth(self
._to
, True) 
 976                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arcs
, self
) 
 980             if self
._from
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 981                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), toX
, toY
) 
 983             if self
._to
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 984                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), fromX
, fromY
) 
 986         return end_x
, end_y
, other_end_x
, other_end_y
 
 989     def OnDraw(self
, dc
): 
 990         if not self
._lineControlPoints
: 
 996             dc
.SetBrush(self
._brush
) 
 999         for point 
in self
._lineControlPoints
: 
1000             points
.append(wx
.Point(point
[0], point
[1])) 
1004             dc
.DrawSpline(points
) 
1006             dc
.DrawLines(points
) 
1008         if sys
.platform
[:3] == "win": 
1009             # For some reason, last point isn't drawn under Windows 
1011             dc
.DrawPoint(pt
[0], pt
[1]) 
1013         # Problem with pen - if not a solid pen, does strange things 
1014         # to the arrowhead. So make (get) a new pen that's solid. 
1015         if self
._pen 
and self
._pen
.GetStyle() != wx
.SOLID
: 
1016             solid_pen 
= wx
.ThePenList
.FindOrCreatePen(self
._pen
.GetColour(), 1, wx
.SOLID
) 
1018                 dc
.SetPen(solid_pen
) 
1022     def OnDrawControlPoints(self
, dc
): 
1023         if not self
._drawHandles
: 
1026         # Draw temporary label rectangles if necessary 
1028             if self
._labelObjects
[i
]: 
1029                 self
._labelObjects
[i
].Draw(dc
) 
1031         Shape
.OnDrawControlPoints(self
, dc
) 
1033     def OnEraseControlPoints(self
, dc
): 
1034         # Erase temporary label rectangles if necessary 
1037             if self
._labelObjects
[i
]: 
1038                 self
._labelObjects
[i
].Erase(dc
) 
1040         Shape
.OnEraseControlPoints(self
, dc
) 
1042     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1045     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1048     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1051     def OnDrawContents(self
, dc
): 
1052         if self
.GetDisableLabel(): 
1056             if self
._regions
[i
]: 
1057                 x
, y 
= self
.GetLabelPosition(i
) 
1058                 self
.DrawRegion(dc
, self
._regions
[i
], x
, y
) 
1060     def SetTo(self
, object): 
1061         """Set the 'to' object for the line.""" 
1064     def SetFrom(self
, object): 
1065         """Set the 'from' object for the line.""" 
1068     def MakeControlPoints(self
): 
1069         """Make handle control points.""" 
1070         if self
._canvas 
and self
._lineControlPoints
: 
1071             first 
= self
._lineControlPoints
[0] 
1072             last 
= self
._lineControlPoints
[-1] 
1074             control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, first
[0], first
[1], CONTROL_POINT_ENDPOINT_FROM
) 
1075             control
._point 
= first
 
1076             self
._canvas
.AddShape(control
) 
1077             self
._controlPoints
.append(control
) 
1079             for point 
in self
._lineControlPoints
[1:-1]: 
1080                 control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
[0], point
[1], CONTROL_POINT_LINE
) 
1081                 control
._point 
= point
 
1082                 self
._canvas
.AddShape(control
) 
1083                 self
._controlPoints
.append(control
) 
1085             control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, last
[0], last
[1], CONTROL_POINT_ENDPOINT_TO
) 
1086             control
._point 
= last
 
1087             self
._canvas
.AddShape(control
) 
1088             self
._controlPoints
.append(control
) 
1090     def ResetControlPoints(self
): 
1091         if self
._canvas 
and self
._lineControlPoints
: 
1092             for i 
in range(min(len(self
._controlPoints
), len(self
._lineControlPoints
))): 
1093                 point 
= self
._lineControlPoints
[i
] 
1094                 control 
= self
._controlPoints
[i
] 
1095                 control
.SetX(point
[0]) 
1096                 control
.SetY(point
[1]) 
1098     # Override select, to create / delete temporary label-moving objects 
1099     def Select(self
, select
, dc 
= None): 
1100         Shape
.Select(self
, select
, dc
) 
1103                 if self
._regions
[i
]: 
1104                     region 
= self
._regions
[i
] 
1105                     if region
._formattedText
: 
1106                         w
, h 
= region
.GetSize() 
1107                         x
, y 
= region
.GetPosition() 
1108                         xx
, yy 
= self
.GetLabelPosition(i
) 
1110                         if self
._labelObjects
[i
]: 
1111                             self
._labelObjects
[i
].Select(False) 
1112                             self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
1114                         self
._labelObjects
[i
] = self
.OnCreateLabelShape(self
, region
, w
, h
) 
1115                         self
._labelObjects
[i
].AddToCanvas(self
._canvas
) 
1116                         self
._labelObjects
[i
].Show(True) 
1118                             self
._labelObjects
[i
].Move(dc
, x 
+ xx
, y 
+ yy
) 
1119                         self
._labelObjects
[i
].Select(True, dc
) 
1122                 if self
._labelObjects
[i
]: 
1123                     self
._labelObjects
[i
].Select(False, dc
) 
1124                     self
._labelObjects
[i
].Erase(dc
) 
1125                     self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
1126                     self
._labelObjects
[i
] = None 
1128     # Control points ('handles') redirect control to the actual shape, to 
1129     # make it easier to override sizing behaviour. 
1130     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1131         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1132         self
.GetCanvas().PrepareDC(dc
) 
1134         dc
.SetLogicalFunction(OGLRBLF
) 
1136         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1137         dc
.SetPen(dottedPen
) 
1138         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1140         if pt
._type 
== CONTROL_POINT_LINE
: 
1141             x
, y 
= self
._canvas
.Snap(x
, y
) 
1147             old_pen 
= self
.GetPen() 
1148             old_brush 
= self
.GetBrush() 
1150             self
.SetPen(dottedPen
) 
1151             self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1153             self
.GetEventHandler().OnMoveLink(dc
, False) 
1155             self
.SetPen(old_pen
) 
1156             self
.SetBrush(old_brush
) 
1158     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
1159         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1160         self
.GetCanvas().PrepareDC(dc
) 
1162         if pt
._type 
== CONTROL_POINT_LINE
: 
1163             pt
._originalPos 
= pt
._point
 
1164             x
, y 
= self
._canvas
.Snap(x
, y
) 
1168             # Redraw start and end objects because we've left holes 
1169             # when erasing the line 
1170             self
.GetFrom().OnDraw(dc
) 
1171             self
.GetFrom().OnDrawContents(dc
) 
1172             self
.GetTo().OnDraw(dc
) 
1173             self
.GetTo().OnDrawContents(dc
) 
1175             self
.SetDisableLabel(True) 
1176             dc
.SetLogicalFunction(OGLRBLF
) 
1182             old_pen 
= self
.GetPen() 
1183             old_brush 
= self
.GetBrush() 
1185             dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1186             self
.SetPen(dottedPen
) 
1187             self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1189             self
.GetEventHandler().OnMoveLink(dc
, False) 
1191             self
.SetPen(old_pen
) 
1192             self
.SetBrush(old_brush
) 
1194         if pt
._type 
== CONTROL_POINT_ENDPOINT_FROM 
or pt
._type 
== CONTROL_POINT_ENDPOINT_TO
: 
1195             self
._canvas
.SetCursor(wx
.StockCursor(wx
.CURSOR_BULLSEYE
)) 
1196             pt
._oldCursor 
= wx
.STANDARD_CURSOR
 
1198     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
1199         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1200         self
.GetCanvas().PrepareDC(dc
) 
1202         self
.SetDisableLabel(False) 
1204         if pt
._type 
== CONTROL_POINT_LINE
: 
1205             x
, y 
= self
._canvas
.Snap(x
, y
) 
1207             rpt 
= wx
.RealPoint(x
, y
) 
1209             # Move the control point back to where it was; 
1210             # MoveControlPoint will move it to the new position 
1211             # if it decides it wants. We only moved the position 
1212             # during user feedback so we could redraw the line 
1213             # as it changed shape. 
1214             pt
._xpos 
= pt
._originalPos
[0] 
1215             pt
._ypos 
= pt
._originalPos
[1] 
1216             pt
._point 
= pt
._originalPos
[0], pt
._originalPos
[1] 
1218             self
.OnMoveMiddleControlPoint(dc
, pt
, rpt
) 
1220         if pt
._type 
== CONTROL_POINT_ENDPOINT_FROM
: 
1222                 self
._canvas
.SetCursor(pt
._oldCursor
) 
1225                     self
.GetFrom().MoveLineToNewAttachment(dc
, self
, x
, y
) 
1227         if pt
._type 
== CONTROL_POINT_ENDPOINT_TO
: 
1229                 self
._canvas
.SetCursor(pt
._oldCursor
) 
1232                     self
.GetTo().MoveLineToNewAttachment(dc
, self
, x
, y
) 
1234     # This is called only when a non-end control point is moved 
1235     def OnMoveMiddleControlPoint(self
, dc
, lpt
, pt
): 
1239         lpt
._point 
= pt
[0], pt
[1] 
1241         self
.GetEventHandler().OnMoveLink(dc
) 
1245     def AddArrow(self
, type, end 
= ARROW_POSITION_END
, size 
= 10.0, xOffset 
= 0.0, name 
= "", mf 
= None, arrowId 
= -1): 
1246         """Add an arrow (or annotation) to the line. 
1248         type may currently be one of: 
1255           Conventional arrowhead.  
1256         ARROW_SINGLE_OBLIQUE 
1257           Single oblique stroke.  
1258         ARROW_DOUBLE_OBLIQUE 
1259           Double oblique stroke.  
1260         ARROW_DOUBLE_METAFILE 
1263         end may currently be one of: 
1266           Arrow appears at the end.  
1267         ARROW_POSITION_START 
1268           Arrow appears at the start.  
1270         arrowSize specifies the length of the arrow. 
1272         xOffset specifies the offset from the end of the line. 
1274         name specifies a name for the arrow. 
1276         mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows 
1279         arrowId is the id for the arrow. 
1281         arrow 
= ArrowHead(type, end
, size
, xOffset
, name
, mf
, arrowId
) 
1282         self
._arcArrows
.append(arrow
) 
1285     # Add arrowhead at a particular position in the arrowhead list 
1286     def AddArrowOrdered(self
, arrow
, referenceList
, end
): 
1287         """Add an arrowhead in the position indicated by the reference list 
1288         of arrowheads, which contains all legal arrowheads for this line, in 
1289         the correct order. E.g. 
1291         Reference list:      a b c d e 
1292         Current line list:   a d 
1294         Add c, then line list is: a c d. 
1296         If no legal arrowhead position, return FALSE. Assume reference list 
1297         is for one end only, since it potentially defines the ordering for 
1298         any one of the 3 positions. So we don't check the reference list for 
1301         if not referenceList
: 
1304         targetName 
= arrow
.GetName() 
1306         # First check whether we need to insert in front of list, 
1307         # because this arrowhead is the first in the reference 
1308         # list and should therefore be first in the current list. 
1309         refArrow 
= referenceList
[0] 
1310         if refArrow
.GetName() == targetName
: 
1311             self
._arcArrows
.insert(0, arrow
) 
1315         while i1 
< len(referenceList
) and i2 
< len(self
._arcArrows
): 
1316             refArrow 
= referenceList
[i1
] 
1317             currArrow 
= self
._arcArrows
[i2
] 
1319             # Matching: advance current arrow pointer 
1320             if currArrow
.GetArrowEnd() == end 
and currArrow
.GetName() == refArrow
.GetName(): 
1323             # Check if we're at the correct position in the 
1325             if targetName 
== refArrow
.GetName(): 
1326                 if i2 
< len(self
._arcArrows
): 
1327                     self
._arcArrows
.insert(i2
, arrow
) 
1329                     self
._arcArrows
.append(arrow
) 
1333         self
._arcArrows
.append(arrow
) 
1336     def ClearArrowsAtPosition(self
, end
): 
1337         """Delete the arrows at the specified position, or at any position 
1341             self
._arcArrows 
= [] 
1344         for arrow 
in self
._arcArrows
: 
1345             if arrow
.GetArrowEnd() == end
: 
1346                 self
._arcArrows
.remove(arrow
) 
1348     def ClearArrow(self
, name
): 
1349         """Delete the arrow with the given name.""" 
1350         for arrow 
in self
._arcArrows
: 
1351             if arrow
.GetName() == name
: 
1352                 self
._arcArrows
.remove(arrow
) 
1356     def FindArrowHead(self
, position
, name
): 
1357         """Find arrowhead by position and name. 
1359         if position is -1, matches any position. 
1361         for arrow 
in self
._arcArrows
: 
1362             if (position 
== -1 or position 
== arrow
.GetArrowEnd()) and arrow
.GetName() == name
: 
1367     def FindArrowHeadId(self
, arrowId
): 
1368         """Find arrowhead by id.""" 
1369         for arrow 
in self
._arcArrows
: 
1370             if arrowId 
== arrow
.GetId(): 
1375     def DeleteArrowHead(self
, position
, name
): 
1376         """Delete arrowhead by position and name. 
1378         if position is -1, matches any position. 
1380         for arrow 
in self
._arcArrows
: 
1381             if (position 
== -1 or position 
== arrow
.GetArrowEnd()) and arrow
.GetName() == name
: 
1382                 self
._arcArrows
.remove(arrow
) 
1386     def DeleteArrowHeadId(self
, id): 
1387         """Delete arrowhead by id.""" 
1388         for arrow 
in self
._arcArrows
: 
1389             if arrowId 
== arrow
.GetId(): 
1390                 self
._arcArrows
.remove(arrow
) 
1394     # Calculate the minimum width a line 
1395     # occupies, for the purposes of drawing lines in tools. 
1396     def FindMinimumWidth(self
): 
1397         """Find the horizontal width for drawing a line with arrows in 
1398         minimum space. Assume arrows at end only. 
1401         for arrowHead 
in self
._arcArrows
: 
1402             minWidth 
+= arrowHead
.GetSize() 
1403             if arrowHead 
!= self
._arcArrows
[-1]: 
1404                 minWidth 
+= arrowHead 
+ GetSpacing
 
1406         # We have ABSOLUTE minimum now. So 
1407         # scale it to give it reasonable aesthetics 
1408         # when drawing with line. 
1410             minWidth 
= minWidth 
* 1.4 
1414         self
.SetEnds(0.0, 0.0, minWidth
, 0.0) 
1419     def FindLinePosition(self
, x
, y
): 
1420         """Find which position we're talking about at this x, y. 
1422         Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END. 
1424         startX
, startY
, endX
, endY 
= self
.GetEnds() 
1426         # Find distances from centre, start and end. The smallest wins 
1427         centreDistance 
= math
.sqrt((x 
- self
._xpos
) * (x 
- self
._xpos
) + (y 
- self
._ypos
) * (y 
- self
._ypos
)) 
1428         startDistance 
= math
.sqrt((x 
- startX
) * (x 
- startX
) + (y 
- startY
) * (y 
- startY
)) 
1429         endDistance 
= math
.sqrt((x 
- endX
) * (x 
- endX
) + (y 
- endY
) * (y 
- endY
)) 
1431         if centreDistance 
< startDistance 
and centreDistance 
< endDistance
: 
1432             return ARROW_POSITION_MIDDLE
 
1433         elif startDistance 
< endDistance
: 
1434             return ARROW_POSITION_START
 
1436             return ARROW_POSITION_END
 
1438     def SetAlignmentOrientation(self
, isEnd
, isHoriz
): 
1440             if isHoriz 
and self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
!= LINE_ALIGNMENT_HORIZ
: 
1441                 self
._alignmentEnd 
!= LINE_ALIGNMENT_HORIZ
 
1442             elif not isHoriz 
and self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
: 
1443                 self
._alignmentEnd 
-= LINE_ALIGNMENT_HORIZ
 
1445             if isHoriz 
and self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
!= LINE_ALIGNMENT_HORIZ
: 
1446                 self
._alignmentStart 
!= LINE_ALIGNMENT_HORIZ
 
1447             elif not isHoriz 
and self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
: 
1448                 self
._alignmentStart 
-= LINE_ALIGNMENT_HORIZ
 
1450     def SetAlignmentType(self
, isEnd
, alignType
): 
1452             if alignType 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1453                 if self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
!= LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1454                     self
._alignmentEnd |
= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1455             elif self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1456                 self
._alignmentEnd 
-= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1458             if alignType 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1459                 if self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
!= LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1460                     self
._alignmentStart |
= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1461             elif self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1462                 self
._alignmentStart 
-= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1464     def GetAlignmentOrientation(self
, isEnd
): 
1466             return self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
 
1468             return self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
 
1470     def GetAlignmentType(self
, isEnd
): 
1472             return self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1474             return self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1476     def GetNextControlPoint(self
, shape
): 
1477         """Find the next control point in the line after the start / end point, 
1478         depending on whether the shape is at the start or end. 
1480         n 
= len(self
._lineControlPoints
) 
1481         if self
._to 
== shape
: 
1482             # Must be END of line, so we want (n - 1)th control point. 
1483             # But indexing ends at n-1, so subtract 2. 
1487         if nn 
< len(self
._lineControlPoints
): 
1488             return self
._lineControlPoints
[nn
] 
1491     def OnCreateLabelShape(self
, parent
, region
, w
, h
): 
1492         return LabelShape(parent
, region
, w
, h
) 
1495     def OnLabelMovePre(self
, dc
, labelShape
, x
, y
, old_x
, old_y
, display
): 
1496         labelShape
._shapeRegion
.SetSize(labelShape
.GetWidth(), labelShape
.GetHeight()) 
1498         # Find position in line's region list 
1500         for region 
in self
.GetRegions(): 
1501             if labelShape
._shapeRegion 
== region
: 
1502                 self
.GetRegions().remove(region
) 
1506         xx
, yy 
= self
.GetLabelPosition(i
) 
1507         # Set the region's offset, relative to the default position for 
1509         labelShape
._shapeRegion
.SetPosition(x 
- xx
, y 
- yy
) 
1513         # Need to reformat to fit region 
1514         if labelShape
._shapeRegion
.GetText(): 
1515             s 
= labelShape
._shapeRegion
.GetText() 
1516             labelShape
.FormatText(dc
, s
, i
) 
1517             self
.DrawRegion(dc
, labelShape
._shapeRegion
, xx
, yy
)