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
 
  19 from _basic 
import Shape
, ShapeRegion
, ControlPoint
, RectangleShape
 
  20 from _oglmisc 
import * 
  22 # Line alignment flags 
  24 LINE_ALIGNMENT_HORIZ
=              1 
  25 LINE_ALIGNMENT_VERT
=               0 
  26 LINE_ALIGNMENT_TO_NEXT_HANDLE
=     2 
  27 LINE_ALIGNMENT_NONE
=               0 
  31 class LineControlPoint(ControlPoint
): 
  32     def __init__(self
, theCanvas 
= None, object = None, size 
= 0.0, x 
= 0.0, y 
= 0.0, the_type 
= 0): 
  33         ControlPoint
.__init
__(self
, theCanvas
, object, size
, x
, y
, the_type
) 
  38         self
._originalPos 
= None 
  41         RectangleShape
.OnDraw(self
, dc
) 
  43     # Implement movement of Line point 
  44     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
  45         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
  47     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
  48         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
  50     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
  51         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
  55 class ArrowHead(object): 
  56     def __init__(self
, type = 0, end 
= 0, size 
= 0.0, dist 
= 0.0, name
="",mf 
= None, arrowId
=-1): 
  57         if isinstance(type, ArrowHead
): 
  60             self
._arrowType 
= type 
  62             self
._arrowSize 
= size
 
  67             self
._arrowName 
= name
 
  74         return self
._arrowType
 
  76     def GetPosition(self
): 
  79     def SetPosition(self
, pos
): 
  92         return self
._arrowSize
 
  94     def SetSize(self
, size
): 
  95         self
._arrowSize 
= size
 
  96         if self
._arrowType 
== ARROW_METAFILE 
and self
._metaFile
: 
  97             oldWidth 
= self
._metaFile
._width
 
 101             scale 
= size 
/ oldWidth
 
 103                 self
._metaFile
.Scale(scale
, scale
) 
 106         return self
._arrowName
 
 108     def SetXOffset(self
, x
): 
 111     def SetYOffset(self
, y
): 
 114     def GetMetaFile(self
): 
 115         return self
._metaFile
 
 120     def GetArrowEnd(self
): 
 121         return self
._arrowEnd
 
 123     def GetArrowSize(self
): 
 124         return self
._arrowSize
 
 126     def SetSpacing(self
, sp
): 
 131 class LabelShape(RectangleShape
): 
 132     def __init__(self
, parent
, region
, w
, h
): 
 133         RectangleShape
.__init
__(self
, w
, h
) 
 134         self
._lineShape 
= parent
 
 135         self
._shapeRegion 
= region
 
 136         self
.SetPen(wx
.ThePenList
.FindOrCreatePen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)) 
 138     def OnDraw(self
, dc
): 
 139         if self
._lineShape 
and not self
._lineShape
.GetDrawHandles(): 
 142         x1 
= self
._xpos
-self
._width 
/ 2 
 143         y1 
= self
._ypos
-self
._height 
/ 2 
 146             if self
._pen
.GetWidth() == 0: 
 147                 dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
)) 
 150         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 152         if self
._cornerRadius
>0: 
 153             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
 155             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
 157     def OnDrawContents(self
, dc
): 
 160     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 161         RectangleShape
.OnDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
 163     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 164         RectangleShape
.OnBeginDragLeft(self
, x
, y
, keys
, attachment
) 
 166     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 167         RectangleShape
.OnEndDragLeft(self
, x
, y
, keys
, attachment
) 
 169     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display
): 
 170         return self
._lineShape
.OnLabelMovePre(dc
, self
, x
, y
, old_x
, old_y
, display
) 
 172     # Divert left and right clicks to line object 
 173     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 174         self
._lineShape
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
 176     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 177         self
