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
._lineControlPoints 
= [] 
 225             if self
._labelObjects
[i
]: 
 226                 self
._labelObjects
[i
].Select(False) 
 227                 self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
 228         self
._labelObjects 
= [] 
 229         self
.ClearArrowsAtPosition(-1) 
 232         """Return the 'from' object.""" 
 236         """Return the 'to' object.""" 
 239     def GetAttachmentFrom(self
): 
 240         """Return the attachment point on the 'from' node.""" 
 241         return self
._attachmentFrom
 
 243     def GetAttachmentTo(self
): 
 244         """Return the attachment point on the 'to' node.""" 
 245         return self
._attachmentTo
 
 247     def GetLineControlPoints(self
): 
 248         return self
._lineControlPoints
 
 250     def SetSpline(self
, spline
): 
 251         """Specifies whether a spline is to be drawn through the control points.""" 
 252         self
._isSpline 
= spline
 
 255         """TRUE if a spline is drawn through the control points.""" 
 256         return self
._isSpline
 
 258     def SetAttachmentFrom(self
, attach
): 
 259         """Set the 'from' shape attachment.""" 
 260         self
._attachmentFrom 
= attach
 
 262     def SetAttachmentTo(self
, attach
): 
 263         """Set the 'to' shape attachment.""" 
 264         self
._attachmentTo 
= attach
 
 266     # This is really to distinguish between lines and other images. 
 267     # For lines, want to pass drag to canvas, since lines tend to prevent 
 268     # dragging on a canvas (they get in the way.) 
 272     def SetIgnoreOffsets(self
, ignore
): 
 273         """Set whether to ignore offsets from the end of the line when drawing.""" 
 274         self
._ignoreArrowOffsets 
= ignore
 
 277         return self
._arcArrows
 
 279     def GetAlignmentStart(self
): 
 280         return self
._alignmentStart
 
 282     def GetAlignmentEnd(self
): 
 283         return self
._alignmentEnd
 
 285     def IsEnd(self
, nodeObject
): 
 286         """TRUE if shape is at the end of the line.""" 
 287         return self
._to 
== nodeObject
 
 289     def MakeLineControlPoints(self
, n
): 
 290         """Make a given number of control points (minimum of two).""" 
 291         self
._lineControlPoints 
= [] 
 294             point 
= wx
.RealPoint(-999, -999) 
 295             self
._lineControlPoints
.append(point
) 
 297         # pi: added _initialised to keep track of when we have set 
 298         # the middle points to something other than (-999, -999) 
 299         self
._initialised 
= False 
 301     def InsertLineControlPoint(self
, dc 
= None): 
 302         """Insert a control point at an arbitrary position.""" 
 306         last_point 
= self
._lineControlPoints
[-1] 
 307         second_last_point 
= self
._lineControlPoints
[-2] 
 309         line_x 
= (last_point
[0] + second_last_point
[0]) / 2.0 
 310         line_y 
= (last_point
[1] + second_last_point
[1]) / 2.0 
 312         point 
= wx
.RealPoint(line_x
, line_y
) 
 313         self
._lineControlPoints
.insert(len(self
._lineControlPoints
), point
) 
 315     def DeleteLineControlPoint(self
): 
 316         """Delete an arbitary point on the line.""" 
 317         if len(self
._lineControlPoints
) < 3: 
 320         del self
._lineControlPoints
[-2] 
 323     def Initialise(self
): 
 324         """Initialise the line object.""" 
 325         if self
._lineControlPoints
: 
 326             # Just move the first and last control points 
 327             first_point 
= self
._lineControlPoints
[0] 
 328             last_point 
= self
._lineControlPoints
[-1] 
 330             # If any of the line points are at -999, we must 
 331             # initialize them by placing them half way between the first 
 334             for i 
in range(1,len(self
._lineControlPoints
)): 
 335                 point 
= self
._lineControlPoints
[i
] 
 337                     if first_point
[0] < last_point
[0]: 
 343                     if first_point
[1] < last_point
[1]: 
 349                     self
._lineControlPoints
[i
] = wx
.RealPoint((x2 
- x1
) / 2.0 + x1
, (y2 
- y1
) / 2.0 + y1
) 
 350                     self
