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 #---------------------------------------------------------------------------- 
  14 from __future__ 
import division
 
  20 from basic 
import Shape
, ShapeRegion
, ControlPoint
, RectangleShape
 
  23 # Line alignment flags 
  25 LINE_ALIGNMENT_HORIZ
=              1 
  26 LINE_ALIGNMENT_VERT
=               0 
  27 LINE_ALIGNMENT_TO_NEXT_HANDLE
=     2 
  28 LINE_ALIGNMENT_NONE
=               0 
  32 class LineControlPoint(ControlPoint
): 
  33     def __init__(self
, theCanvas 
= None, object = None, size 
= 0.0, x 
= 0.0, y 
= 0.0, the_type 
= 0): 
  34         ControlPoint
.__init
__(self
, theCanvas
, object, size
, x
, y
, the_type
) 
  39         self
._originalPos 
= None 
  42         RectangleShape
.OnDraw(self
, dc
) 
  44     # Implement movement of Line point 
  45     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
  46         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
  48     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
  49         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
  51     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
  52         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
  56 class ArrowHead(object): 
  57     def __init__(self
, type = 0, end 
= 0, size 
= 0.0, dist 
= 0.0, name
="",mf 
= None, arrowId
=-1): 
  58         if isinstance(type, ArrowHead
): 
  61             self
._arrowType 
= type 
  63             self
._arrowSize 
= size
 
  68             self
._arrowName 
= name
 
  75         return self
._arrowType
 
  77     def GetPosition(self
): 
  80     def SetPosition(self
, pos
): 
  93         return self
._arrowSize
 
  95     def SetSize(self
, size
): 
  96         self
._arrowSize 
= size
 
  97         if self
._arrowType 
== ARROW_METAFILE 
and self
._metaFile
: 
  98             oldWidth 
= self
._metaFile
._width
 
 102             scale 
= size 
/ oldWidth
 
 104                 self
._metaFile
.Scale(scale
, scale
) 
 107         return self
._arrowName
 
 109     def SetXOffset(self
, x
): 
 112     def SetYOffset(self
, y
): 
 115     def GetMetaFile(self
): 
 116         return self
._metaFile
 
 121     def GetArrowEnd(self
): 
 122         return self
._arrowEnd
 
 124     def GetArrowSize(self
): 
 125         return self
._arrowSize
 
 127     def SetSpacing(self
, sp
): 
 132 class LabelShape(RectangleShape
): 
 133     def __init__(self
, parent
, region
, w
, h
): 
 134         RectangleShape
.__init
__(self
, w
, h
) 
 135         self
._lineShape 
= parent
 
 136         self
._shapeRegion 
= region
 
 137         self
.SetPen(wx
.ThePenList
.FindOrCreatePen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)) 
 139     def OnDraw(self
, dc
): 
 140         if self
._lineShape 
and not self
._lineShape
.GetDrawHandles(): 
 143         x1 
= self
._xpos
-self
._width 
/ 2 
 144         y1 
= self
._ypos
-self
._height 
/ 2 
 147             if self
._pen
.GetWidth() == 0: 
 148                 dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
)) 
 151         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 153         if self
._cornerRadius
>0: 
 154             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
 156             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
 158     def OnDrawContents(self
, dc
): 
 161     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 162         RectangleShape
.OnDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
 164     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 165         RectangleShape
.OnBeginDragLeft(self
, x
, y
, keys
, attachment
) 
 167     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 168         RectangleShape
.OnEndDragLeft(self
, x
, y
, keys
, attachment
) 
 170     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display
): 
 171         return self
._lineShape
.OnLabelMovePre(dc
, self
, x
, y
, old_x
, old_y
, display
) 
 173     # Divert left and right clicks to line object 
 174     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 175         self
._lineShape
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
 177     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 178         self