._lineShape
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
 181 class LineShape(Shape
): 
 182     """LineShape may be attached to two nodes; 
 183     it may be segmented, in which case a control point is drawn for each joint. 
 185     A wxLineShape may have arrows at the beginning, end and centre. 
 193         self
._sensitivity 
= OP_CLICK_LEFT | OP_CLICK_RIGHT
 
 194         self
._draggable 
= False 
 195         self
._attachmentTo 
= 0 
 196         self
._attachmentFrom 
= 0 
 199         self
._erasing 
= False 
 200         self
._arrowSpacing 
= 5.0 
 201         self
._ignoreArrowOffsets 
= False 
 202         self
._isSpline 
= False 
 203         self
._maintainStraightLines 
= False 
 204         self
._alignmentStart 
= 0 
 205         self
._alignmentEnd 
= 0 
 207         self
._lineControlPoints 
= None 
 209         # Clear any existing regions (created in an earlier constructor) 
 210         # and make the three line regions. 
 212         for name 
in ["Middle","Start","End"]: 
 213             newRegion 
= ShapeRegion() 
 214             newRegion
.SetName(name
) 
 215             newRegion
.SetSize(150, 50) 
 216             self
._regions
.append(newRegion
) 
 218         self
._labelObjects 
= [None, None, None] 
 219         self
._lineOrientations 
= [] 
 220         self
._lineControlPoints 
= [] 
 224         if self
._lineControlPoints
: 
 225             self
.ClearPointList(self
._lineControlPoints
) 
 226             self
._lineControlPoints 
= [] 
 228             if self
._labelObjects
[i
]: 
 229                 self
._labelObjects
[i
].Select(False) 
 230                 self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
 231         self
._labelObjects 
= [] 
 232         self
.ClearArrowsAtPosition(-1) 
 235         """Return the 'from' object.""" 
 239         """Return the 'to' object.""" 
 242     def GetAttachmentFrom(self
): 
 243         """Return the attachment point on the 'from' node.""" 
 244         return self
._attachmentFrom
 
 246     def GetAttachmentTo(self
): 
 247         """Return the attachment point on the 'to' node.""" 
 248         return self
._attachmentTo
 
 250     def GetLineControlPoints(self
): 
 251         return self
._lineControlPoints
 
 253     def SetSpline(self
, spline
): 
 254         """Specifies whether a spline is to be drawn through the control points.""" 
 255         self
._isSpline 
= spline
 
 258         """TRUE if a spline is drawn through the control points.""" 
 259         return self
._isSpline
 
 261     def SetAttachmentFrom(self
, attach
): 
 262         """Set the 'from' shape attachment.""" 
 263         self
._attachmentFrom 
= attach
 
 265     def SetAttachmentTo(self
, attach
): 
 266         """Set the 'to' shape attachment.""" 
 267         self
._attachmentTo 
= attach
 
 269     # This is really to distinguish between lines and other images. 
 270     # For lines, want to pass drag to canvas, since lines tend to prevent 
 271     # dragging on a canvas (they get in the way.) 
 275     def SetIgnoreOffsets(self
, ignore
): 
 276         """Set whether to ignore offsets from the end of the line when drawing.""" 
 277         self
._ignoreArrowOffsets 
= ignore
 
 280         return self
._arcArrows
 
 282     def GetAlignmentStart(self
): 
 283         return self
._alignmentStart
 
 285     def GetAlignmentEnd(self
): 
 286         return self
._alignmentEnd
 
 288     def IsEnd(self
, nodeObject
): 
 289         """TRUE if shape is at the end of the line.""" 
 290         return self
._to 
== nodeObject
 
 292     def MakeLineControlPoints(self
, n
): 
 293         """Make a given number of control points (minimum of two).""" 
 294         if self
._lineControlPoints
: 
 295             self
.ClearPointList(self
._lineControlPoints
) 
 296         self
._lineControlPoints 
= [] 
 299             point 
= wx
.RealPoint(-999,-999) 
 300             self
._lineControlPoints
.append(point
) 
 302     def InsertLineControlPoint(self
, dc 
= None): 
 303         """Insert a control point at an arbitrary position.""" 
 307         last_point 
= self
._lineControlPoints
[-1] 
 308         second_last_point 
= self
._lineControlPoints
[-2] 
 310         line_x 
= (last_point
[0] + second_last_point
[0]) / 2 
 311         line_y 
= (last_point
[1] + second_last_point
[1]) / 2 
 313         point 
= wx
.RealPoint(line_x
, line_y
) 
 314         self
._lineControlPoints
.insert(len(self
._lineControlPoints
), point
) 
 316     def DeleteLineControlPoint(self
): 
 317         """Delete an arbitary point on the line.""" 
 318         if len(self
._lineControlPoints
)<3: 
 321         del self
._lineControlPoints
[-2] 
 324     def Initialise(self
): 
 325         """Initialise the line object.""" 
 326         if self
._lineControlPoints
: 
 327             # Just move the first and last control points 
 328             first_point 
= self
._lineControlPoints
[0] 
 329             last_point 
= self
._lineControlPoints
[-1] 
 331             # If any of the line points are at -999, we must 
 332             # initialize them by placing them half way between the first 
 335             for point 
in self
._lineControlPoints
[1:]: 
 337                     if first_point
[0]<last_point
[0]: 
 343                     if first_point
[1]<last_point
[1]: 
 349                     point
[0] = (x2
-x1
) / 2 + x1
 
 350                     point
[1] = (y2
-y1
) / 2 + y1
 
 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, yp
-h 
/ 2, 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, yp
-h 
/ 2, 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) 
 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, point
[1] + dy 
/ 2 
 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.""" 
 496         first_point 