._initialised 
= True 
 352     def FormatText(self
, dc
, s
, i
): 
 353         """Format a text string according to the region size, adding 
 354         strings with positions to region text list. 
 358         if len(self
._regions
) == 0 or i 
>= len(self
._regions
): 
 361         region 
= self
._regions
[i
] 
 363         dc
.SetFont(region
.GetFont()) 
 365         w
, h 
= region
.GetSize() 
 366         # Initialize the size if zero 
 367         if (w 
== 0 or h 
== 0) and s
: 
 371         string_list 
= FormatText(dc
, s
, w 
- 5, h 
- 5, region
.GetFormatMode()) 
 372         for s 
in string_list
: 
 373             line 
= ShapeTextLine(0.0, 0.0, s
) 
 374             region
.GetFormattedText().append(line
) 
 378         if region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
: 
 379             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w
, h
) 
 380             if actualW 
!= w 
or actualH 
!= h
: 
 381                 xx
, yy 
= self
.GetLabelPosition(i
) 
 382                 self
.EraseRegion(dc
, region
, xx
, yy
) 
 383                 if len(self
._labelObjects
) < i
: 
 384                     self
._labelObjects
[i
].Select(False, dc
) 
 385                     self
._labelObjects
[i
].Erase(dc
) 
 386                     self
._labelObjects
[i
].SetSize(actualW
, actualH
) 
 388                 region
.SetSize(actualW
, actualH
) 
 390                 if len(self
._labelObjects
) < i
: 
 391                     self
._labelObjects
[i
].Select(True, dc
) 
 392                     self
._labelObjects
[i
].Draw(dc
) 
 394         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW
, actualH
, region
.GetFormatMode()) 
 395         self
._formatted 
= True 
 397     def DrawRegion(self
, dc
, region
, x
, y
): 
 398         """Format one region at this position.""" 
 399         if self
.GetDisableLabel(): 
 402         w
, h 
= region
.GetSize() 
 404         # Get offset from x, y 
 405         xx
, yy 
= region
.GetPosition() 
 410         # First, clear a rectangle for the text IF there is any 
 411         if len(region
.GetFormattedText()): 
 412             dc
.SetPen(self
.GetBackgroundPen()) 
 413             dc
.SetBrush(self
.GetBackgroundBrush()) 
 417                 dc
.SetFont(region
.GetFont()) 
 418                 dc
.DrawRectangle(xp 
- w 
/ 2.0, yp 
- h 
/ 2.0, w
, h
) 
 422                 dc
.SetTextForeground(region
.GetActualColourObject()) 
 424                 DrawFormattedText(dc
, region
.GetFormattedText(), xp
, yp
, w
, h
, region
.GetFormatMode()) 
 426     def EraseRegion(self
, dc
, region
, x
, y
): 
 427         """Erase one region at this position.""" 
 428         if self
.GetDisableLabel(): 
 431         w
, h 
= region
.GetSize() 
 433         # Get offset from x, y 
 434         xx
, yy 
= region
.GetPosition() 
 439         if region
.GetFormattedText(): 
 440             dc
.SetPen(self
.GetBackgroundPen()) 
 441             dc
.SetBrush(self
.GetBackgroundBrush()) 
 443             dc
.DrawRectangle(xp 
- w 
/ 2.0, yp 
- h 
/ 2.0, w
, h
) 
 445     def GetLabelPosition(self
, position
): 
 446         """Get the reference point for a label. 
 448         Region x and y are offsets from this. 
 449         position is 0 (middle), 1 (start), 2 (end). 
 452             # Want to take the middle section for the label 
 453             half_way 
= int(len(self
._lineControlPoints
) / 2.0) 
 455             # Find middle of this line 
 456             point 
= self
._lineControlPoints
[half_way 
- 1] 
 457             next_point 
= self
._lineControlPoints
[half_way
] 
 459             dx 
= next_point
[0] - point
[0] 
 460             dy 
= next_point
[1] - point
[1] 
 462             return point
[0] + dx 
/ 2.0, point
[1] + dy 
/ 2.0 
 464             return self
._lineControlPoints
[0][0], self
._lineControlPoints
[0][1] 
 466             return self
._lineControlPoints
[-1][0], self
._lineControlPoints
[-1][1] 
 468     def Straighten(self
, dc 
= None): 
 469         """Straighten verticals and horizontals.""" 
 470         if len(self
._lineControlPoints
) < 3: 
 476         GraphicsStraightenLine(self
._lineControlPoints
[-1], self
._lineControlPoints
[-2]) 
 478         for i 
in range(len(self
._lineControlPoints
) - 2): 
 479             GraphicsStraightenLine(self
._lineControlPoints
[i
], self
._lineControlPoints
[i 
+ 1]) 
 485         """Unlink the line from the nodes at either end.""" 
 487             self
._to
.GetLines().remove(self
) 
 489             self
._from
.GetLines().remove(self
) 
 493     def SetEnds(self
, x1
, y1
, x2
, y2
): 
 494         """Set the end positions of the line.""" 
 495         self
._lineControlPoints
[0] = wx
.RealPoint(x1
, y1
) 
 496         self
._lineControlPoints
[-1] = wx
.RealPoint(x2
, y2
) 
 499         self
._xpos 
= (x1 
+ x2
) / 2.0 
 500         self
._ypos 
= (y1 
+ y2
) / 2.0 
 502     # Get absolute positions of ends 
 504         """Get the visible endpoints of the lines for drawing between two objects.""" 
 505         first_point 
= self
._lineControlPoints
[0] 
 506         last_point 
= self
._lineControlPoints
[-1] 
 508         return first_point