._lineShape
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
 182 class LineShape(Shape
): 
 183     """LineShape may be attached to two nodes; 
 184     it may be segmented, in which case a control point is drawn for each joint. 
 186     A wxLineShape may have arrows at the beginning, end and centre. 
 194         self
._sensitivity 
= OP_CLICK_LEFT | OP_CLICK_RIGHT
 
 195         self
._draggable 
= False 
 196         self
._attachmentTo 
= 0 
 197         self
._attachmentFrom 
= 0 
 200         self
._erasing 
= False 
 201         self
._arrowSpacing 
= 5.0 
 202         self
._ignoreArrowOffsets 
= False 
 203         self
._isSpline 
= False 
 204         self
._maintainStraightLines 
= False 
 205         self
._alignmentStart 
= 0 
 206         self
._alignmentEnd 
= 0 
 208         self
._lineControlPoints 
= None 
 210         # Clear any existing regions (created in an earlier constructor) 
 211         # and make the three line regions. 
 213         for name 
in ["Middle","Start","End"]: 
 214             newRegion 
= ShapeRegion() 
 215             newRegion
.SetName(name
) 
 216             newRegion
.SetSize(150, 50) 
 217             self
._regions
.append(newRegion
) 
 219         self
._labelObjects 
= [None, None, None] 
 220         self
._lineOrientations 
= [] 
 221         self
._lineControlPoints 
= [] 
 225         if self
._lineControlPoints
: 
 226             self
.ClearPointList(self
._lineControlPoints
) 
 227             self
._lineControlPoints 
= [] 
 229             if self
._labelObjects
[i
]: 
 230                 self
._labelObjects
[i
].Select(False) 
 231                 self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
 232         self
._labelObjects 
= [] 
 233         self
.ClearArrowsAtPosition(-1) 
 236         """Return the 'from' object.""" 
 240         """Return the 'to' object.""" 
 243     def GetAttachmentFrom(self
): 
 244         """Return the attachment point on the 'from' node.""" 
 245         return self
._attachmentFrom
 
 247     def GetAttachmentTo(self
): 
 248         """Return the attachment point on the 'to' node.""" 
 249         return self
._attachmentTo
 
 251     def GetLineControlPoints(self
): 
 252         return self
._lineControlPoints
 
 254     def SetSpline(self
, spline
): 
 255         """Specifies whether a spline is to be drawn through the control points.""" 
 256         self
._isSpline 
= spline
 
 259         """TRUE if a spline is drawn through the control points.""" 
 260         return self
._isSpline
 
 262     def SetAttachmentFrom(self
, attach
): 
 263         """Set the 'from' shape attachment.""" 
 264         self
._attachmentFrom 
= attach
 
 266     def SetAttachmentTo(self
, attach
): 
 267         """Set the 'to' shape attachment.""" 
 268         self
._attachmentTo 
= attach
 
 270     # This is really to distinguish between lines and other images. 
 271     # For lines, want to pass drag to canvas, since lines tend to prevent 
 272     # dragging on a canvas (they get in the way.) 
 276     def SetIgnoreOffsets(self
, ignore
): 
 277         """Set whether to ignore offsets from the end of the line when drawing.""" 
 278         self
._ignoreArrowOffsets 
= ignore
 
 281         return self
._arcArrows
 
 283     def GetAlignmentStart(self
): 
 284         return self
._alignmentStart
 
 286     def GetAlignmentEnd(self
): 
 287         return self
._alignmentEnd
 
 289     def IsEnd(self
, nodeObject
): 
 290         """TRUE if shape is at the end of the line.""" 
 291         return self
._to 
== nodeObject
 
 293     def MakeLineControlPoints(self
, n
): 
 294         """Make a given number of control points (minimum of two).""" 
 295         if self
._lineControlPoints
: 
 296             self
.ClearPointList(self
._lineControlPoints
) 
 297         self
._lineControlPoints 
= [] 
 300             point 
= wx
.RealPoint(-999,-999) 
 301             self
._lineControlPoints
.append(point
) 
 303     def InsertLineControlPoint(self
, dc 
= None): 
 304         """Insert a control point at an arbitrary position.""" 
 308         last_point 
= self
._lineControlPoints
[-1] 
 309         second_last_point 
= self
._lineControlPoints
[-2] 
 311         line_x 
= (last_point
[0] + second_last_point
[0]) / 2 
 312         line_y 
= (last_point
[1] + second_last_point
[1]) / 2 
 314         point 
= wx
.RealPoint(line_x
, line_y
) 
 315         self
._lineControlPoints
.insert(len(self
._lineControlPoints
), point
) 
 317     def DeleteLineControlPoint(self
): 
 318         """Delete an arbitary point on the line.""" 
 319         if len(self
._lineControlPoints
)<3: 
 322         del self
._lineControlPoints
[-2] 
 325     def Initialise(self
): 
 326         """Initialise the line object.""" 
 327         if self
._lineControlPoints
: 
 328             # Just move the first and last control points 
 329             first_point 
= self
._lineControlPoints
[0] 
 330             last_point 
= self
._lineControlPoints
[-1] 
 332             # If any of the line points are at -999, we must 
 333             # initialize them by placing them half way between the first 
 336             for point 
in self
._lineControlPoints
[1:]: 
 338                     if first_point
[0]<last_point
[0]: 
 344                     if first_point
[1]<last_point
[1]: 
 350                     point
[0] = (x2
-x1
) / 2 + x1
 
 351                     point
[1] = (y2
-y1
) / 2 + y1
 
 353     def FormatText(self
, dc
, s
, i
): 
 354         """Format a text string according to the region size, adding 
 355         strings with positions to region text list. 
 359         if len(self
._regions
) == 0 or i 
>= len(self
._regions
): 
 362         region 
= self
._regions
[i
] 
 364         dc
.SetFont(region
.GetFont()) 
 366         w
, h 
= region
.GetSize() 
 367         # Initialize the size if zero 
 368         if (w 
== 0 or h 
== 0) and s
: 
 372         string_list 
= FormatText(dc
, s
, w
-5, h
-5, region
.GetFormatMode()) 
 373         for s 
in string_list
: 
 374             line 
= ShapeTextLine(0.0, 0.0, s
) 
 375             region
.GetFormattedText().append(line
) 
 379         if region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
: 
 380             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w
, h
) 
 381             if actualW 
!= w 
or actualH 
!= h
: 
 382                 xx
, yy 
= self
.GetLabelPosition(i
) 
 383                 self
.EraseRegion(dc
, region
, xx
, yy
) 
 384                 if len(self
._labelObjects
)<i
: 
 385                     self
._labelObjects
[i
].Select(False, dc
) 
 386                     self
._labelObjects
[i
].Erase(dc
) 
 387                     self
._labelObjects
[i
].SetSize(actualW
, actualH
) 
 389                 region
.SetSize(actualW
, actualH
) 
 391                 if len(self
._labelObjects
)<i
: 
 392                     self
._labelObjects
[i
].Select(True, dc
) 
 393                     self
._labelObjects
[i
].Draw(dc
) 
 395         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW
, actualH
, region
.GetFormatMode()) 
 396         self
._formatted 
= True 
 398     def DrawRegion(self
, dc
, region
, x
, y
): 
 399         """Format one region at this position.""" 
 400         if self
.GetDisableLabel(): 
 403         w
, h 
= region
.GetSize() 
 405         # Get offset from x, y 
 406         xx
, yy 
= region
.GetPosition() 
 411         # First, clear a rectangle for the text IF there is any 
 412         if len(region
.GetFormattedText()): 
 413             dc
.SetPen(self
.GetBackgroundPen()) 
 414             dc
.SetBrush(self
.GetBackgroundBrush()) 
 418                 dc
.SetFont(region
.GetFont()) 
 419                 dc
.DrawRectangle(xp
-w 
/ 2, yp
-h 
/ 2, w
, h
) 
 423                 dc
.SetTextForeground(region
.GetActualColourObject()) 
 425                 DrawFormattedText(dc
, region
.GetFormattedText(), xp
, yp
, w
, h
, region
.GetFormatMode()) 
 427     def EraseRegion(self
, dc
, region
, x
, y
): 
 428         """Erase one region at this position.""" 
 429         if self
.GetDisableLabel(): 
 432         w
, h 
= region
.GetSize() 
 434         # Get offset from x, y 
 435         xx
, yy 
= region
.GetPosition() 
 440         if region
.GetFormattedText(): 
 441             dc
.SetPen(self
.GetBackgroundPen()) 
 442             dc
.SetBrush(self
.GetBackgroundBrush()) 
 444             dc