= self
._lineControlPoints
[0] 
 497         last_point 
= self
._lineControlPoints
[-1] 
 504         self
._xpos 
= (x1 
+ x2
) / 2 
 505         self
._ypos 
= (y1 
+ y2
) / 2 
 507     # Get absolute positions of ends 
 509         """Get the visible endpoints of the lines for drawing between two objects.""" 
 510         first_point 
= self
._lineControlPoints
[0] 
 511         last_point 
= self
._lineControlPoints
[-1] 
 513         return (first_point
[0], first_point
[1]), (last_point
[0], last_point
[1]) 
 515     def SetAttachments(self
, from_attach
, to_attach
): 
 516         """Specify which object attachment points should be used at each end 
 519         self
._attachmentFrom 
= from_attach
 
 520         self
._attachmentTo 
= to_attach
 
 522     def HitTest(self
, x
, y
): 
 523         if not self
._lineControlPoints
: 
 526         # Look at label regions in case mouse is over a label 
 527         inLabelRegion 
= False 
 530                 region 
= self
._regions
[i
] 
 531                 if len(region
._formattedText
): 
 532                     xp
, yp 
= self
.GetLabelPosition(i
) 
 533                     # Offset region from default label position 
 534                     cx
, cy 
= region
.GetPosition() 
 535                     cw
, ch 
= region
.GetSize() 
 542                     rBottom 
= cy 
+ ch 
/ 2 
 543                     if x
>rLeft 
and x
<rRight 
and y
>rTop 
and y
<rBottom
: 
 547         for i 
in range(len(self
._lineControlPoints
)-1): 
 548             point1 
= self
._lineControlPoints
[i
] 
 549             point2 
= self
._lineControlPoints
[i 
+ 1] 
 551             # For inaccurate mousing allow 8 pixel corridor 
 554             dx 
= point2
[0]-point1
[0] 
 555             dy 
= point2
[1]-point1
[1] 
 557             seg_len 
= math
.sqrt(dx 
* dx 
+ dy 
* dy
) 
 558             if dy 
== 0 or dx 
== 0: 
 560             distance_from_seg 
= seg_len 
* ((x
-point1
[0]) * dy
-(y
-point1
[1]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 561             distance_from_prev 
= seg_len 
* ((y
-point1
[1]) * dy 
+ (x
-point1
[0]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 563             if abs(distance_from_seg
)<extra 
and distance_from_prev 
>= 0 and distance_from_prev 
<= seg_len 
or inLabelRegion
: 
 564                 return 0, distance_from_seg
 
 568     def DrawArrows(self
, dc
): 
 569         """Draw all arrows.""" 
 570         # Distance along line of each arrow: space them out evenly 
 575         for arrow 
in self
._arcArrows
: 
 576             ah 
= arrow
.GetArrowEnd() 
 577             if ah 
== ARROW_POSITION_START
: 
 578                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 579                     # If specified, x offset is proportional to line length 
 580                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 582                     self
.DrawArrow(dc
, arrow
, startArrowPos
, False) 
 583                     startArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 584             elif ah 
== ARROW_POSITION_END
: 
 585                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 586                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 588                     self
.DrawArrow(dc
, arrow
, endArrowPos
, False) 
 589                     endArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 590             elif ah 
== ARROW_POSITION_MIDDLE
: 
 591                 arrow
.SetXOffset(middleArrowPos
) 
 592                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 593                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 595                     self
.DrawArrow(dc
, arrow
, middleArrowPos
, False) 
 596                     middleArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 598     def DrawArrow(self
, dc
, arrow
, XOffset
, proportionalOffset
): 
 599         """Draw the given arrowhead (or annotation).""" 
 600         first_line_point 
= self
._lineControlPoints
[0] 
 601         second_line_point 
= self
._lineControlPoints
[1] 
 603         last_line_point 
= self
._lineControlPoints
[-1] 
 604         second_last_line_point 
= self
._lineControlPoints
[-2] 
 606         # Position of start point of line, at the end of which we draw the arrow 
 607         startPositionX
, startPositionY 
= 0.0, 0.0 
 609         ap 
= arrow
.GetPosition() 
 610         if ap 
== ARROW_POSITION_START
: 
 611             # If we're using a proportional offset, calculate just where this 
 612             # will be on the line. 
 614             if proportionalOffset
: 
 615                 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])) 
 616                 realOffset 
= XOffset 
* totalLength
 
 618             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_line_point
[0], second_line_point
[1], first_line_point
[0], first_line_point
[1], realOffset
) 
 620             startPositionX 
= second_line_point
[0] 
 621             startPositionY 
= second_line_point
[1] 
 622         elif ap 
== ARROW_POSITION_END
: 
 623             # If we're using a proportional offset, calculate just where this 
 624             # will be on the line. 
 626             if proportionalOffset
: 
 627                 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])); 
 628                 realOffset 
= XOffset 
* totalLength
 
 630             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], last_line_point
[0], last_line_point
[1], realOffset
) 
 632             startPositionX 
= second_last_line_point
[0] 
 633             startPositionY 
= second_last_line_point
[1] 
 634         elif ap 
== ARROW_POSITION_MIDDLE
: 
 635             # Choose a point half way between the last and penultimate points 
 636             x 
= (last_line_point
[0] + second_last_line_point
[0]) / 2 
 637             y 
= (last_line_point
[1] + second_last_line_point
[1]) / 2 
 639             # If we're using a proportional offset, calculate just where this 
 640             # will be on the line. 
 642             if proportionalOffset
: 
 643                 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
)); 
 644                 realOffset 
= XOffset 
* totalLength
 
 646             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], x
, y
, realOffset
) 
 647             startPositionX 
= second_last_line_point
[0] 
 648             startPositionY 
= second_last_line_point
[1] 
 650         # Add yOffset to arrow, if any 
 652         # The translation that the y offset may give 
 655         if arrow
.GetYOffset 
and not self
._ignoreArrowOffsets
: 
 659             #   (x1, y1)--------------(x3, y3)------------------(x2, y2) 
 660             #   x4 = x3 - d * math.sin(theta) 
 661             #   y4 = y3 + d * math.cos(theta) 
 663             #   Where theta = math.tan(-1) of (y3-y1) / (x3-x1) 
 668             d
=-arrow
.GetYOffset() # Negate so +offset is above line 
 673                 theta 
= math
.atan((y3
-y1
) / (x3
-x1
)) 
 675             x4 
= x3
-d 
* math
.sin(theta
) 
 676             y4 
= y3 
+ d 
* math
.cos(theta
) 
 678             deltaX 
= x4
-positionOnLineX
 
 679             deltaY 
= y4
-positionOnLineY
 
 681         at 
= arrow
._GetType
() 
 682         if at 
== ARROW_ARROW
: 
 683             arrowLength 
= arrow
.GetSize() 
 684             arrowWidth 
= arrowLength 
/ 3 
 686             tip_x
, tip_y
, side1_x
, side1_y
, side2_x
, side2_y 
= GetArrowPoints(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, arrowLength
, arrowWidth
) 
 688             points 
= [[tip_x
, tip_y
], 
 694             dc
.SetBrush(self
._brush
) 
 695             dc
.DrawPolygon(points
) 
 696         elif at 
in [ARROW_HOLLOW_CIRCLE
, ARROW_FILLED_CIRCLE
]: 
 697             # Find point on line of centre of circle, which is a radius away 
 698             # from the end position 
 699             diameter 
= arrow
.GetSize() 
 700             x
, y 
= GetPointOnLine(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, 
 701                                positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, 
 706             if arrow
._GetType
() == ARROW_HOLLOW_CIRCLE
: 
 707                 dc
.SetBrush(self
.GetBackgroundBrush()) 
 709                 dc
.SetBrush(self
._brush
) 
 711             dc
.DrawEllipse(x1
, y1
, diameter
, diameter
) 
 712         elif at 
== ARROW_SINGLE_OBLIQUE
: 
 714         elif at 
== ARROW_METAFILE
: 
 715             if arrow
.GetMetaFile(): 
 716                 # Find point on line of centre of object, which is a half-width away 
 717                 # from the end position 
 720                 #  <-- start pos  <-----><-- positionOnLineX 
 722                 #  --------------|  x  | <-- e.g. rectangular arrowhead 
 725                 x
, y 
= GetPointOnLine(startPositionX
, startPositionY
, 
 726                                    positionOnLineX
, positionOnLineY
, 
 727                                    arrow
.GetMetaFile()._width 
/ 2) 
 728                 # Calculate theta for rotating the metafile. 
 731                 # |     o(x2, y2)   'o' represents the arrowhead. 
 736                 # |______________________ 
 744                 if x1 
== x2 
and y1 
== y2
: 
 746                 elif x1 
== x2 
and y1
>y2
: 
 747                     theta 
= 3.0 * math
.pi 
/ 2 
 748                 elif x1 
== x2 
and y2
>y1
: 
 750                 elif x2
>x1 
and y2 
>= y1
: 
 751                     theta 
= math
.atan((y2
-y1
) / (x2
-x1
)) 
 753                     theta 
= math
.pi 
+ math
.atan((y2
-y1
) / (x2
-x1
)) 
 754                 elif x2
>x1 
and y2
<y1
: 
 755                     theta 
= 2 * math
.pi 
+ math
.atan((y2
-y1
) / (x2
-x1
)) 
 757                     raise "Unknown arrowhead rotation case" 
 759                 # Rotate about the centre of the object, then place 
 760                 # the object on the line. 
 761                 if arrow
.GetMetaFile().GetRotateable(): 
 762                     arrow
.GetMetaFile().Rotate(0.0, 0.0, theta
) 
 765                     # If erasing, just draw a rectangle 
 766                     minX
, minY
, maxX
, maxY 
= arrow
.GetMetaFile().GetBounds() 
 767                     # Make erasing rectangle slightly bigger or you get droppings 
 769                     dc
.DrawRectangle(deltaX 
+ x 
+ minX
-extraPixels 
/ 2, deltaY 
+ y 
+ minY
-extraPixels 
/ 2, maxX
-minX 
+ extraPixels
, maxY
-minY 
+ extraPixels
) 
 771                     arrow
.GetMetaFile().Draw(dc
, x 
+ deltaX
, y 
+ deltaY
) 
 773     def OnErase(self
, dc
): 
 775         old_brush 
= self
._brush
 
 777         bg_pen 
= self
.GetBackgroundPen() 
 778         bg_brush 
= self
.GetBackgroundBrush() 
 780         self
.SetBrush(bg_brush
) 
 782         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
 784             dc
.SetFont(self
._font
) 
 786         # Undraw text regions 
 789                 x
, y 
= self
.GetLabelPosition(i
) 
 790                 self
.EraseRegion(dc
, self
._regions
[i
], x
, y
) 
 793         dc
.SetPen(self
.GetBackgroundPen()) 
 794         dc
.SetBrush(self
.GetBackgroundBrush()) 
 796         # Drawing over the line only seems to work if the line has a thickness 
 798         if old_pen 
and old_pen
.GetWidth()>1: 
 799             dc
.DrawRectangle(self
._xpos
-bound_x 
/ 2-2, self
._ypos
-bound_y 
/ 2-2, 
 800                              bound_x 
+ 4, bound_y 
+ 4) 
 803             self
.GetEventHandler().OnDraw(dc
) 
 804             self
.GetEventHandler().OnEraseControlPoints(dc
) 
 805             self
._erasing 
= False 
 810             self
.SetBrush(old_brush
) 
 812     def GetBoundingBoxMin(self
): 
 813         x1
, y1 
= 10000, 10000 
 816         for point 
in self
._lineControlPoints
: 
 828     # For a node image of interest, finds the position of this arc 
 829     # amongst all the arcs which are attached to THIS SIDE of the node image, 
 830     # and the number of same. 
 831     def FindNth(self
, image
, incoming
): 
 832         """Find the position of the line on the given object. 
 834         Specify whether incoming or outgoing lines are being considered 
 840         if image 