[0], first_point
[1], last_point
[0], last_point
[1] 
 510     def SetAttachments(self
, from_attach
, to_attach
): 
 511         """Specify which object attachment points should be used at each end 
 514         self
._attachmentFrom 
= from_attach
 
 515         self
._attachmentTo 
= to_attach
 
 517     def HitTest(self
, x
, y
): 
 518         if not self
._lineControlPoints
: 
 521         # Look at label regions in case mouse is over a label 
 522         inLabelRegion 
= False 
 525                 region 
= self
._regions
[i
] 
 526                 if len(region
._formattedText
): 
 527                     xp
, yp 
= self
.GetLabelPosition(i
) 
 528                     # Offset region from default label position 
 529                     cx
, cy 
= region
.GetPosition() 
 530                     cw
, ch 
= region
.GetSize()  
 534                     rLeft 
= cx 
- cw 
/ 2.0 
 536                     rRight 
= cx 
+ cw 
/ 2.0 
 537                     rBottom 
= cy 
+ ch 
/ 2.0 
 538                     if x 
> rLeft 
and x 
< rRight 
and y 
> rTop 
and y 
< rBottom
: 
 542         for i 
in range(len(self
._lineControlPoints
) - 1): 
 543             point1 
= self
._lineControlPoints
[i
] 
 544             point2 
= self
._lineControlPoints
[i 
+ 1] 
 546             # For inaccurate mousing allow 8 pixel corridor 
 549             dx 
= point2
[0] - point1
[0] 
 550             dy 
= point2
[1] - point1
[1] 
 552             seg_len 
= math
.sqrt(dx 
* dx 
+ dy 
* dy
) 
 553             if dy 
== 0 and dx 
== 0: 
 555             distance_from_seg 
= seg_len 
* float((x 
- point1
[0]) * dy 
- (y 
- point1
[1]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 556             distance_from_prev 
= seg_len 
* float((y 
- point1
[1]) * dy 
+ (x 
- point1
[0]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 558             if abs(distance_from_seg
) < extra 
and distance_from_prev 
>= 0 and distance_from_prev 
<= seg_len 
or inLabelRegion
: 
 559                 return 0, distance_from_seg
 
 563     def DrawArrows(self
, dc
): 
 564         """Draw all arrows.""" 
 565         # Distance along line of each arrow: space them out evenly 
 570         for arrow 
in self
._arcArrows
: 
 571             ah 
= arrow
.GetArrowEnd() 
 572             if ah 
== ARROW_POSITION_START
: 
 573                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 574                     # If specified, x offset is proportional to line length 
 575                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 577                     self
.DrawArrow(dc
, arrow
, startArrowPos
, False) 
 578                     startArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 579             elif ah 
== ARROW_POSITION_END
: 
 580                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 581                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 583                     self
.DrawArrow(dc
, arrow
, endArrowPos
, False) 
 584                     endArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 585             elif ah 
== ARROW_POSITION_MIDDLE
: 
 586                 arrow
.SetXOffset(middleArrowPos
) 
 587                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 588                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 590                     self
.DrawArrow(dc
, arrow
, middleArrowPos
, False) 
 591                     middleArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 593     def DrawArrow(self
, dc
, arrow
, XOffset
, proportionalOffset
): 
 594         """Draw the given arrowhead (or annotation).""" 
 595         first_line_point 
= self
._lineControlPoints
[0] 
 596         second_line_point 
= self
._lineControlPoints
[1] 
 598         last_line_point 
= self
._lineControlPoints
[-1] 
 599         second_last_line_point 
= self
._lineControlPoints
[-2] 
 601         # Position of start point of line, at the end of which we draw the arrow 
 602         startPositionX
, startPositionY 
= 0.0, 0.0 
 604         ap 
= arrow
.GetPosition() 
 605         if ap 
== ARROW_POSITION_START
: 
 606             # If we're using a proportional offset, calculate just where this 
 607             # will be on the line. 
 609             if proportionalOffset
: 
 610                 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])) 
 611                 realOffset 
= XOffset 
* totalLength
 
 613             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_line_point
[0], second_line_point
[1], first_line_point
[0], first_line_point
[1], realOffset
) 
 615             startPositionX 
= second_line_point
[0] 
 616             startPositionY 
= second_line_point
[1] 
 617         elif ap 
== ARROW_POSITION_END
: 
 618             # If we're using a proportional offset, calculate just where this 
 619             # will be on the line. 
 621             if proportionalOffset
: 
 622                 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])); 
 623                 realOffset 
= XOffset 
* totalLength
 
 625             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], last_line_point
[0], last_line_point
[1], realOffset
) 
 627             startPositionX 
= second_last_line_point
[0] 
 628             startPositionY 
= second_last_line_point
[1] 
 629         elif ap 
== ARROW_POSITION_MIDDLE
: 
 630             # Choose a point half way between the last and penultimate points 
 631             x 
= (last_line_point
[0] + second_last_line_point
[0]) / 2.0 
 632             y 
= (last_line_point
[1] + second_last_line_point
[1]) / 2.0 
 634             # If we're using a proportional offset, calculate just where this 
 635             # will be on the line. 
 637             if proportionalOffset
: 
 638                 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
)); 
 639                 realOffset 
= XOffset 
* totalLength
 
 641             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], x
, y
, realOffset
) 
 642             startPositionX 
= second_last_line_point
[0] 
 643             startPositionY 
= second_last_line_point
[1] 
 645         # Add yOffset to arrow, if any 
 647         # The translation that the y offset may give 
 650         if arrow
.GetYOffset 
and not self
._ignoreArrowOffsets
: 
 654             #   (x1, y1)--------------(x3, y3)------------------(x2, y2) 
 655             #   x4 = x3 - d * math.sin(theta) 
 656             #   y4 = y3 + d * math.cos(theta) 
 658             #   Where theta = math.tan(-1) of (y3-y1) / (x3-x1) 
 661             x3 
= float(positionOnLineX
) 
 662             y3 
= float(positionOnLineY
) 
 663             d 
= -arrow
.GetYOffset() # Negate so +offset is above line 
 666                 theta 
= math
.pi 
/ 2.0 
 668                 theta 
= math
.atan((y3 
- y1
) / (x3 
- x1
)) 
 670             x4 
= x3 
- d 
* math
.sin(theta
) 
 671             y4 
= y3 
+ d 
* math
.cos(theta
) 
 673             deltaX 
= x4 
- positionOnLineX
 
 674             deltaY 
= y4 
- positionOnLineY
 
 676         at 
= arrow
._GetType
() 
 677         if at 
== ARROW_ARROW
: 
 678             arrowLength 
= arrow
.GetSize() 
 679             arrowWidth 
= arrowLength 
/ 3.0 
 681             tip_x
, tip_y
, side1_x
, side1_y
, side2_x
, side2_y 
= GetArrowPoints(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, arrowLength
, arrowWidth
) 
 683             points 
= [[tip_x
, tip_y
], 
 689             dc
.SetBrush(self
._brush
) 
 690             dc
.DrawPolygon(points
) 
 691         elif at 
in [ARROW_HOLLOW_CIRCLE
, ARROW_FILLED_CIRCLE
]: 
 692             # Find point on line of centre of circle, which is a radius away 
 693             # from the end position 
 694             diameter 
= arrow
.GetSize() 
 695             x
, y 
= GetPointOnLine(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, 
 696                                positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, 
 698             x1 
= x 
- diameter 
/ 2.0 
 699             y1 
= y 
- diameter 
/ 2.0 
 701             if arrow
._GetType
() == ARROW_HOLLOW_CIRCLE
: 
 702                 dc
.SetBrush(self
.GetBackgroundBrush()) 
 704                 dc
.SetBrush(self
._brush
) 
 706             dc
.DrawEllipse(x1
, y1
, diameter
, diameter
) 
 707         elif at 
== ARROW_SINGLE_OBLIQUE
: 
 709         elif at 
== ARROW_METAFILE
: 
 710             if arrow
.GetMetaFile(): 
 711                 # Find point on line of centre of object, which is a half-width away 
 712                 # from the end position 
 715                 #  <-- start pos  <-----><-- positionOnLineX 
 717                 #  --------------|  x  | <-- e.g. rectangular arrowhead 
 720                 x
, y 
= GetPointOnLine(startPositionX
, startPositionY
, 
 721                                    positionOnLineX
, positionOnLineY
, 
 722                                    arrow
.GetMetaFile()._width 
/ 2.0) 
 723                 # Calculate theta for rotating the metafile. 
 726                 # |     o(x2, y2)   'o' represents the arrowhead. 
 731                 # |______________________ 
 736                 x2 
= float(positionOnLineX
) 
 737                 y2 
= float(positionOnLineY
) 
 739                 if x1 
== x2 
and y1 
== y2
: 
 741                 elif x1 
== x2 
and y1 
> y2
: 
 742                     theta 
= 3.0 * math
.pi 
/ 2.0 
 743                 elif x1 
== x2 
and y2 
> y1
: 
 744                     theta 
= math
.pi 
/ 2.0 
 745                 elif x2 
> x1 
and y2 
>= y1
: 
 746                     theta 
= math
.atan((y2 
- y1
) / (x2 
- x1
)) 
 748                     theta 
= math
.pi 
+ math
.atan((y2 
- y1
) / (x2 
- x1
)) 
 749                 elif x2 
> x1 
and y2 
< y1
: 
 750                     theta 
= 2 * math
.pi 
+ math
.atan((y2 
- y1
) / (x2 
- x1
)) 
 752                     raise "Unknown arrowhead rotation case" 
 754                 # Rotate about the centre of the object, then place 
 755                 # the object on the line. 
 756                 if arrow
.GetMetaFile().GetRotateable(): 
 757                     arrow
.GetMetaFile().Rotate(0.0, 0.0, theta
) 
 760                     # If erasing, just draw a rectangle 
 761                     minX
, minY
, maxX
, maxY 
= arrow
.GetMetaFile().GetBounds() 
 762                     # Make erasing rectangle slightly bigger or you get droppings 
 764                     dc
.DrawRectangle(deltaX 
+ x 
+ minX 
- extraPixels 
/ 2.0, deltaY 
+ y 
+ minY 
- extraPixels 
/ 2.0, maxX 
- minX 
+ extraPixels
, maxY 
- minY 
+ extraPixels
) 
 766                     arrow
.GetMetaFile().Draw(dc
, x 
+ deltaX
, y 
+ deltaY
) 
 768     def OnErase(self
, dc
): 
 770         old_brush 
= self
._brush
 
 772         bg_pen 
= self
.GetBackgroundPen() 
 773         bg_brush 
= self
.GetBackgroundBrush() 
 775         self
.SetBrush(bg_brush
) 
 777         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
 779             dc
.SetFont(self
._font
) 
 781         # Undraw text regions 
 784                 x
, y 
= self
.GetLabelPosition(i
) 
 785                 self
.EraseRegion(dc
, self
._regions
[i
], x
, y
) 
 788         dc
.SetPen(self
.GetBackgroundPen()) 
 789         dc
.SetBrush(self
.GetBackgroundBrush()) 
 791         # Drawing over the line only seems to work if the line has a thickness 
 793         if old_pen 
and old_pen
.GetWidth() > 1: 
 794             dc
.DrawRectangle(self
._xpos 
- bound_x 
/ 2.0 - 2, self
._ypos 
- bound_y 
/ 2.0 - 2, 
 795                              bound_x 
+ 4, bound_y 
+ 4) 
 798             self
.GetEventHandler().OnDraw(dc
) 
 799             self
.GetEventHandler().OnEraseControlPoints(dc
) 
 800             self
._erasing 
= False 
 805             self
.SetBrush(old_brush
) 
 807     def GetBoundingBoxMin(self
): 
 808         x1
, y1 
= 10000, 10000 
 809         x2
, y2 
= -10000, -10000 
 811         for point 
in self
._lineControlPoints
: 
 821         return x2 
- x1
, y2 
- y1
 
 823     # For a node image of interest, finds the position of this arc 
 824     # amongst all the arcs which are attached to THIS SIDE of the node image, 
 825     # and the number of same. 
 826     def FindNth(self
, image
, incoming
): 
 827         """Find the position of the line on the given object. 
 829         Specify whether incoming or outgoing lines are being considered 
 835         if image 