.DrawRectangle(xp
-w 
/ 2, yp
-h 
/ 2, w
, h
) 
 446     def GetLabelPosition(self
, position
): 
 447         """Get the reference point for a label. 
 449         Region x and y are offsets from this. 
 450         position is 0 (middle), 1 (start), 2 (end). 
 453             # Want to take the middle section for the label 
 454             half_way 
= int(len(self
._lineControlPoints
) / 2) 
 456             # Find middle of this line 
 457             point 
= self
._lineControlPoints
[half_way
-1] 
 458             next_point 
= self
._lineControlPoints
[half_way
] 
 460             dx 
= next_point
[0]-point
[0] 
 461             dy 
= next_point
[1]-point
[1] 
 463             return point
[0] + dx 
/ 2, point
[1] + dy 
/ 2 
 465             return self
._lineControlPoints
[0][0], self
._lineControlPoints
[0][1] 
 467             return self
._lineControlPoints
[-1][0], self
._lineControlPoints
[-1][1] 
 469     def Straighten(self
, dc 
= None): 
 470         """Straighten verticals and horizontals.""" 
 471         if len(self
._lineControlPoints
)<3: 
 477         GraphicsStraightenLine(self
._lineControlPoints
[-1], self
._lineControlPoints
[-2]) 
 479         for i 
in range(len(self
._lineControlPoints
)-2): 
 480             GraphicsStraightenLine(self
._lineControlPoints
[i
], self
._lineControlPoints
[i 
+ 1]) 
 486         """Unlink the line from the nodes at either end.""" 
 488             self
._to
.GetLines().remove(self
) 
 490             self
._from
.GetLines().remove(self
) 
 494     def SetEnds(self
, x1
, y1
, x2
, y2
): 
 495         """Set the end positions of the line.""" 
 497         first_point 
= self
._lineControlPoints
[0] 
 498         last_point 
= self
._lineControlPoints
[-1] 
 505         self
._xpos 
= (x1 
+ x2
) / 2 
 506         self
._ypos 
= (y1 
+ y2
) / 2 
 508     # Get absolute positions of ends 
 510         """Get the visible endpoints of the lines for drawing between two objects.""" 
 511         first_point 
= self
._lineControlPoints
[0] 
 512         last_point 
= self
._lineControlPoints
[-1] 
 514         return (first_point
[0], first_point
[1]), (last_point
[0], last_point
[1]) 
 516     def SetAttachments(self
, from_attach
, to_attach
): 
 517         """Specify which object attachment points should be used at each end 
 520         self
._attachmentFrom 
= from_attach
 
 521         self
._attachmentTo 
= to_attach
 
 523     def HitTest(self
, x
, y
): 
 524         if not self
._lineControlPoints
: 
 527         # Look at label regions in case mouse is over a label 
 528         inLabelRegion 
= False 
 531                 region 
= self
._regions
[i
] 
 532                 if len(region
._formattedText
): 
 533                     xp
, yp 
= self
.GetLabelPosition(i
) 
 534                     # Offset region from default label position 
 535                     cx
, cy 
= region
.GetPosition() 
 536                     cw
, ch 
= region
.GetSize() 
 543                     rBottom 
= cy 
+ ch 
/ 2 
 544                     if x
>rLeft 
and x
<rRight 
and y
>rTop 
and y
<rBottom
: 
 548         for i 
in range(len(self
._lineControlPoints
)-1): 
 549             point1 
= self
._lineControlPoints
[i
] 
 550             point2 
= self
._lineControlPoints
[i 
+ 1] 
 552             # For inaccurate mousing allow 8 pixel corridor 
 555             dx 
= point2
[0]-point1
[0] 
 556             dy 
= point2
[1]-point1
[1] 
 558             seg_len 
= sqrt(dx 
* dx 
+ dy 
* dy
) 
 559             if dy 
== 0 or dx 
== 0: 
 561             distance_from_seg 
= seg_len 
* ((x
-point1
[0]) * dy
-(y
-point1
[1]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 562             distance_from_prev 
= seg_len 
* ((y
-point1
[1]) * dy 
+ (x
-point1
[0]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 564             if abs(distance_from_seg
)<extra 
and distance_from_prev 
>= 0 and distance_from_prev 
<= seg_len 
or inLabelRegion
: 
 565                 return 0, distance_from_seg
 
 569     def DrawArrows(self
, dc
): 
 570         """Draw all arrows.""" 
 571         # Distance along line of each arrow: space them out evenly 
 576         for arrow 
in self
._arcArrows
: 
 577             ah 
= arrow
.GetArrowEnd() 
 578             if ah 
== ARROW_POSITION_START
: 
 579                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 580                     # If specified, x offset is proportional to line length 
 581                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 583                     self
.DrawArrow(dc
, arrow
, startArrowPos
, False) 
 584                     startArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 585             elif ah 
== ARROW_POSITION_END
: 
 586                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 587                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 589                     self
.DrawArrow(dc
, arrow
, endArrowPos
, False) 
 590                     endArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 591             elif ah 
== ARROW_POSITION_MIDDLE
: 
 592                 arrow
.SetXOffset(middleArrowPos
) 
 593                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 594                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 596                     self
.DrawArrow(dc
, arrow
, middleArrowPos
, False) 
 597                     middleArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 599     def DrawArrow(self
, dc
, arrow
, XOffset
, proportionalOffset
): 
 600         """Draw the given arrowhead (or annotation).""" 
 601         first_line_point 
= self
._lineControlPoints
[0] 
 602         second_line_point 
= self
._lineControlPoints
[1] 
 604         last_line_point 
= self
._lineControlPoints
[-1] 
 605         second_last_line_point 
= self
._lineControlPoints
[-2] 
 607         # Position of start point of line, at the end of which we draw the arrow 
 608         startPositionX
, startPositionY 
= 0.0, 0.0 
 610         ap 
= arrow
.GetPosition() 
 611         if ap 
== ARROW_POSITION_START
: 
 612             # If we're using a proportional offset, calculate just where this 
 613             # will be on the line. 
 615             if proportionalOffset
: 
 616                 totalLength 
= sqrt((second_line_point
[0]-first_line_point
[0]) * (second_line_point
[0]-first_line_point
[0]) + (second_line_point
[1]-first_line_point
[1]) * (second_line_point
[1]-first_line_point
[1])) 
 617                 realOffset 
= XOffset 
* totalLength
 
 619             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_line_point
[0], second_line_point
[1], first_line_point
[0], first_line_point
[1], realOffset
) 
 621             startPositionX 
= second_line_point
[0] 
 622             startPositionY 
= second_line_point
[1] 
 623         elif ap 
== ARROW_POSITION_END
: 
 624             # If we're using a proportional offset, calculate just where this 
 625             # will be on the line. 
 627             if proportionalOffset
: 
 628                 totalLength 
= sqrt((second_last_line_point
[0]-last_line_point
[0]) * (second_last_line_point
[0]-last_line_point
[0]) + (second_last_line_point
[1]-last_line_point
[1]) * (second_last_line_point
[1]-last_line_point
[1])); 
 629                 realOffset 
= XOffset 
* totalLength
 
 631             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], last_line_point
[0], last_line_point
[1], realOffset
) 
 633             startPositionX 
= second_last_line_point
[0] 
 634             startPositionY 
= second_last_line_point
[1] 
 635         elif ap 
== ARROW_POSITION_MIDDLE
: 
 636             # Choose a point half way between the last and penultimate points 
 637             x 
= (last_line_point
[0] + second_last_line_point
[0]) / 2 
 638             y 
= (last_line_point
[1] + second_last_line_point
[1]) / 2 
 640             # If we're using a proportional offset, calculate just where this 
 641             # will be on the line. 
 643             if proportionalOffset
: 
 644                 totalLength 
= sqrt((second_last_line_point
[0]-x
) * (second_last_line_point
[0]-x
) + (second_last_line_point
[1]-y
) * (second_last_line_point
[1]-y
)); 
 645                 realOffset 
= XOffset 
* totalLength
 
 647             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], x