== self
._to
: 
 841             this_attachment 
= self
._attachmentTo
 
 843             this_attachment 
= self
._attachmentFrom
 
 845         # Find number of lines going into / out of this particular attachment point 
 846         for line 
in image
.GetLines(): 
 847             if line
._from 
== image
: 
 848                 # This is the nth line attached to 'image' 
 849                 if line 
== self 
and not incoming
: 
 852                 # Increment num count if this is the same side (attachment number) 
 853                 if line
._attachmentFrom 
== this_attachment
: 
 856             if line
._to 
== image
: 
 857                 # This is the nth line attached to 'image' 
 858                 if line 
== self 
and incoming
: 
 861                 # Increment num count if this is the same side (attachment number) 
 862                 if line
._attachmentTo 
== this_attachment
: 
 867     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 869         old_brush 
= self
._brush
 
 871         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
 872         self
.SetPen(dottedPen
) 
 873         self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 875         self
.GetEventHandler().OnDraw(dc
) 
 882             self
.SetBrush(old_brush
) 
 886     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 890         if self
._lineControlPoints 
and not (x_offset 
== 0 and y_offset 
== 0): 
 891             for point 
in self
._lineControlPoints
: 
 895         # Move temporary label rectangles if necessary 
 897             if self
._labelObjects
[i
]: 
 898                 self