== self
._to
: 
 836             this_attachment 
= self
._attachmentTo
 
 838             this_attachment 
= self
._attachmentFrom
 
 840         # Find number of lines going into / out of this particular attachment point 
 841         for line 
in image
.GetLines(): 
 842             if line
._from 
== image
: 
 843                 # This is the nth line attached to 'image' 
 844                 if line 
== self 
and not incoming
: 
 847                 # Increment num count if this is the same side (attachment number) 
 848                 if line
._attachmentFrom 
== this_attachment
: 
 851             if line
._to 
== image
: 
 852                 # This is the nth line attached to 'image' 
 853                 if line 
== self 
and incoming
: 
 856                 # Increment num count if this is the same side (attachment number) 
 857                 if line
._attachmentTo 
== this_attachment
: 
 862     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 864         old_brush 
= self
._brush
 
 866         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
 867         self
.SetPen(dottedPen
) 
 868         self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 870         self
.GetEventHandler().OnDraw(dc
) 
 877             self
.SetBrush(old_brush
) 
 881     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 885         if self
._lineControlPoints 
and not (x_offset 
== 0 and y_offset 
== 0): 
 886             for point 
in self
._lineControlPoints
: 
 890         # Move temporary label rectangles if necessary 
 892             if self
._labelObjects
[i
]: 
 893                 self