, y
, realOffset
) 
 648             startPositionX 
= second_last_line_point
[0] 
 649             startPositionY 
= second_last_line_point
[1] 
 651         # Add yOffset to arrow, if any 
 653         # The translation that the y offset may give 
 656         if arrow
.GetYOffset 
and not self
._ignoreArrowOffsets
: 
 660             #   (x1, y1)--------------(x3, y3)------------------(x2, y2) 
 661             #   x4 = x3 - d * sin(theta) 
 662             #   y4 = y3 + d * cos(theta) 
 664             #   Where theta = tan(-1) of (y3-y1) / (x3-x1) 
 669             d
=-arrow
.GetYOffset() # Negate so +offset is above line 
 674                 theta 
= atan((y3
-y1
) / (x3
-x1
)) 
 676             x4 
= x3
-d 
* sin(theta
) 
 677             y4 
= y3 
+ d 
* cos(theta
) 
 679             deltaX 
= x4
-positionOnLineX
 
 680             deltaY 
= y4
-positionOnLineY
 
 682         at 
= arrow
._GetType
() 
 683         if at 
== ARROW_ARROW
: 
 684             arrowLength 
= arrow
.GetSize() 
 685             arrowWidth 
= arrowLength 
/ 3 
 687             tip_x
, tip_y
, side1_x
, side1_y
, side2_x
, side2_y 
= GetArrowPoints(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, arrowLength
, arrowWidth
) 
 689             points 