._labelObjects
[i
].Erase(dc
) 
 899                 xp
, yp 
= self
.GetLabelPosition(i
) 
 900                 if i
<len(self
._regions
): 
 901                     xr
, yr 
= self
._regions
[i
].GetPosition() 
 904                 self
._labelObjects
[i
].Move(dc
, xp 
+ xr
, yp 
+ yr
) 
 907     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
 908         """Called when a connected object has moved, to move the link to 
 911         if not self
._from 
or not self
._to
: 
 914         if len(self
._lineControlPoints
)>2: 
 917         # Do each end - nothing in the middle. User has to move other points 
 918         # manually if necessary 
 919         end_x
, end_y
, other_end_x
, other_end_y 
= self
.FindLineEndPoints() 
 921         first 
= self
._lineControlPoints
[0] 
 922         last 
= self
._lineControlPoints
[-1] 
 924         oldX
, oldY 
= self
._xpos
, self
._ypos
 
 926         self
.SetEnds(end_x
, end_y
, other_end_x
, other_end_y
) 
 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         if len(self
._lineControlPoints
)>2: 
 961             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 962                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) # Not incoming 
 963                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 965                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), second_point
[0], second_point
[1]) 
 967             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 968                 nth
, no_arch 
= self
.FindNth(self
._to
, True) # Incoming 
 969                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arch
, self
) 
 971                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), second_last_point
[0], second_last_point
[1]) 
 973             fromX 
= self
._from
.GetX() 
 974             fromY 
= self
._from
.GetY() 
 975             toX 
= self
._to
.GetX() 
 976             toY 
= self
._to
.GetY() 
 978             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 979                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) 
 980                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 984             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 985                 nth
, no_arcs 
= self
.FindNth(self
._to
, True) 
 986                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arcs
, self
) 
 990             if self
._from
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 991                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), toX
, toY
) 
 993             if self
._to
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 994                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), fromX
, fromY
) 
 996             #print type(self._from), type(self._to), end_x, end_y, other_end_x, other_end_y 
 997             return end_x
, end_y
, other_end_x
, other_end_y
 
 999     def OnDraw(self
, dc
): 
1000         if not self
._lineControlPoints
: 
1004             dc
.SetPen(self
._pen
) 
1006             dc
.SetBrush(self
._brush
) 
1009         for point 
in self
._lineControlPoints
: 
1010             points
.append(wx
.Point(point
.x
, point
.y
)) 
1014             dc
.DrawSpline(points
) 
1016             dc
.DrawLines(points
) 
1018         if sys
.platform
[:3]=="win": 
1019             # For some reason, last point isn't drawn under Windows 
1021             dc
.DrawPoint(pt
.x
, pt
.y
) 
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 
= math
.sqrt((x
-self
._xpos
) * (x
-self
._xpos
) + (y
-self
._ypos
) * (y
-self
._ypos
)) 
1442         startDistance 
= math
.sqrt((x
-startX
) * (x
-startX
) + (y
-startY
) * (y
-startY
)) 
1443         endDistance 
= math
.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
)