._labelObjects
[i
].Erase(dc
) 
 894                 xp
, yp 
= self
.GetLabelPosition(i
) 
 895                 if i 
< len(self
._regions
): 
 896                     xr
, yr 
= self
._regions
[i
].GetPosition() 
 899                 self
._labelObjects
[i
].Move(dc
, xp 
+ xr
, yp 
+ yr
) 
 902     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
 903         """Called when a connected object has moved, to move the link to 
 906         if not self
._from 
or not self
._to
: 
 909         # Do each end - nothing in the middle. User has to move other points 
 910         # manually if necessary 
 911         end_x
, end_y
, other_end_x
, other_end_y 
= self
.FindLineEndPoints() 
 913         oldX
, oldY 
= self
._xpos
, self
._ypos
 
 915         # pi: The first time we go through FindLineEndPoints we can't 
 916         # use the middle points (since they don't have sane values), 
 917         # so we just do what we do for a normal line. Then we call 
 918         # Initialise to set the middle points, and then FindLineEndPoints 
 919         # again, but this time (and from now on) we use the middle 
 920         # points to calculate the end points. 
 921         # This was buggy in the C++ version too. 
 923         self
.SetEnds(end_x
, end_y
, other_end_x
, other_end_y
) 
 925         if len(self
._lineControlPoints
) > 2: 
 928         # Do a second time, because one may depend on the other 
 929         end_x
, end_y
, other_end_x
, other_end_y 
= self
.FindLineEndPoints() 
 930         self
.SetEnds(end_x
, end_y
, other_end_x
, other_end_y
) 
 932         # Try to move control points with the arc 
 933         x_offset 
= self
._xpos 
- oldX
 
 934         y_offset 
= self
._ypos 
- oldY
 
 936         # Only move control points if it's a self link. And only works 
 937         # if attachment mode is ON 
 938         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): 
 939             for point 
in self
._lineControlPoints
[1:-1]: 
 943         self
.Move(dc
, self
._xpos
, self
._ypos
) 
 945     def FindLineEndPoints(self
): 
 946         """Finds the x, y points at the two ends of the line. 
 948         This function can be used by e.g. line-routing routines to 
 949         get the actual points on the two node images where the lines will be 
 952         if not self
._from 
or not self
._to
: 
 955         # Do each end - nothing in the middle. User has to move other points 
 956         # manually if necessary. 
 957         second_point 
= self
._lineControlPoints
[1] 
 958         second_last_point 
= self
._lineControlPoints
[-2] 
 960         # pi: If we have a segmented line and this is the first time, 
 961         # do this as a straight line. 
 962         if len(self
._lineControlPoints
) > 2 and self
._initialised
: 
 963             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 964                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) # Not incoming 
 965                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 967                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), second_point
[0], second_point
[1]) 
 969             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 970                 nth
, no_arch 
= self
.FindNth(self
._to
, True) # Incoming 
 971                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arch
, self
) 
 973                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), second_last_point
[0], second_last_point
[1]) 
 975             fromX 
= self
._from
.GetX() 
 976             fromY 
= self
._from
.GetY() 
 977             toX 
= self
._to
.GetX() 
 978             toY 
= self
._to
.GetY() 
 980             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 981                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) 
 982                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 986             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 987                 nth
, no_arcs 
= self
.FindNth(self
._to
, True) 
 988                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arcs
, self
) 
 992             if self
._from
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 993                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), toX
, toY
) 
 995             if self
._to
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 996                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), fromX
, fromY
) 
 998         return end_x
, end_y
, other_end_x
, other_end_y
 
1001     def OnDraw(self
, dc
): 
1002         if not self
._lineControlPoints
: 
1006             dc
.SetPen(self
._pen
) 
1008             dc
.SetBrush(self
._brush
) 
1011         for point 
in self
._lineControlPoints
: 
1012             points
.append(wx
.Point(point
[0], point
[1])) 
1015             dc
.DrawSpline(points
) 
1017             dc
.DrawLines(points
) 
1019         if sys
.platform
[:3] == "win": 
1020             # For some reason, last point isn't drawn under Windows 
1022             dc
.DrawPoint(pt
[0], pt
[1]) 
1024         # Problem with pen - if not a solid pen, does strange things 
1025         # to the arrowhead. So make (get) a new pen that's solid. 
1026         if self
._pen 
and self
._pen
.GetStyle() != wx
.SOLID
: 
1027             solid_pen 
= wx
.ThePenList
.FindOrCreatePen(self
._pen
.GetColour(), 1, wx
.SOLID
) 
1029                 dc
.SetPen(solid_pen
) 
1033     def OnDrawControlPoints(self
, dc
): 
1034         if not self
._drawHandles
: 
1037         # Draw temporary label rectangles if necessary 
1039             if self
._labelObjects
[i
]: 
1040                 self
._labelObjects
[i
].Draw(dc
) 
1042         Shape
.OnDrawControlPoints(self
, dc
) 
1044     def OnEraseControlPoints(self
, dc
): 
1045         # Erase temporary label rectangles if necessary 
1048             if self
._labelObjects
[i
]: 
1049                 self
._labelObjects
[i
].Erase(dc
) 
1051         Shape
.OnEraseControlPoints(self
, dc
) 
1053     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1056     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1059     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1062     def OnDrawContents(self
, dc
): 
1063         if self
.GetDisableLabel(): 
1067             if self
._regions
[i
]: 
1068                 x
, y 
= self
.GetLabelPosition(i
) 
1069                 self
.DrawRegion(dc
, self
._regions
[i
], x
, y
) 
1071     def SetTo(self
, object): 
1072         """Set the 'to' object for the line.""" 
1075     def SetFrom(self
, object): 
1076         """Set the 'from' object for the line.""" 
1079     def MakeControlPoints(self
): 
1080         """Make handle control points.""" 
1081         if self
._canvas 
and self
._lineControlPoints
: 
1082             first 
= self
._lineControlPoints
[0] 
1083             last 
= self
._lineControlPoints
[-1] 
1085             control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, first
[0], first
[1], CONTROL_POINT_ENDPOINT_FROM
) 
1086             control
._point 
= first
 