= [[tip_x
, tip_y
], 
 695             dc
.SetBrush(self
._brush
) 
 696             dc
.DrawPolygon(points
) 
 697         elif at 
in [ARROW_HOLLOW_CIRCLE
, ARROW_FILLED_CIRCLE
]: 
 698             # Find point on line of centre of circle, which is a radius away 
 699             # from the end position 
 700             diameter 
= arrow
.GetSize() 
 701             x
, y 
= GetPointOnLine(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, 
 702                                positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, 
 707             if arrow
._GetType
() == ARROW_HOLLOW_CIRCLE
: 
 708                 dc
.SetBrush(self
.GetBackgroundBrush()) 
 710                 dc
.SetBrush(self
._brush
) 
 712             dc
.DrawEllipse(x1
, y1
, diameter
, diameter
) 
 713         elif at 
== ARROW_SINGLE_OBLIQUE
: 
 715         elif at 
== ARROW_METAFILE
: 
 716             if arrow
.GetMetaFile(): 
 717                 # Find point on line of centre of object, which is a half-width away 
 718                 # from the end position 
 721                 #  <-- start pos  <-----><-- positionOnLineX 
 723                 #  --------------|  x  | <-- e.g. rectangular arrowhead 
 726                 x
, y 
= GetPointOnLine(startPositionX
, startPositionY
, 
 727                                    positionOnLineX
, positionOnLineY
, 
 728                                    arrow
.GetMetaFile()._width 
/ 2) 
 729                 # Calculate theta for rotating the metafile. 
 732                 # |     o(x2, y2)   'o' represents the arrowhead. 
 737                 # |______________________ 
 745                 if x1 
== x2 
and y1 
== y2
: 
 747                 elif x1 
== x2 
and y1
>y2
: 
 749                 elif x1 
== x2 
and y2
>y1
: 
 751                 elif x2
>x1 
and y2 
>= y1
: 
 752                     theta 
= atan((y2
-y1
) / (x2
-x1
)) 
 754                     theta 
= pi 
+ atan((y2
-y1
) / (x2
-x1
)) 
 755                 elif x2
>x1 
and y2
<y1
: 
 756                     theta 
= 2 * pi 
+ atan((y2
-y1
) / (x2
-x1
)) 
 758                     raise "Unknown arrowhead rotation case" 
 760                 # Rotate about the centre of the object, then place 
 761                 # the object on the line. 
 762                 if arrow
.GetMetaFile().GetRotateable(): 
 763                     arrow
.GetMetaFile().Rotate(0.0, 0.0, theta
) 
 766                     # If erasing, just draw a rectangle 
 767                     minX
, minY
, maxX
, maxY 
= arrow
.GetMetaFile().GetBounds() 
 768                     # Make erasing rectangle slightly bigger or you get droppings 
 770                     dc
.DrawRectangle(deltaX 
+ x 
+ minX
-extraPixels 
/ 2, deltaY 
+ y 
+ minY
-extraPixels 
/ 2, maxX
-minX 
+ extraPixels
, maxY
-minY 
+ extraPixels
) 
 772                     arrow
.GetMetaFile().Draw(dc
, x 
+ deltaX
, y 
+ deltaY
) 
 774     def OnErase(self
, dc
): 
 776         old_brush 
= self
._brush
 
 778         bg_pen 
= self
.GetBackgroundPen() 
 779         bg_brush 
= self
.GetBackgroundBrush() 
 781         self
.SetBrush(bg_brush
) 
 783         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
 785             dc
.SetFont(self
._font
) 
 787         # Undraw text regions 
 790                 x
, y 
= self
.GetLabelPosition(i
) 
 791                 self
.EraseRegion(dc
, self
._regions
[i
], x
, y
) 
 794         dc
.SetPen(self
.GetBackgroundPen()) 
 795         dc
.SetBrush(self
.GetBackgroundBrush()) 
 797         # Drawing over the line only seems to work if the line has a thickness 
 799         if old_pen 
and old_pen
.GetWidth()>1: 
 800             dc
.DrawRectangle(self
._xpos
-bound_x 
/ 2-2, self
._ypos
-bound_y 
/ 2-2, 
 801                              bound_x 
+ 4, bound_y 
+ 4) 
 804             self
.GetEventHandler().OnDraw(dc
) 
 805             self
.GetEventHandler().OnEraseControlPoints(dc
) 
 806             self
._erasing 
= False 
 811             self
.SetBrush(old_brush
) 
 813     def GetBoundingBoxMin(self
): 
 814         x1
, y1 
= 10000, 10000 
 817         for point 
in self
._lineControlPoints
: 
 829     # For a node image of interest, finds the position of this arc 
 830     # amongst all the arcs which are attached to THIS SIDE of the node image, 
 831     # and the number of same. 
 832     def FindNth(self
, image
, incoming
): 
 833         """Find the position of the line on the given object. 
 835         Specify whether incoming or outgoing lines are being considered 
 841         if image 
== self
._to
: 
 842             this_attachment 
= self
._attachmentTo
 
 844             this_attachment 
= self
._attachmentFrom
 
 846         # Find number of lines going into / out of this particular attachment point 
 847         for line 
in image
.GetLines(): 
 848             if line
._from 
== image
: 
 849                 # This is the nth line attached to 'image' 
 850                 if line 
== self 
and not incoming
: 
 853                 # Increment num count if this is the same side (attachment number) 
 854                 if line
._attachmentFrom 
== this_attachment
: 
 857             if line
._to 
== image
: 
 858                 # This is the nth line attached to 'image' 
 859                 if line 
== self 
and incoming
: 
 862                 # Increment num count if this is the same side (attachment number) 
 863                 if line
._attachmentTo 
== this_attachment
: 
 868     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 870         old_brush 
= self
._brush
 
 872         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
 873         self
.SetPen(dottedPen
) 
 874         self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 876         self
.GetEventHandler().OnDraw(dc
) 
 883             self
.SetBrush(old_brush
) 
 887     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 891         if self
._lineControlPoints 
and not (x_offset 
== 0 and y_offset 
== 0): 
 892             for point 
in self
._lineControlPoints
: 
 896         # Move temporary label rectangles if necessary 
 898             if self
._labelObjects
[i
]: 
 899                 self
._labelObjects
[i
].Erase(dc
) 
 900                 xp
, yp 
= self
.GetLabelPosition(i
) 
 901                 if i
<len(self
._regions
): 
 902                     xr
, yr 
= self
._regions
[i
].GetPosition() 
 905                 self
._labelObjects
[i
].Move(dc
, xp 
+ xr
, yp 
+ yr
) 
 908     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
 909         """Called when a connected object has moved, to move the link to 
 912         if not self
._from 
or not self
._to
: 
 915         if len(self
._lineControlPoints
)>2: 
 918         # Do each end - nothing in the middle. User has to move other points 
 919         # manually if necessary 
 920         end_x
, end_y
, other_end_x
, other_end_y 
= self
.FindLineEndPoints() 
 922         first 
= self
._lineControlPoints
[0] 
 923         last 
= self
._lineControlPoints
[-1] 
 925         oldX
, oldY 
= self
._xpos
, self
._ypos
 
 927         self
.SetEnds(end_x
, end_y
, other_end_x
, other_end_y
) 
 929         # Do a second time, because one may depend on the other 
 930         end_x
, end_y
, other_end_x
, other_end_y 
= self
.FindLineEndPoints() 
 931         self
.SetEnds(end_x
, end_y
, other_end_x
, other_end_y
) 
 933         # Try to move control points with the arc 
 934         x_offset 
= self
._xpos
-oldX
 
 935         y_offset 
= self
._ypos
-oldY
 
 937         # Only move control points if it's a self link. And only works 
 938         # if attachment mode is ON 
 939         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): 
 940             for point 
in self
._lineControlPoints
[1:-1]: 
 944         self
.Move(dc
, self
._xpos
, self
._ypos
) 
 946     def FindLineEndPoints(self
): 
 947         """Finds the x, y points at the two ends of the line. 
 949         This function can be used by e.g. line-routing routines to 
 950         get the actual points on the two node images where the lines will be 
 953         if not self
._from 
or not self
._to
: 
 956         # Do each end - nothing in the middle. User has to move other points 
 957         # manually if necessary. 
 958         second_point 
= self
._lineControlPoints
[1] 
 959         second_last_point 
= self
._lineControlPoints
[-2] 
 961         if len(self
._lineControlPoints
)>2: 
 962             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 963                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) # Not incoming 
 964                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 966                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), second_point
[0], second_point
[1]) 
 968             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 969                 nth
, no_arch 
= self
.FindNth(self
._to
, True) # Incoming 
 970                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arch
, self
) 
 972                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), second_last_point
[0], second_last_point
[1]) 
 974             fromX 
= self
._from
.GetX() 
 975             fromY 
= self
._from
.GetY() 
 976             toX 
= self
._to
.GetX() 
 977             toY 
= self
._to
.GetY() 
 979             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 980                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) 
 981                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 985             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 986                 nth
, no_arcs 
= self
.FindNth(self
._to
, True) 
 987                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arcs
, self
) 
 991             if self
._from
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 992                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), toX
, toY
) 
 994             if self
._to
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 995                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), fromX
, fromY
) 
 997             #print type(self._from), type(self._to), end_x, end_y, other_end_x, other_end_y 
 998             return end_x
, end_y
, other_end_x
, other_end_y
 
1000     def OnDraw(self
, dc
): 
1001         if not self
._lineControlPoints
: 
1005             dc
.SetPen(self
._pen
) 
1007             dc
.SetBrush(self
._brush
) 
1010         for point 
in self
._lineControlPoints
: 
1011             points
.append(wx
.Point(point
.x
, point
.y
)) 
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 
1021             dc
.DrawPoint(points
[-1]) 
1023         # Problem with pen - if not a solid pen, does strange things 
1024         # to the arrowhead. So make (get) a new pen that's solid. 
1025         if self
._pen 
and self
._pen
.GetStyle() != wx
.SOLID
: 
1026             solid_pen 
= wx
.ThePenList().FindOrCreatePen(self
._pen
.GetColour(), 1, wx
.SOLID
) 
1028                 dc
.SetPen(solid_pen
) 
1032     def OnDrawControlPoints(self
, dc
): 
1033         if not self
._drawHandles
: 
1036         # Draw temporary label rectangles if necessary 
1038             if self
._labelObjects
[i
]: 
1039                 self
._labelObjects
[i
].Draw(dc
) 
1041         Shape
.OnDrawControlPoints(self
, dc
) 
1043     def OnEraseControlPoints(self
, dc
): 
1044         # Erase temporary label rectangles if necessary 
1047             if self
._labelObjects
[i
]: 
1048                 self
._labelObjects
[i
].Erase(dc
) 
1050         Shape
.OnEraseControlPoints(self
, dc
) 
1052     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1055     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1058     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1061     def OnDrawContents(self
, dc
): 
1062         if self
.GetDisableLabel(): 
1066             if self
._regions
[i
]: 
1067                 x
, y 
= self
.GetLabelPosition(i
) 
1068                 self
.DrawRegion(dc
, self
._regions
[i
], x
, y
) 
1070     def SetTo(self
, object): 
1071         """Set the 'to' object for the line.""" 
1074     def SetFrom(self
, object): 
1075         """Set the 'from' object for the line.""" 
1078     def MakeControlPoints(self
): 
1079         """Make handle control points.""" 
1080         if self
._canvas 
and self
._lineControlPoints
: 
1081             first 
= self
._lineControlPoints
[0] 
1082             last 
= self
._lineControlPoints
[-1] 
1084             control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, first
[0], first
[1], CONTROL_POINT_ENDPOINT_FROM
) 
1085             control
._point 
= first
 
1086             self
._canvas
.AddShape(control
) 
1087             self
._controlPoints
.Append(control
) 
1089             for point 
in self
._lineControlPoints
[1:-1]: 
1090                 control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
[0], point
[1], CONTROL_POINT_LINE
) 
1091                 control
._point 
= point
 
1092                 self
._canvas
.AddShape(control
) 
1093                 self
._controlPoints
.Append(control
) 
1095             control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, last
[0], last
[1], CONTROL_POINT_ENDPOINT_TO
) 
1096             control
._point 
= last
 
1097             self
._canvas
.AddShape(control
) 
1098             self
._controlPoints
.Append(control
) 
1100     def ResetControlPoints(self
): 
1101         if self
._canvas 
and self
._lineControlPoints
: 
1102             for i 
in range(min(len(self
._controlPoints
), len(self
._lineControlPoints
))): 
1103                 point 
= self
._lineControlPoints
[i
] 
1104                 control 
= self
._controlPoints
[i
] 
1105                 control
.SetX(point
[0]) 
1106                 control
.SetY(point
[1]) 
1108     # Override select, to create / delete temporary label-moving objects 
1109     def Select(self
, select
, dc 
= None): 
1110         Shape
.Select(self
, select
, dc
) 
1113                 if self
._regions
[i
]: 
1114                     region 
= self
._regions
[i
] 
1115                     if region
._formattedText
: 
1116                         w
, h 
= region
.GetSize() 
1117                         x
, y 
= region
.GetPosition() 
1118                         xx
, yy 
= self
.GetLabelPosition(i
) 
1120                         if self
._labelObjects
[i
]: 
1121                             self
._labelObjects
[i
].Select(False) 
1122                             self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
1124                         self
._labelObjects
[i
] = self
.OnCreateLabelShape(self
, region
, w
, h
) 
1125                         self
._labelObjects
[i
].AddToCanvas(self
._canvas
) 
1126                         self
._labelObjects
[i
].Show(True) 
1128                             self
._labelObjects
[i
].Move(dc
, x 
+ xx
, y 
+ yy
) 
1129                         self
._labelObjects
[i
].Select(True, dc
) 
1132                 if self
._labelObjects
[i
]: 
1133                     self
._labelObjects
[i
].Select(False, dc
) 
1134                     self
._labelObjects
[i
].Erase(dc
) 
1135                     self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
1136                     self
._labelObjects
[i
] = None 
1138     # Control points ('handles') redirect control to the actual shape, to 
1139     # make it easier to override sizing behaviour. 
1140     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1141         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1142         self
.GetCanvas().PrepareDC(dc
) 
1144         dc
.SetLogicalFunction(OGLRBLF
) 
1146         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1147         dc
.SetPen(dottedPen
) 
1148         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1150         if pt
._type 
== CONTROL_POINT_LINE
: 
1151             x
, y 
= self
._canvas
.Snap() 
1158             old_pen 
= self
.GetPen() 
1159             old_brush 
= self
.GetBrush() 
1161             self
.SetPen(dottedPen
) 
1162             self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1164             self
.GetEventHandler().OnMoveLink(dc
, False) 
1166             self
.SetPen(old_pen
) 
1167             self
.SetBrush(old_brush
) 
1169     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
1170         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1171         self
.GetCanvas().PrepareDC(dc
) 
1173         if pt
._type 
== CONTROL_POINT_LINE
: 
1174             pt
._originalPos 
= pt
._point
 
1175             x
, y 
= self
._canvas
.Snap() 
1179             # Redraw start and end objects because we've left holes 
1180             # when erasing the line 
1181             self
.GetFrom().OnDraw(dc
) 
1182             self
.GetFrom().OnDrawContents(dc
) 
1183             self
.GetTo().OnDraw(dc
) 
1184             self
.GetTo().OnDrawContents(dc
) 
1186             self
.SetDisableLabel(True) 
1187             dc
.SetLogicalFunction(OGLRBLF
) 
1194             old_pen 
= self
.GetPen() 
1195             old_brush 
= self
.GetBrush() 
1197             dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1198             self
.SetPen(dottedPen
) 
1199             self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1201             self
.GetEventHandler().OnMoveLink(dc
, False) 
1203             self
.SetPen(old_pen
) 
1204             self
.SetBrush(old_brush
) 
1206         if pt
._type 
== CONTROL_POINT_ENDPOINT_FROM 
or pt
._type 
== CONTROL_POINT_ENDPOINT_TO
: 
1207             self
._canvas
.SetCursor(wx
.Cursor(wx
.CURSOR_BULLSEYE
)) 
1208             pt
._oldCursor 
= wx
.STANDARD_CURSOR
 
1210     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
1211         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1212         self
.GetCanvas().PrepareDC(dc
) 
1214         self
.SetDisableLabel(False) 
1216         if pt
._type 
== CONTROL_POINT_LINE
: 
1217             x
, y 
= self
._canvas
.Snap() 
1219             rpt 
= wx
.RealPoint(x
, y
) 
1221             # Move the control point back to where it was; 
1222             # MoveControlPoint will move it to the new position 
1223             # if it decides it wants. We only moved the position 
1224             # during user feedback so we could redraw the line 
1225             # as it changed shape. 
1226             pt
._xpos 
= pt
._originalPos
[0] 
1227             pt
._ypos 
= pt
._originalPos
[1] 
1228             pt
._point
[0] = pt
._originalPos
[0] 
1229             pt
._point
[1] = pt
._originalPos
[1] 
1231             self
.OnMoveMiddleControlPoint(dc
, pt
, rpt
) 
1233         if pt
._type 
== CONTROL_POINT_ENDPOINT_FROM
: 
1235                 self
._canvas
.SetCursor(pt
._oldCursor
) 
1238                     self
.GetFrom().MoveLineToNewAttachment(dc
, self
, x
, y
) 
1240         if pt
._type 
== CONTROL_POINT_ENDPOINT_TO
: 
1242                 self
._canvas
.SetCursor(pt
._oldCursor
) 
1245                     self
.GetTo().MoveLineToNewAttachment(dc
, self
, x
, y
) 
1247     # This is called only when a non-end control point is moved 
1248     def OnMoveMiddleControlPoint(self
, dc
, lpt
, pt
): 
1252         lpt
._point
[0] = pt
[0] 
1253         lpt
._point
[1] = pt
[1] 
1255         self
.GetEventHandler().OnMoveLink(dc
) 
1259     def AddArrow(self
, type, end 
= ARROW_POSITION_END
, size 
= 10.0, xOffset 
= 0.0, name
="",mf 
= None, arrowId
=-1): 
1260         """Add an arrow (or annotation) to the line. 
1262         type may currently be one of: 
1269           Conventional arrowhead.  
1270         ARROW_SINGLE_OBLIQUE 
1271           Single oblique stroke.  
1272         ARROW_DOUBLE_OBLIQUE 
1273           Double oblique stroke.  
1274         ARROW_DOUBLE_METAFILE 
1277         end may currently be one of: 
1280           Arrow appears at the end.  
1281         ARROW_POSITION_START 
1282           Arrow appears at the start.  
1284         arrowSize specifies the length of the arrow. 
1286         xOffset specifies the offset from the end of the line. 
1288         name specifies a name for the arrow. 
1290         mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows 
1293         arrowId is the id for the arrow. 
1295         arrow 
= ArrowHead(type, end
, size
, xOffset
, name
, mf
, arrowId
) 
1296         self
._arcArrows
.append(arrow
) 
1299     # Add arrowhead at a particular position in the arrowhead list 
1300     def AddArrowOrdered(self
, arrow
, referenceList
, end
): 
1301         """Add an arrowhead in the position indicated by the reference list 
1302         of arrowheads, which contains all legal arrowheads for this line, in 
1303         the correct order. E.g. 
1305         Reference list:      a b c d e 
1306         Current line list:   a d 
1308         Add c, then line list is: a c d. 
1310         If no legal arrowhead position, return FALSE. Assume reference list 
1311         is for one end only, since it potentially defines the ordering for 
1312         any one of the 3 positions. So we don't check the reference list for 
1315         if not referenceList
: 
1318         targetName 
= arrow
.GetName() 
1320         # First check whether we need to insert in front of list, 
1321         # because this arrowhead is the first in the reference 
1322         # list and should therefore be first in the current list. 
1323         refArrow 
= referenceList
[0] 
1324         if refArrow
.GetName() == targetName
: 
1325             self
._arcArrows
.insert(0, arrow
) 
1329         while i1
<len(referenceList
) and i2
<len(self
._arcArrows
): 
1330             refArrow 
= referenceList
[i1
] 
1331             currArrow 
= self
._arcArrows
[i2
] 
1333             # Matching: advance current arrow pointer 
1334             if currArrow
.GetArrowEnd() == end 
and currArrow
.GetName() == refArrow
.GetName(): 
1337             # Check if we're at the correct position in the 
1339             if targetName 
== refArrow
.GetName(): 
1340                 if i2
<len(self
._arcArrows
): 
1341                     self
._arcArrows
.insert(i2
, arrow
) 
1343                     self
._arcArrows
.append(arrow
) 
1347         self
._arcArrows
.append(arrow
) 
1350     def ClearArrowsAtPosition(self
, end
): 
1351         """Delete the arrows at the specified position, or at any position 
1355             self
._arcArrows 
= [] 
1358         for arrow 
in self
._arcArrows
: 
1359             if arrow
.GetArrowEnd() == end
: 
1360                 self
._arcArrows
.remove(arrow
) 
1362     def ClearArrow(self
, name
): 
1363         """Delete the arrow with the given name.""" 
1364         for arrow 
in self
._arcArrows
: 
1365             if arrow
.GetName() == name
: 
1366                 self
._arcArrows
.remove(arrow
) 
1370     def FindArrowHead(self
, position
, name
): 
1371         """Find arrowhead by position and name. 
1373         if position is -1, matches any position. 
1375         for arrow 
in self
._arcArrows
: 
1376             if (position
==-1 or position 
== arrow
.GetArrowEnd()) and arrow
.GetName() == name
: 
1381     def FindArrowHeadId(self
, arrowId
): 
1382         """Find arrowhead by id.""" 
1383         for arrow 
in self
._arcArrows
: 
1384             if arrowId 
== arrow
.GetId(): 
1389     def DeleteArrowHead(self
, position
, name
): 
1390         """Delete arrowhead by position and name. 
1392         if position is -1, matches any position. 
1394         for arrow 
in self
._arcArrows
: 
1395             if (position
==-1 or position 
== arrow
.GetArrowEnd()) and arrow
.GetName() == name
: 
1396                 self
._arcArrows
.remove(arrow
) 
1400     def DeleteArrowHeadId(self
, id): 
1401         """Delete arrowhead by id.""" 
1402         for arrow 
in self
._arcArrows
: 
1403             if arrowId 
== arrow
.GetId(): 
1404                 self
._arcArrows
.remove(arrow
) 
1408     # Calculate the minimum width a line 
1409     # occupies, for the purposes of drawing lines in tools. 
1410     def FindMinimumWidth(self
): 
1411         """Find the horizontal width for drawing a line with arrows in 
1412         minimum space. Assume arrows at end only. 
1415         for arrowHead 
in self
._arcArrows
: 
1416             minWidth 
+= arrowHead
.GetSize() 
1417             if arrowHead 
!= self
._arcArrows
[-1]: 
1418                 minWidth 
+= arrowHead 
+ GetSpacing
 
1420         # We have ABSOLUTE minimum now. So 
1421         # scale it to give it reasonable aesthetics 
1422         # when drawing with line. 
1424             minWidth 
= minWidth 
* 1.4 
1428         self
.SetEnds(0.0, 0.0, minWidth
, 0.0) 
1433     def FindLinePosition(self
, x
, y
): 
1434         """Find which position we're talking about at this x, y. 
1436         Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END. 
1438         startX
, startY
, endX
, endY 
= self
.GetEnds() 
1440         # Find distances from centre, start and end. The smallest wins 
1441         centreDistance 
= sqrt((x
-self
._xpos
) * (x
-self
._xpos
) + (y
-self
._ypos
) * (y
-self
._ypos
)) 
1442         startDistance 
= sqrt((x
-startX
) * (x
-startX
) + (y
-startY
) * (y
-startY
)) 
1443         endDistance 
= sqrt((x
-endX
) * (x
-endX
) + (y
-endY
) * (y
-endY
)) 
1445         if centreDistance
<startDistance 
and centreDistance
<endDistance
: 
1446             return ARROW_POSITION_MIDDLE
 
1447         elif startDistance
<endDistance
: 
1448             return ARROW_POSITION_START
 
1450             return ARROW_POSITION_END
 
1452     def SetAlignmentOrientation(self
, isEnd
, isHoriz
): 
1454             if isHoriz 
and self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
!= LINE_ALIGNMENT_HORIZ
: 
1455                 self
._alignmentEnd 
!= LINE_ALIGNMENT_HORIZ
 
1456             elif not isHoriz 
and self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
: 
1457                 self
._alignmentEnd 
-= LINE_ALIGNMENT_HORIZ
 
1459             if isHoriz 
and self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
!= LINE_ALIGNMENT_HORIZ
: 
1460                 self
._alignmentStart 
!= LINE_ALIGNMENT_HORIZ
 
1461             elif not isHoriz 
and self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
: 
1462                 self
._alignmentStart 
-= LINE_ALIGNMENT_HORIZ
 
1464     def SetAlignmentType(self
, isEnd
, alignType
): 
1466             if alignType 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1467                 if self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
!= LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1468                     self
._alignmentEnd |
= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1469             elif self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1470                 self
._alignmentEnd 
-= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1472             if alignType 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1473                 if self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
!= LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1474                     self
._alignmentStart |
= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1475             elif self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1476                 self
._alignmentStart 
-= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1478     def GetAlignmentOrientation(self
, isEnd
): 
1480             return self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
 
1482             return self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
 
1484     def GetAlignmentType(self
, isEnd
): 
1486             return self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1488             return self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1490     def GetNextControlPoint(self
, shape
): 
1491         """Find the next control point in the line after the start / end point, 
1492         depending on whether the shape is at the start or end. 
1494         n 
= len(self
._lineControlPoints
) 
1495         if self
._to 
== shape
: 
1496             # Must be END of line, so we want (n - 1)th control point. 
1497             # But indexing ends at n-1, so subtract 2. 
1501         if nn
<len(self
._lineControlPoints
): 
1502             return self
._lineControlPoints
[nn
] 
1505     def OnCreateLabelShape(self
, parent
, region
, w
, h
): 
1506         return LabelShape(parent
, region
, w
, h
) 
1509     def OnLabelMovePre(self
, dc
, labelShape
, x
, y
, old_x
, old_y
, display
): 
1510         labelShape
._shapeRegion
.SetSize(labelShape
.GetWidth(), labelShape
.GetHeight()) 
1512         # Find position in line's region list 
1514         for region 
in self
.GetRegions(): 
1515             if labelShape
._shapeRegion 
== region
: 
1516                 self
.GetRegions().remove(region
) 
1520         xx
, yy 
= self
.GetLabelPosition(i
) 
1521         # Set the region's offset, relative to the default position for 
1523         labelShape
._shapeRegion
.SetPosition(x
-xx
, y
-yy
) 
1527         # Need to reformat to fit region 
1528         if labelShape
._shapeRegion
.GetText(): 
1529             s 
= labelShape
._shapeRegion
.GetText() 
1530             labelShape
.FormatText(dc
, s
, i
) 
1531             self
.DrawRegion(dc
, labelShape
._shapeRegion
, xx
, yy
)