1087             self
._canvas
.AddShape(control
) 
1088             self
._controlPoints
.append(control
) 
1090             for point 
in self
._lineControlPoints
[1:-1]: 
1091                 control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
[0], point
[1], CONTROL_POINT_LINE
) 
1092                 control
._point 
= point
 
1093                 self
._canvas
.AddShape(control
) 
1094                 self
._controlPoints
.append(control
) 
1096             control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, last
[0], last
[1], CONTROL_POINT_ENDPOINT_TO
) 
1097             control
._point 
= last
 
1098             self
._canvas
.AddShape(control
) 
1099             self
._controlPoints
.append(control
) 
1101     def ResetControlPoints(self
): 
1102         if self
._canvas 
and self
._lineControlPoints 
and self
._controlPoints
: 
1103             for i 
in range(min(len(self
._controlPoints
), len(self
._lineControlPoints
))): 
1104                 point 
= self
._lineControlPoints
[i
] 
1105                 control 
= self
._controlPoints
[i
] 
1106                 control
.SetX(point
[0]) 
1107                 control
.SetY(point
[1]) 
1109     # Override select, to create / delete temporary label-moving objects 
1110     def Select(self
, select
, dc 
= None): 
1111         Shape
.Select(self
, select
, dc
) 
1114                 if self
._regions
[i
]: 
1115                     region 
= self
._regions
[i
] 
1116                     if region
._formattedText
: 
1117                         w
, h 
= region
.GetSize() 
1118                         x
, y 
= region
.GetPosition() 
1119                         xx
, yy 
= self
.GetLabelPosition(i
) 
1121                         if self
._labelObjects
[i
]: 
1122                             self
._labelObjects
[i
].Select(False) 
1123                             self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
1125                         self
._labelObjects
[i
] = self
.OnCreateLabelShape(self
, region
, w
, h
) 
1126                         self
._labelObjects
[i
].AddToCanvas(self
._canvas
) 
1127                         self
._labelObjects
[i
].Show(True) 
1129                             self
._labelObjects
[i
].Move(dc
, x 
+ xx
, y 
+ yy
) 
1130                         self
._labelObjects
[i
].Select(True, dc
) 
1133                 if self
._labelObjects
[i
]: 
1134                     self
._labelObjects
[i
].Select(False, dc
) 
1135                     self
._labelObjects
[i
].Erase(dc
) 
1136                     self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
1137                     self
._labelObjects
[i
] = None 
1139     # Control points ('handles') redirect control to the actual shape, to 
1140     # make it easier to override sizing behaviour. 
1141     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1142         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1143         self
.GetCanvas().PrepareDC(dc
) 
1145         dc
.SetLogicalFunction(OGLRBLF
) 
1147         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1148         dc
.SetPen(dottedPen
) 
1149         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1151         if pt
._type 
== CONTROL_POINT_LINE
: 
1152             x
, y 
= self
._canvas
.Snap(x
, y
) 
1159             old_pen 
= self
.GetPen() 
1160             old_brush 
= self
.GetBrush() 
1162             self
.SetPen(dottedPen
) 
1163             self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1165             self
.GetEventHandler().OnMoveLink(dc
, False) 
1167             self
.SetPen(old_pen
) 
1168             self
.SetBrush(old_brush
) 
1170     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
1171         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1172         self
.GetCanvas().PrepareDC(dc
) 
1174         if pt
._type 
== CONTROL_POINT_LINE
: 
1175             pt
._originalPos 
= pt
._point
 
1176             x
, y 
= self
._canvas
.Snap(x
, y
) 
1180             # Redraw start and end objects because we've left holes 
1181             # when erasing the line 
1182             self
.GetFrom().OnDraw(dc
) 
1183             self
.GetFrom().OnDrawContents(dc
) 
1184             self
.GetTo().OnDraw(dc
) 
1185             self
.GetTo().OnDrawContents(dc
) 
1187             self
.SetDisableLabel(True) 
1188             dc
.SetLogicalFunction(OGLRBLF
) 
1195             old_pen 
= self
.GetPen() 
1196             old_brush 
= self
.GetBrush() 
1198             dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1199             self
.SetPen(dottedPen
) 
1200             self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1202             self
.GetEventHandler().OnMoveLink(dc
, False) 
1204             self
.SetPen(old_pen
) 
1205             self
.SetBrush(old_brush
) 
1207         if pt
._type 
== CONTROL_POINT_ENDPOINT_FROM 
or pt
._type 
== CONTROL_POINT_ENDPOINT_TO
: 
1208             self
._canvas
.SetCursor(wx
.StockCursor(wx
.CURSOR_BULLSEYE
)) 
1209             pt
._oldCursor 
= wx
.STANDARD_CURSOR
 
1211     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
1212         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1213         self
.GetCanvas().PrepareDC(dc
) 
1215         self
.SetDisableLabel(False) 
1217         if pt
._type 
== CONTROL_POINT_LINE
: 
1218             x
, y 
= self
._canvas
.Snap(x
, y
) 
1220             rpt 
= wx
.RealPoint(x
, y
) 
1222             # Move the control point back to where it was; 
1223             # MoveControlPoint will move it to the new position 
1224             # if it decides it wants. We only moved the position 
1225             # during user feedback so we could redraw the line 
1226             # as it changed shape. 
1227             pt
._xpos 
= pt
._originalPos
[0] 
1228             pt
._ypos 
= pt
._originalPos
[1] 
1229             pt
._point
[0] = pt
._originalPos
[0] 
1230             pt
._point
[1] = pt
._originalPos
[1] 
1232             self
.OnMoveMiddleControlPoint(dc
, pt
, rpt
) 
1234         if pt
._type 
== CONTROL_POINT_ENDPOINT_FROM
: 
1236                 self
._canvas
.SetCursor(pt
._oldCursor
) 
1239                 self
.GetFrom().MoveLineToNewAttachment(dc
, self
, x
, y
) 
1241         if pt
._type 
== CONTROL_POINT_ENDPOINT_TO
: 
1243                 self
._canvas
.SetCursor(pt
._oldCursor
) 
1246                 self
.GetTo().MoveLineToNewAttachment(dc
, self
, x
, y
) 
1248     # This is called only when a non-end control point is moved 
1249     def OnMoveMiddleControlPoint(self
, dc
, lpt
, pt
): 
1253         lpt
._point
[0] = pt
[0] 
1254         lpt
._point
[1] = pt
[1] 
1256         self
.GetEventHandler().OnMoveLink(dc
) 
1260     def AddArrow(self
, type, end 
= ARROW_POSITION_END
, size 
= 10.0, xOffset 
= 0.0, name 
= "", mf 
= None, arrowId 
= -1): 
1261         """Add an arrow (or annotation) to the line. 
1263         type may currently be one of: 
1270           Conventional arrowhead.  
1271         ARROW_SINGLE_OBLIQUE 
1272           Single oblique stroke.  
1273         ARROW_DOUBLE_OBLIQUE 
1274           Double oblique stroke.  
1275         ARROW_DOUBLE_METAFILE 
1278         end may currently be one of: 
1281           Arrow appears at the end.  
1282         ARROW_POSITION_START 
1283           Arrow appears at the start.  
1285         arrowSize specifies the length of the arrow. 
1287         xOffset specifies the offset from the end of the line. 
1289         name specifies a name for the arrow. 
1291         mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows 
1294         arrowId is the id for the arrow. 
1296         arrow 
= ArrowHead(type, end
, size
, xOffset
, name
, mf
, arrowId
) 
1297         self
._arcArrows
.append(arrow
) 
1300     # Add arrowhead at a particular position in the arrowhead list 
1301     def AddArrowOrdered(self
, arrow
, referenceList
, end
): 
1302         """Add an arrowhead in the position indicated by the reference list 
1303         of arrowheads, which contains all legal arrowheads for this line, in 
1304         the correct order. E.g. 
1306         Reference list:      a b c d e 
1307         Current line list:   a d 
1309         Add c, then line list is: a c d. 
1311         If no legal arrowhead position, return FALSE. Assume reference list 
1312         is for one end only, since it potentially defines the ordering for 
1313         any one of the 3 positions. So we don't check the reference list for 
1316         if not referenceList
: 
1319         targetName 
= arrow
.GetName() 
1321         # First check whether we need to insert in front of list, 
1322         # because this arrowhead is the first in the reference 
1323         # list and should therefore be first in the current list. 
1324         refArrow 
= referenceList
[0] 
1325         if refArrow
.GetName() == targetName
: 
1326             self
._arcArrows
.insert(0, arrow
) 
1330         while i1 
< len(referenceList
) and i2 
< len(self
._arcArrows
): 
1331             refArrow 
= referenceList
[i1
] 
1332             currArrow 
= self
._arcArrows
[i2
] 
1334             # Matching: advance current arrow pointer 
1335             if currArrow
.GetArrowEnd() == end 
and currArrow
.GetName() == refArrow
.GetName(): 
1338             # Check if we're at the correct position in the 
1340             if targetName 
== refArrow
.GetName(): 
1341                 if i2 
< len(self
._arcArrows
): 
1342                     self
._arcArrows
.insert(i2
, arrow
) 
1344                     self
._arcArrows
.append(arrow
) 
1348         self
._arcArrows
.append(arrow
) 
1351     def ClearArrowsAtPosition(self
, end
): 
1352         """Delete the arrows at the specified position, or at any position 
1356             self
._arcArrows 
= [] 
1359         for arrow 
in self
._arcArrows
: 
1360             if arrow
.GetArrowEnd() == end
: 
1361                 self
._arcArrows
.remove(arrow
) 
1363     def ClearArrow(self
, name
): 
1364         """Delete the arrow with the given name.""" 
1365         for arrow 
in self
._arcArrows
: 
1366             if arrow
.GetName() == name
: 
1367                 self
._arcArrows
.remove(arrow
) 
1371     def FindArrowHead(self
, position
, name
): 
1372         """Find arrowhead by position and name. 
1374         if position is -1, matches any position. 
1376         for arrow 
in self
._arcArrows
: 
1377             if (position 
== -1 or position 
== arrow
.GetArrowEnd()) and arrow
.GetName() == name
: 
1382     def FindArrowHeadId(self
, arrowId
): 
1383         """Find arrowhead by id.""" 
1384         for arrow 
in self
._arcArrows
: 
1385             if arrowId 
== arrow
.GetId(): 
1390     def DeleteArrowHead(self
, position
, name
): 
1391         """Delete arrowhead by position and name. 
1393         if position is -1, matches any position. 
1395         for arrow 
in self
._arcArrows
: 
1396             if (position 
== -1 or position 
== arrow
.GetArrowEnd()) and arrow
.GetName() == name
: 
1397                 self
._arcArrows
.remove(arrow
) 
1401     def DeleteArrowHeadId(self
, id): 
1402         """Delete arrowhead by id.""" 
1403         for arrow 
in self
._arcArrows
: 
1404             if arrowId 
== arrow
.GetId(): 
1405                 self
._arcArrows
.remove(arrow
) 
1409     # Calculate the minimum width a line 
1410     # occupies, for the purposes of drawing lines in tools. 
1411     def FindMinimumWidth(self
): 
1412         """Find the horizontal width for drawing a line with arrows in 
1413         minimum space. Assume arrows at end only. 
1416         for arrowHead 
in self
._arcArrows
: 
1417             minWidth 
+= arrowHead
.GetSize() 
1418             if arrowHead 
!= self
._arcArrows
[-1]: 
1419                 minWidth 
+= arrowHead 
+ GetSpacing
 
1421         # We have ABSOLUTE minimum now. So 
1422         # scale it to give it reasonable aesthetics 
1423         # when drawing with line. 
1425             minWidth 
= minWidth 
* 1.4 
1429         self
.SetEnds(0.0, 0.0, minWidth
, 0.0) 
1434     def FindLinePosition(self
, x
, y
): 
1435         """Find which position we're talking about at this x, y. 
1437         Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END. 
1439         startX
, startY
, endX
, endY 
= self
.GetEnds() 
1441         # Find distances from centre, start and end. The smallest wins 
1442         centreDistance 
= math
.sqrt((x 
- self
._xpos
) * (x 
- self
._xpos
) + (y 
- self
._ypos
) * (y 
- self
._ypos
)) 
1443         startDistance 
= math
.sqrt((x 
- startX
) * (x 
- startX
) + (y 
- startY
) * (y 
- startY
)) 
1444         endDistance 
= math
.sqrt((x 
- endX
) * (x 
- endX
) + (y 
- endY
) * (y 
- endY
)) 
1446         if centreDistance 
< startDistance 
and centreDistance 
< endDistance
: 
1447             return ARROW_POSITION_MIDDLE
 
1448         elif startDistance 
< endDistance
: 
1449             return ARROW_POSITION_START
 
1451             return ARROW_POSITION_END
 
1453     def SetAlignmentOrientation(self
, isEnd
, isHoriz
): 
1455             if isHoriz 
and self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
!= LINE_ALIGNMENT_HORIZ
: 
1456                 self
._alignmentEnd 
!= LINE_ALIGNMENT_HORIZ
 
1457             elif not isHoriz 
and self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
: 
1458                 self
._alignmentEnd 
-= LINE_ALIGNMENT_HORIZ
 
1460             if isHoriz 
and self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
!= LINE_ALIGNMENT_HORIZ
: 
1461                 self
._alignmentStart 
!= LINE_ALIGNMENT_HORIZ
 
1462             elif not isHoriz 
and self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
: 
1463                 self
._alignmentStart 
-= LINE_ALIGNMENT_HORIZ
 
1465     def SetAlignmentType(self
, isEnd
, alignType
): 
1467             if alignType 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1468                 if self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
!= LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1469                     self
._alignmentEnd |
= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1470             elif self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1471                 self
._alignmentEnd 
-= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1473             if alignType 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1474                 if self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
!= LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1475                     self
._alignmentStart |
= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1476             elif self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1477                 self
._alignmentStart 
-= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1479     def GetAlignmentOrientation(self
, isEnd
): 
1481             return self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
 
1483             return self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
 
1485     def GetAlignmentType(self
, isEnd
): 
1487             return self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1489             return self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1491     def GetNextControlPoint(self
, shape
): 
1492         """Find the next control point in the line after the start / end point, 
1493         depending on whether the shape is at the start or end. 
1495         n 
= len(self
._lineControlPoints
) 
1496         if self
._to 
== shape
: 
1497             # Must be END of line, so we want (n - 1)th control point. 
1498             # But indexing ends at n-1, so subtract 2. 
1502         if nn 
< len(self
._lineControlPoints
): 
1503             return self
._lineControlPoints
[nn
] 
1506     def OnCreateLabelShape(self
, parent
, region
, w
, h
): 
1507         return LabelShape(parent
, region
, w
, h
) 
1510     def OnLabelMovePre(self
, dc
, labelShape
, x
, y
, old_x
, old_y
, display
): 
1511         labelShape
._shapeRegion
.SetSize(labelShape
.GetWidth(), labelShape
.GetHeight()) 
1513         # Find position in line's region list 
1514         i 
= self
._regions
.index(labelShape
._shapeRegion
) 
1516         xx
, yy 
= self
.GetLabelPosition(i
) 
1517         # Set the region's offset, relative to the default position for 
1519         labelShape
._shapeRegion
.SetPosition(x 
- xx
, y 
- yy
) 
1523         # Need to reformat to fit region 
1524         if labelShape
._shapeRegion
.GetText(): 
1525             s 
= labelShape
._shapeRegion
.GetText() 
1526             labelShape
.FormatText(dc
, s
, i
) 
1527             self
.DrawRegion(dc
, labelShape
._shapeRegion
, xx
, yy
)