1 # -*- coding: iso-8859-1 -*- 
   2 #---------------------------------------------------------------------------- 
   4 # Purpose:      LineShape class 
   6 # Author:       Pierre Hjälm (from C++ original by Julian Smart) 
  10 # Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart 
  11 # Licence:      wxWindows license 
  12 #---------------------------------------------------------------------------- 
  17 from _basic 
import Shape
, ShapeRegion
, ShapeTextLine
, ControlPoint
, RectangleShape
 
  18 from _oglmisc 
import * 
  20 # Line alignment flags 
  22 LINE_ALIGNMENT_HORIZ 
=              1 
  23 LINE_ALIGNMENT_VERT 
=               0 
  24 LINE_ALIGNMENT_TO_NEXT_HANDLE 
=     2 
  25 LINE_ALIGNMENT_NONE 
=               0 
  29 class LineControlPoint(ControlPoint
): 
  30     def __init__(self
, theCanvas 
= None, object = None, size 
= 0.0, x 
= 0.0, y 
= 0.0, the_type 
= 0): 
  31         ControlPoint
.__init
__(self
, theCanvas
, object, size
, x
, y
, the_type
) 
  36         self
._originalPos 
= None 
  39         RectangleShape
.OnDraw(self
, dc
) 
  41     # Implement movement of Line point 
  42     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
  43         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
  45     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
  46         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
  48     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
  49         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
  53 class ArrowHead(object): 
  54     def __init__(self
, type = 0, end 
= 0, size 
= 0.0, dist 
= 0.0, name 
= "", mf 
= None, arrowId 
= -1): 
  55         if isinstance(type, ArrowHead
): 
  58             self
._arrowType 
= type 
  60             self
._arrowSize 
= size
 
  65             self
._arrowName 
= name
 
  72         return self
._arrowType
 
  74     def GetPosition(self
): 
  77     def SetPosition(self
, pos
): 
  90         return self
._arrowSize
 
  92     def SetSize(self
, size
): 
  93         self
._arrowSize 
= size
 
  94         if self
._arrowType 
== ARROW_METAFILE 
and self
._metaFile
: 
  95             oldWidth 
= self
._metaFile
._width
 
  99             scale 
= float(size
) / oldWidth
 
 101                 self
._metaFile
.Scale(scale
, scale
) 
 104         return self
._arrowName
 
 106     def SetXOffset(self
, x
): 
 109     def SetYOffset(self
, y
): 
 112     def GetMetaFile(self
): 
 113         return self
._metaFile
 
 118     def GetArrowEnd(self
): 
 119         return self
._arrowEnd
 
 121     def GetArrowSize(self
): 
 122         return self
._arrowSize
 
 124     def SetSpacing(self
, sp
): 
 129 class LabelShape(RectangleShape
): 
 130     def __init__(self
, parent
, region
, w
, h
): 
 131         RectangleShape
.__init
__(self
, w
, h
) 
 132         self
._lineShape 
= parent
 
 133         self
._shapeRegion 
= region
 
 134         self
.SetPen(wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
)) 
 136     def OnDraw(self
, dc
): 
 137         if self
._lineShape 
and not self
._lineShape
.GetDrawHandles(): 
 140         x1 
= self
._xpos 
- self
._width 
/ 2.0 
 141         y1 
= self
._ypos 
- self
._height 
/ 2.0 
 144             if self
._pen
.GetWidth() == 0: 
 145                 dc
.SetPen(wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
)) 
 148         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 150         if self
._cornerRadius 
> 0: 
 151             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
 153             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
 155     def OnDrawContents(self
, dc
): 
 158     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 159         RectangleShape
.OnDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
 161     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 162         RectangleShape
.OnBeginDragLeft(self
, x
, y
, keys
, attachment
) 
 164     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 165         RectangleShape
.OnEndDragLeft(self
, x
, y
, keys
, attachment
) 
 167     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display
): 
 168         return self
._lineShape
.OnLabelMovePre(dc
, self
, x
, y
, old_x
, old_y
, display
) 
 170     # Divert left and right clicks to line object 
 171     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 172         self
._lineShape
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
 174     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 175         self
._lineShape
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
 179 class LineShape(Shape
): 
 180     """LineShape may be attached to two nodes; 
 181     it may be segmented, in which case a control point is drawn for each joint. 
 183     A wxLineShape may have arrows at the beginning, end and centre. 
 191         self
._sensitivity 
= OP_CLICK_LEFT | OP_CLICK_RIGHT
 
 192         self
._draggable 
= False 
 193         self
._attachmentTo 
= 0 
 194         self
._attachmentFrom 
= 0 
 197         self
._erasing 
= False 
 198         self
._arrowSpacing 
= 5.0 
 199         self
._ignoreArrowOffsets 
= False 
 200         self
._isSpline 
= False 
 201         self
._maintainStraightLines 
= False 
 202         self
._alignmentStart 
= 0 
 203         self
._alignmentEnd 
= 0 
 205         self
._lineControlPoints 
= None 
 207         # Clear any existing regions (created in an earlier constructor) 
 208         # and make the three line regions. 
 210         for name 
in ["Middle","Start","End"]: 
 211             newRegion 
= ShapeRegion() 
 212             newRegion
.SetName(name
) 
 213             newRegion
.SetSize(150, 50) 
 214             self
._regions
.append(newRegion
) 
 216         self
._labelObjects 
= [None, None, None] 
 217         self
._lineOrientations 
= [] 
 218         self
._lineControlPoints 
= [] 
 222         if self
._lineControlPoints
: 
 223             self
._lineControlPoints 
= [] 
 225             if self
._labelObjects
[i
]: 
 226                 self
._labelObjects
[i
].Select(False) 
 227                 self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
 228         self
._labelObjects 
= [] 
 229         self
.ClearArrowsAtPosition(-1) 
 232         """Return the 'from' object.""" 
 236         """Return the 'to' object.""" 
 239     def GetAttachmentFrom(self
): 
 240         """Return the attachment point on the 'from' node.""" 
 241         return self
._attachmentFrom
 
 243     def GetAttachmentTo(self
): 
 244         """Return the attachment point on the 'to' node.""" 
 245         return self
._attachmentTo
 
 247     def GetLineControlPoints(self
): 
 248         return self
._lineControlPoints
 
 250     def SetSpline(self
, spline
): 
 251         """Specifies whether a spline is to be drawn through the control points.""" 
 252         self
._isSpline 
= spline
 
 255         """TRUE if a spline is drawn through the control points.""" 
 256         return self
._isSpline
 
 258     def SetAttachmentFrom(self
, attach
): 
 259         """Set the 'from' shape attachment.""" 
 260         self
._attachmentFrom 
= attach
 
 262     def SetAttachmentTo(self
, attach
): 
 263         """Set the 'to' shape attachment.""" 
 264         self
._attachmentTo 
= attach
 
 266     # This is really to distinguish between lines and other images. 
 267     # For lines, want to pass drag to canvas, since lines tend to prevent 
 268     # dragging on a canvas (they get in the way.) 
 272     def SetIgnoreOffsets(self
, ignore
): 
 273         """Set whether to ignore offsets from the end of the line when drawing.""" 
 274         self
._ignoreArrowOffsets 
= ignore
 
 277         return self
._arcArrows
 
 279     def GetAlignmentStart(self
): 
 280         return self
._alignmentStart
 
 282     def GetAlignmentEnd(self
): 
 283         return self
._alignmentEnd
 
 285     def IsEnd(self
, nodeObject
): 
 286         """TRUE if shape is at the end of the line.""" 
 287         return self
._to 
== nodeObject
 
 289     def MakeLineControlPoints(self
, n
): 
 290         """Make a given number of control points (minimum of two).""" 
 291         self
._lineControlPoints 
= [] 
 294             point 
= wx
.RealPoint(-999, -999) 
 295             self
._lineControlPoints
.append(point
) 
 297         # pi: added _initialised to keep track of when we have set 
 298         # the middle points to something other than (-999, -999) 
 299         self
._initialised 
= False 
 301     def InsertLineControlPoint(self
, dc 
= None, point 
= None): 
 302         """Insert a control point at an optional given position.""" 
 307             line_x
, line_y 
= point
 
 309             last_point 
= self
._lineControlPoints
[-1] 
 310             second_last_point 
= self
._lineControlPoints
[-2] 
 312             line_x 
= (last_point
[0] + second_last_point
[0]) / 2.0 
 313             line_y 
= (last_point
[1] + second_last_point
[1]) / 2.0 
 315         point 
= wx
.RealPoint(line_x
, line_y
) 
 316         self
._lineControlPoints
.insert(len(self
._lineControlPoints
)-1, point
) 
 318     def DeleteLineControlPoint(self
): 
 319         """Delete an arbitary point on the line.""" 
 320         if len(self
._lineControlPoints
) < 3: 
 323         del self
._lineControlPoints
[-2] 
 326     def Initialise(self
): 
 327         """Initialise the line object.""" 
 328         if self
._lineControlPoints
: 
 329             # Just move the first and last control points 
 330             first_point 
= self
._lineControlPoints
[0] 
 331             last_point 
= self
._lineControlPoints
[-1] 
 333             # If any of the line points are at -999, we must 
 334             # initialize them by placing them half way between the first 
 337             for i 
in range(1,len(self
._lineControlPoints
)): 
 338                 point 
= self
._lineControlPoints
[i
] 
 340                     if first_point
[0] < last_point
[0]: 
 346                     if first_point
[1] < last_point
[1]: 
 352                     self
._lineControlPoints
[i
] = wx
.RealPoint((x2 
- x1
) / 2.0 + x1
, (y2 
- y1
) / 2.0 + y1
) 
 353             self
._initialised 
= True 
 355     def FormatText(self
, dc
, s
, i
): 
 356         """Format a text string according to the region size, adding 
 357         strings with positions to region text list. 
 361         if len(self
._regions
) == 0 or i 
>= len(self
._regions
): 
 364         region 
= self
._regions
[i
] 
 366         dc
.SetFont(region
.GetFont()) 
 368         w
, h 
= region
.GetSize() 
 369         # Initialize the size if zero 
 370         if (w 
== 0 or h 
== 0) and s
: 
 374         string_list 
= FormatText(dc
, s
, w 
- 5, h 
- 5, region
.GetFormatMode()) 
 375         for s 
in string_list
: 
 376             line 
= ShapeTextLine(0.0, 0.0, s
) 
 377             region
.GetFormattedText().append(line
) 
 381         if region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
: 
 382             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w
, h
) 
 383             if actualW 
!= w 
or actualH 
!= h
: 
 384                 xx
, yy 
= self
.GetLabelPosition(i
) 
 385                 self
.EraseRegion(dc
, region
, xx
, yy
) 
 386                 if len(self
._labelObjects
) < i
: 
 387                     self
._labelObjects
[i
].Select(False, dc
) 
 388                     self
._labelObjects
[i
].Erase(dc
) 
 389                     self
._labelObjects
[i
].SetSize(actualW
, actualH
) 
 391                 region
.SetSize(actualW
, actualH
) 
 393                 if len(self
._labelObjects
) < i
: 
 394                     self
._labelObjects
[i
].Select(True, dc
) 
 395                     self
._labelObjects
[i
].Draw(dc
) 
 397         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW
, actualH
, region
.GetFormatMode()) 
 398         self
._formatted 
= True 
 400     def DrawRegion(self
, dc
, region
, x
, y
): 
 401         """Format one region at this position.""" 
 402         if self
.GetDisableLabel(): 
 405         w
, h 
= region
.GetSize() 
 407         # Get offset from x, y 
 408         xx
, yy 
= region
.GetPosition() 
 413         # First, clear a rectangle for the text IF there is any 
 414         if len(region
.GetFormattedText()): 
 415             dc
.SetPen(self
.GetBackgroundPen()) 
 416             dc
.SetBrush(self
.GetBackgroundBrush()) 
 420                 dc
.SetFont(region
.GetFont()) 
 421                 dc
.DrawRectangle(xp 
- w 
/ 2.0, yp 
- h 
/ 2.0, w
, h
) 
 425                 dc
.SetTextForeground(region
.GetActualColourObject()) 
 427                 DrawFormattedText(dc
, region
.GetFormattedText(), xp
, yp
, w
, h
, region
.GetFormatMode()) 
 429     def EraseRegion(self
, dc
, region
, x
, y
): 
 430         """Erase one region at this position.""" 
 431         if self
.GetDisableLabel(): 
 434         w
, h 
= region
.GetSize() 
 436         # Get offset from x, y 
 437         xx
, yy 
= region
.GetPosition() 
 442         if region
.GetFormattedText(): 
 443             dc
.SetPen(self
.GetBackgroundPen()) 
 444             dc
.SetBrush(self
.GetBackgroundBrush()) 
 446             dc
.DrawRectangle(xp 
- w 
/ 2.0, yp 
- h 
/ 2.0, w
, h
) 
 448     def GetLabelPosition(self
, position
): 
 449         """Get the reference point for a label. 
 451         Region x and y are offsets from this. 
 452         position is 0 (middle), 1 (start), 2 (end). 
 455             # Want to take the middle section for the label 
 456             half_way 
= int(len(self
._lineControlPoints
) / 2.0) 
 458             # Find middle of this line 
 459             point 
= self
._lineControlPoints
[half_way 
- 1] 
 460             next_point 
= self
._lineControlPoints
[half_way
] 
 462             dx 
= next_point
[0] - point
[0] 
 463             dy 
= next_point
[1] - point
[1] 
 465             return point
[0] + dx 
/ 2.0, point
[1] + dy 
/ 2.0 
 467             return self
._lineControlPoints
[0][0], self
._lineControlPoints
[0][1] 
 469             return self
._lineControlPoints
[-1][0], self
._lineControlPoints
[-1][1] 
 471     def Straighten(self
, dc 
= None): 
 472         """Straighten verticals and horizontals.""" 
 473         if len(self
._lineControlPoints
) < 3: 
 479         GraphicsStraightenLine(self
._lineControlPoints
[-1], self
._lineControlPoints
[-2]) 
 481         for i 
in range(len(self
._lineControlPoints
) - 2): 
 482             GraphicsStraightenLine(self
._lineControlPoints
[i
], self
._lineControlPoints
[i 
+ 1]) 
 488         """Unlink the line from the nodes at either end.""" 
 490             self
._to
.GetLines().remove(self
) 
 492             self
._from
.GetLines().remove(self
) 
 496     def SetEnds(self
, x1
, y1
, x2
, y2
): 
 497         """Set the end positions of the line.""" 
 498         self
._lineControlPoints
[0] = wx
.RealPoint(x1
, y1
) 
 499         self
._lineControlPoints
[-1] = wx
.RealPoint(x2
, y2
) 
 502         self
._xpos 
= (x1 
+ x2
) / 2.0 
 503         self
._ypos 
= (y1 
+ y2
) / 2.0 
 505     # Get absolute positions of ends 
 507         """Get the visible endpoints of the lines for drawing between two objects.""" 
 508         first_point 
= self
._lineControlPoints
[0] 
 509         last_point 
= self
._lineControlPoints
[-1] 
 511         return first_point
[0], first_point
[1], last_point
[0], last_point
[1] 
 513     def SetAttachments(self
, from_attach
, to_attach
): 
 514         """Specify which object attachment points should be used at each end 
 517         self
._attachmentFrom 
= from_attach
 
 518         self
._attachmentTo 
= to_attach
 
 520     def HitTest(self
, x
, y
): 
 521         if not self
._lineControlPoints
: 
 524         # Look at label regions in case mouse is over a label 
 525         inLabelRegion 
= False 
 528                 region 
= self
._regions
[i
] 
 529                 if len(region
._formattedText
): 
 530                     xp
, yp 
= self
.GetLabelPosition(i
) 
 531                     # Offset region from default label position 
 532                     cx
, cy 
= region
.GetPosition() 
 533                     cw
, ch 
= region
.GetSize()  
 537                     rLeft 
= cx 
- cw 
/ 2.0 
 539                     rRight 
= cx 
+ cw 
/ 2.0 
 540                     rBottom 
= cy 
+ ch 
/ 2.0 
 541                     if x 
> rLeft 
and x 
< rRight 
and y 
> rTop 
and y 
< rBottom
: 
 545         for i 
in range(len(self
._lineControlPoints
) - 1): 
 546             point1 
= self
._lineControlPoints
[i
] 
 547             point2 
= self
._lineControlPoints
[i 
+ 1] 
 549             # For inaccurate mousing allow 8 pixel corridor 
 552             dx 
= point2
[0] - point1
[0] 
 553             dy 
= point2
[1] - point1
[1] 
 555             seg_len 
= math
.sqrt(dx 
* dx 
+ dy 
* dy
) 
 556             if dy 
== 0 and dx 
== 0: 
 558             distance_from_seg 
= seg_len 
* float((x 
- point1
[0]) * dy 
- (y 
- point1
[1]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 559             distance_from_prev 
= seg_len 
* float((y 
- point1
[1]) * dy 
+ (x 
- point1
[0]) * dx
) / (dy 
* dy 
+ dx 
* dx
) 
 561             if abs(distance_from_seg
) < extra 
and distance_from_prev 
>= 0 and distance_from_prev 
<= seg_len 
or inLabelRegion
: 
 562                 return 0, distance_from_seg
 
 566     def DrawArrows(self
, dc
): 
 567         """Draw all arrows.""" 
 568         # Distance along line of each arrow: space them out evenly 
 573         for arrow 
in self
._arcArrows
: 
 574             ah 
= arrow
.GetArrowEnd() 
 575             if ah 
== ARROW_POSITION_START
: 
 576                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 577                     # If specified, x offset is proportional to line length 
 578                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 580                     self
.DrawArrow(dc
, arrow
, startArrowPos
, False) 
 581                     startArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 582             elif ah 
== ARROW_POSITION_END
: 
 583                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 584                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 586                     self
.DrawArrow(dc
, arrow
, endArrowPos
, False) 
 587                     endArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 588             elif ah 
== ARROW_POSITION_MIDDLE
: 
 589                 arrow
.SetXOffset(middleArrowPos
) 
 590                 if arrow
.GetXOffset() and not self
._ignoreArrowOffsets
: 
 591                     self
.DrawArrow(dc
, arrow
, arrow
.GetXOffset(), True) 
 593                     self
.DrawArrow(dc
, arrow
, middleArrowPos
, False) 
 594                     middleArrowPos 
+= arrow
.GetSize() + arrow
.GetSpacing() 
 596     def DrawArrow(self
, dc
, arrow
, XOffset
, proportionalOffset
): 
 597         """Draw the given arrowhead (or annotation).""" 
 598         first_line_point 
= self
._lineControlPoints
[0] 
 599         second_line_point 
= self
._lineControlPoints
[1] 
 601         last_line_point 
= self
._lineControlPoints
[-1] 
 602         second_last_line_point 
= self
._lineControlPoints
[-2] 
 604         # Position of start point of line, at the end of which we draw the arrow 
 605         startPositionX
, startPositionY 
= 0.0, 0.0 
 607         ap 
= arrow
.GetPosition() 
 608         if ap 
== ARROW_POSITION_START
: 
 609             # If we're using a proportional offset, calculate just where this 
 610             # will be on the line. 
 612             if proportionalOffset
: 
 613                 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])) 
 614                 realOffset 
= XOffset 
* totalLength
 
 616             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_line_point
[0], second_line_point
[1], first_line_point
[0], first_line_point
[1], realOffset
) 
 618             startPositionX 
= second_line_point
[0] 
 619             startPositionY 
= second_line_point
[1] 
 620         elif ap 
== ARROW_POSITION_END
: 
 621             # If we're using a proportional offset, calculate just where this 
 622             # will be on the line. 
 624             if proportionalOffset
: 
 625                 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])); 
 626                 realOffset 
= XOffset 
* totalLength
 
 628             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], last_line_point
[0], last_line_point
[1], realOffset
) 
 630             startPositionX 
= second_last_line_point
[0] 
 631             startPositionY 
= second_last_line_point
[1] 
 632         elif ap 
== ARROW_POSITION_MIDDLE
: 
 633             # Choose a point half way between the last and penultimate points 
 634             x 
= (last_line_point
[0] + second_last_line_point
[0]) / 2.0 
 635             y 
= (last_line_point
[1] + second_last_line_point
[1]) / 2.0 
 637             # If we're using a proportional offset, calculate just where this 
 638             # will be on the line. 
 640             if proportionalOffset
: 
 641                 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
)); 
 642                 realOffset 
= XOffset 
* totalLength
 
 644             positionOnLineX
, positionOnLineY 
= GetPointOnLine(second_last_line_point
[0], second_last_line_point
[1], x
, y
, realOffset
) 
 645             startPositionX 
= second_last_line_point
[0] 
 646             startPositionY 
= second_last_line_point
[1] 
 648         # Add yOffset to arrow, if any 
 650         # The translation that the y offset may give 
 653         if arrow
.GetYOffset 
and not self
._ignoreArrowOffsets
: 
 657             #   (x1, y1)--------------(x3, y3)------------------(x2, y2) 
 658             #   x4 = x3 - d * math.sin(theta) 
 659             #   y4 = y3 + d * math.cos(theta) 
 661             #   Where theta = math.tan(-1) of (y3-y1) / (x3-x1) 
 664             x3 
= float(positionOnLineX
) 
 665             y3 
= float(positionOnLineY
) 
 666             d 
= -arrow
.GetYOffset() # Negate so +offset is above line 
 669                 theta 
= math
.pi 
/ 2.0 
 671                 theta 
= math
.atan((y3 
- y1
) / (x3 
- x1
)) 
 673             x4 
= x3 
- d 
* math
.sin(theta
) 
 674             y4 
= y3 
+ d 
* math
.cos(theta
) 
 676             deltaX 
= x4 
- positionOnLineX
 
 677             deltaY 
= y4 
- positionOnLineY
 
 679         at 
= arrow
._GetType
() 
 680         if at 
== ARROW_ARROW
: 
 681             arrowLength 
= arrow
.GetSize() 
 682             arrowWidth 
= arrowLength 
/ 3.0 
 684             tip_x
, tip_y
, side1_x
, side1_y
, side2_x
, side2_y 
= GetArrowPoints(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, arrowLength
, arrowWidth
) 
 686             points 
= [[tip_x
, tip_y
], 
 692             dc
.SetBrush(self
._brush
) 
 693             dc
.DrawPolygon(points
) 
 694         elif at 
in [ARROW_HOLLOW_CIRCLE
, ARROW_FILLED_CIRCLE
]: 
 695             # Find point on line of centre of circle, which is a radius away 
 696             # from the end position 
 697             diameter 
= arrow
.GetSize() 
 698             x
, y 
= GetPointOnLine(startPositionX 
+ deltaX
, startPositionY 
+ deltaY
, 
 699                                positionOnLineX 
+ deltaX
, positionOnLineY 
+ deltaY
, 
 701             x1 
= x 
- diameter 
/ 2.0 
 702             y1 
= y 
- diameter 
/ 2.0 
 704             if arrow
._GetType
() == ARROW_HOLLOW_CIRCLE
: 
 705                 dc
.SetBrush(self
.GetBackgroundBrush()) 
 707                 dc
.SetBrush(self
._brush
) 
 709             dc
.DrawEllipse(x1
, y1
, diameter
, diameter
) 
 710         elif at 
== ARROW_SINGLE_OBLIQUE
: 
 712         elif at 
== ARROW_METAFILE
: 
 713             if arrow
.GetMetaFile(): 
 714                 # Find point on line of centre of object, which is a half-width away 
 715                 # from the end position 
 718                 #  <-- start pos  <-----><-- positionOnLineX 
 720                 #  --------------|  x  | <-- e.g. rectangular arrowhead 
 723                 x
, y 
= GetPointOnLine(startPositionX
, startPositionY
, 
 724                                    positionOnLineX
, positionOnLineY
, 
 725                                    arrow
.GetMetaFile()._width 
/ 2.0) 
 726                 # Calculate theta for rotating the metafile. 
 729                 # |     o(x2, y2)   'o' represents the arrowhead. 
 734                 # |______________________ 
 739                 x2 
= float(positionOnLineX
) 
 740                 y2 
= float(positionOnLineY
) 
 742                 if x1 
== x2 
and y1 
== y2
: 
 744                 elif x1 
== x2 
and y1 
> y2
: 
 745                     theta 
= 3.0 * math
.pi 
/ 2.0 
 746                 elif x1 
== x2 
and y2 
> y1
: 
 747                     theta 
= math
.pi 
/ 2.0 
 748                 elif x2 
> x1 
and y2 
>= y1
: 
 749                     theta 
= math
.atan((y2 
- y1
) / (x2 
- x1
)) 
 751                     theta 
= math
.pi 
+ math
.atan((y2 
- y1
) / (x2 
- x1
)) 
 752                 elif x2 
> x1 
and y2 
< y1
: 
 753                     theta 
= 2 * math
.pi 
+ math
.atan((y2 
- y1
) / (x2 
- x1
)) 
 755                     raise "Unknown arrowhead rotation case" 
 757                 # Rotate about the centre of the object, then place 
 758                 # the object on the line. 
 759                 if arrow
.GetMetaFile().GetRotateable(): 
 760                     arrow
.GetMetaFile().Rotate(0.0, 0.0, theta
) 
 763                     # If erasing, just draw a rectangle 
 764                     minX
, minY
, maxX
, maxY 
= arrow
.GetMetaFile().GetBounds() 
 765                     # Make erasing rectangle slightly bigger or you get droppings 
 767                     dc
.DrawRectangle(deltaX 
+ x 
+ minX 
- extraPixels 
/ 2.0, deltaY 
+ y 
+ minY 
- extraPixels 
/ 2.0, maxX 
- minX 
+ extraPixels
, maxY 
- minY 
+ extraPixels
) 
 769                     arrow
.GetMetaFile().Draw(dc
, x 
+ deltaX
, y 
+ deltaY
) 
 771     def OnErase(self
, dc
): 
 773         old_brush 
= self
._brush
 
 775         bg_pen 
= self
.GetBackgroundPen() 
 776         bg_brush 
= self
.GetBackgroundBrush() 
 778         self
.SetBrush(bg_brush
) 
 780         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
 782             dc
.SetFont(self
._font
) 
 784         # Undraw text regions 
 787                 x
, y 
= self
.GetLabelPosition(i
) 
 788                 self
.EraseRegion(dc
, self
._regions
[i
], x
, y
) 
 791         dc
.SetPen(self
.GetBackgroundPen()) 
 792         dc
.SetBrush(self
.GetBackgroundBrush()) 
 794         # Drawing over the line only seems to work if the line has a thickness 
 796         if old_pen 
and old_pen
.GetWidth() > 1: 
 797             dc
.DrawRectangle(self
._xpos 
- bound_x 
/ 2.0 - 2, self
._ypos 
- bound_y 
/ 2.0 - 2, 
 798                              bound_x 
+ 4, bound_y 
+ 4) 
 801             self
.GetEventHandler().OnDraw(dc
) 
 802             self
.GetEventHandler().OnEraseControlPoints(dc
) 
 803             self
._erasing 
= False 
 808             self
.SetBrush(old_brush
) 
 810     def GetBoundingBoxMin(self
): 
 811         x1
, y1 
= 10000, 10000 
 812         x2
, y2 
= -10000, -10000 
 814         for point 
in self
._lineControlPoints
: 
 824         return x2 
- x1
, y2 
- y1
 
 826     # For a node image of interest, finds the position of this arc 
 827     # amongst all the arcs which are attached to THIS SIDE of the node image, 
 828     # and the number of same. 
 829     def FindNth(self
, image
, incoming
): 
 830         """Find the position of the line on the given object. 
 832         Specify whether incoming or outgoing lines are being considered 
 838         if image 
== self
._to
: 
 839             this_attachment 
= self
._attachmentTo
 
 841             this_attachment 
= self
._attachmentFrom
 
 843         # Find number of lines going into / out of this particular attachment point 
 844         for line 
in image
.GetLines(): 
 845             if line
._from 
== image
: 
 846                 # This is the nth line attached to 'image' 
 847                 if line 
== self 
and not incoming
: 
 850                 # Increment num count if this is the same side (attachment number) 
 851                 if line
._attachmentFrom 
== this_attachment
: 
 854             if line
._to 
== image
: 
 855                 # This is the nth line attached to 'image' 
 856                 if line 
== self 
and incoming
: 
 859                 # Increment num count if this is the same side (attachment number) 
 860                 if line
._attachmentTo 
== this_attachment
: 
 865     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 867         old_brush 
= self
._brush
 
 869         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
 870         self
.SetPen(dottedPen
) 
 871         self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
 873         self
.GetEventHandler().OnDraw(dc
) 
 880             self
.SetBrush(old_brush
) 
 884     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 888         if self
._lineControlPoints 
and not (x_offset 
== 0 and y_offset 
== 0): 
 889             for point 
in self
._lineControlPoints
: 
 893         # Move temporary label rectangles if necessary 
 895             if self
._labelObjects
[i
]: 
 896                 self
._labelObjects
[i
].Erase(dc
) 
 897                 xp
, yp 
= self
.GetLabelPosition(i
) 
 898                 if i 
< len(self
._regions
): 
 899                     xr
, yr 
= self
._regions
[i
].GetPosition() 
 902                 self
._labelObjects
[i
].Move(dc
, xp 
+ xr
, yp 
+ yr
) 
 905     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
 906         """Called when a connected object has moved, to move the link to 
 909         if not self
._from 
or not self
._to
: 
 912         # Do each end - nothing in the middle. User has to move other points 
 913         # manually if necessary 
 914         end_x
, end_y
, other_end_x
, other_end_y 
= self
.FindLineEndPoints() 
 916         oldX
, oldY 
= self
._xpos
, self
._ypos
 
 918         # pi: The first time we go through FindLineEndPoints we can't 
 919         # use the middle points (since they don't have sane values), 
 920         # so we just do what we do for a normal line. Then we call 
 921         # Initialise to set the middle points, and then FindLineEndPoints 
 922         # again, but this time (and from now on) we use the middle 
 923         # points to calculate the end points. 
 924         # This was buggy in the C++ version too. 
 926         self
.SetEnds(end_x
, end_y
, other_end_x
, other_end_y
) 
 928         if len(self
._lineControlPoints
) > 2: 
 931         # Do a second time, because one may depend on the other 
 932         end_x
, end_y
, other_end_x
, other_end_y 
= self
.FindLineEndPoints() 
 933         self
.SetEnds(end_x
, end_y
, other_end_x
, other_end_y
) 
 935         # Try to move control points with the arc 
 936         x_offset 
= self
._xpos 
- oldX
 
 937         y_offset 
= self
._ypos 
- oldY
 
 939         # Only move control points if it's a self link. And only works 
 940         # if attachment mode is ON 
 941         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): 
 942             for point 
in self
._lineControlPoints
[1:-1]: 
 946         self
.Move(dc
, self
._xpos
, self
._ypos
) 
 948     def FindLineEndPoints(self
): 
 949         """Finds the x, y points at the two ends of the line. 
 951         This function can be used by e.g. line-routing routines to 
 952         get the actual points on the two node images where the lines will be 
 955         if not self
._from 
or not self
._to
: 
 958         # Do each end - nothing in the middle. User has to move other points 
 959         # manually if necessary. 
 960         second_point 
= self
._lineControlPoints
[1] 
 961         second_last_point 
= self
._lineControlPoints
[-2] 
 963         # pi: If we have a segmented line and this is the first time, 
 964         # do this as a straight line. 
 965         if len(self
._lineControlPoints
) > 2 and self
._initialised
: 
 966             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 967                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) # Not incoming 
 968                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 970                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), second_point
[0], second_point
[1]) 
 972             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 973                 nth
, no_arch 
= self
.FindNth(self
._to
, True) # Incoming 
 974                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arch
, self
) 
 976                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), second_last_point
[0], second_last_point
[1]) 
 978             fromX 
= self
._from
.GetX() 
 979             fromY 
= self
._from
.GetY() 
 980             toX 
= self
._to
.GetX() 
 981             toY 
= self
._to
.GetY() 
 983             if self
._from
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 984                 nth
, no_arcs 
= self
.FindNth(self
._from
, False) 
 985                 end_x
, end_y 
= self
._from
.GetAttachmentPosition(self
._attachmentFrom
, nth
, no_arcs
, self
) 
 989             if self
._to
.GetAttachmentMode() != ATTACHMENT_MODE_NONE
: 
 990                 nth
, no_arcs 
= self
.FindNth(self
._to
, True) 
 991                 other_end_x
, other_end_y 
= self
._to
.GetAttachmentPosition(self
._attachmentTo
, nth
, no_arcs
, self
) 
 995             if self
._from
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 996                 end_x
, end_y 
= self
._from
.GetPerimeterPoint(self
._from
.GetX(), self
._from
.GetY(), toX
, toY
) 
 998             if self
._to
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 999                 other_end_x
, other_end_y 
= self
._to
.GetPerimeterPoint(self
._to
.GetX(), self
._to
.GetY(), fromX
, fromY
) 
1001         return end_x
, end_y
, other_end_x
, other_end_y
 
1004     def OnDraw(self
, dc
): 
1005         if not self
._lineControlPoints
: 
1009             dc
.SetPen(self
._pen
) 
1011             dc
.SetBrush(self
._brush
) 
1014         for point 
in self
._lineControlPoints
: 
1015             points
.append(wx
.Point(point
[0], point
[1])) 
1018             dc
.DrawSpline(points
) 
1020             dc
.DrawLines(points
) 
1022         if sys
.platform
[:3] == "win": 
1023             # For some reason, last point isn't drawn under Windows 
1025             dc
.DrawPoint(pt
[0], pt
[1]) 
1027         # Problem with pen - if not a solid pen, does strange things 
1028         # to the arrowhead. So make (get) a new pen that's solid. 
1029         if self
._pen 
and self
._pen
.GetStyle() != wx
.SOLID
: 
1030             solid_pen 
= wx
.Pen(self
._pen
.GetColour(), 1, wx
.SOLID
) 
1032                 dc
.SetPen(solid_pen
) 
1036     def OnDrawControlPoints(self
, dc
): 
1037         if not self
._drawHandles
: 
1040         # Draw temporary label rectangles if necessary 
1042             if self
._labelObjects
[i
]: 
1043                 self
._labelObjects
[i
].Draw(dc
) 
1045         Shape
.OnDrawControlPoints(self
, dc
) 
1047     def OnEraseControlPoints(self
, dc
): 
1048         # Erase temporary label rectangles if necessary 
1051             if self
._labelObjects
[i
]: 
1052                 self
._labelObjects
[i
].Erase(dc
) 
1054         Shape
.OnEraseControlPoints(self
, dc
) 
1056     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1059     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1062     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1065     def OnDrawContents(self
, dc
): 
1066         if self
.GetDisableLabel(): 
1070             if self
._regions
[i
]: 
1071                 x
, y 
= self
.GetLabelPosition(i
) 
1072                 self
.DrawRegion(dc
, self
._regions
[i
], x
, y
) 
1074     def SetTo(self
, object): 
1075         """Set the 'to' object for the line.""" 
1078     def SetFrom(self
, object): 
1079         """Set the 'from' object for the line.""" 
1082     def MakeControlPoints(self
): 
1083         """Make handle control points.""" 
1084         if self
._canvas 
and self
._lineControlPoints
: 
1085             first 
= self
._lineControlPoints
[0] 
1086             last 
= self
._lineControlPoints
[-1] 
1088             control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, first
[0], first
[1], CONTROL_POINT_ENDPOINT_FROM
) 
1089             control
._point 
= first
 
1090             self
._canvas
.AddShape(control
) 
1091             self
._controlPoints
.append(control
) 
1093             for point 
in self
._lineControlPoints
[1:-1]: 
1094                 control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
[0], point
[1], CONTROL_POINT_LINE
) 
1095                 control
._point 
= point
 
1096                 self
._canvas
.AddShape(control
) 
1097                 self
._controlPoints
.append(control
) 
1099             control 
= LineControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, last
[0], last
[1], CONTROL_POINT_ENDPOINT_TO
) 
1100             control
._point 
= last
 
1101             self
._canvas
.AddShape(control
) 
1102             self
._controlPoints
.append(control
) 
1104     def ResetControlPoints(self
): 
1105         if self
._canvas 
and self
._lineControlPoints 
and self
._controlPoints
: 
1106             for i 
in range(min(len(self
._controlPoints
), len(self
._lineControlPoints
))): 
1107                 point 
= self
._lineControlPoints
[i
] 
1108                 control 
= self
._controlPoints
[i
] 
1109                 control
.SetX(point
[0]) 
1110                 control
.SetY(point
[1]) 
1112     # Override select, to create / delete temporary label-moving objects 
1113     def Select(self
, select
, dc 
= None): 
1114         Shape
.Select(self
, select
, dc
) 
1117                 if self
._regions
[i
]: 
1118                     region 
= self
._regions
[i
] 
1119                     if region
._formattedText
: 
1120                         w
, h 
= region
.GetSize() 
1121                         x
, y 
= region
.GetPosition() 
1122                         xx
, yy 
= self
.GetLabelPosition(i
) 
1124                         if self
._labelObjects
[i
]: 
1125                             self
._labelObjects
[i
].Select(False) 
1126                             self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
1128                         self
._labelObjects
[i
] = self
.OnCreateLabelShape(self
, region
, w
, h
) 
1129                         self
._labelObjects
[i
].AddToCanvas(self
._canvas
) 
1130                         self
._labelObjects
[i
].Show(True) 
1132                             self
._labelObjects
[i
].Move(dc
, x 
+ xx
, y 
+ yy
) 
1133                         self
._labelObjects
[i
].Select(True, dc
) 
1136                 if self
._labelObjects
[i
]: 
1137                     self
._labelObjects
[i
].Select(False, dc
) 
1138                     self
._labelObjects
[i
].Erase(dc
) 
1139                     self
._labelObjects
[i
].RemoveFromCanvas(self
._canvas
) 
1140                     self
._labelObjects
[i
] = None 
1142     # Control points ('handles') redirect control to the actual shape, to 
1143     # make it easier to override sizing behaviour. 
1144     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1145         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1146         self
.GetCanvas().PrepareDC(dc
) 
1148         dc
.SetLogicalFunction(OGLRBLF
) 
1150         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1151         dc
.SetPen(dottedPen
) 
1152         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1154         if pt
._type 
== CONTROL_POINT_LINE
: 
1155             x
, y 
= self
._canvas
.Snap(x
, y
) 
1162             old_pen 
= self
.GetPen() 
1163             old_brush 
= self
.GetBrush() 
1165             self
.SetPen(dottedPen
) 
1166             self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1168             self
.GetEventHandler().OnMoveLink(dc
, False) 
1170             self
.SetPen(old_pen
) 
1171             self
.SetBrush(old_brush
) 
1173     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
1174         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1175         self
.GetCanvas().PrepareDC(dc
) 
1177         if pt
._type 
== CONTROL_POINT_LINE
: 
1178             pt
._originalPos 
= pt
._point
 
1179             x
, y 
= self
._canvas
.Snap(x
, y
) 
1183             # Redraw start and end objects because we've left holes 
1184             # when erasing the line 
1185             self
.GetFrom().OnDraw(dc
) 
1186             self
.GetFrom().OnDrawContents(dc
) 
1187             self
.GetTo().OnDraw(dc
) 
1188             self
.GetTo().OnDrawContents(dc
) 
1190             self
.SetDisableLabel(True) 
1191             dc
.SetLogicalFunction(OGLRBLF
) 
1198             old_pen 
= self
.GetPen() 
1199             old_brush 
= self
.GetBrush() 
1201             dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1202             self
.SetPen(dottedPen
) 
1203             self
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1205             self
.GetEventHandler().OnMoveLink(dc
, False) 
1207             self
.SetPen(old_pen
) 
1208             self
.SetBrush(old_brush
) 
1210         if pt
._type 
== CONTROL_POINT_ENDPOINT_FROM 
or pt
._type 
== CONTROL_POINT_ENDPOINT_TO
: 
1211             self
._canvas
.SetCursor(wx
.StockCursor(wx
.CURSOR_BULLSEYE
)) 
1212             pt
._oldCursor 
= wx
.STANDARD_CURSOR
 
1214     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
1215         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1216         self
.GetCanvas().PrepareDC(dc
) 
1218         self
.SetDisableLabel(False) 
1220         if pt
._type 
== CONTROL_POINT_LINE
: 
1221             x
, y 
= self
._canvas
.Snap(x
, y
) 
1223             rpt 
= wx
.RealPoint(x
, y
) 
1225             # Move the control point back to where it was; 
1226             # MoveControlPoint will move it to the new position 
1227             # if it decides it wants. We only moved the position 
1228             # during user feedback so we could redraw the line 
1229             # as it changed shape. 
1230             pt
._xpos 
= pt
._originalPos
[0] 
1231             pt
._ypos 
= pt
._originalPos
[1] 
1232             pt
._point
[0] = pt
._originalPos
[0] 
1233             pt
._point
[1] = pt
._originalPos
[1] 
1235             self
.OnMoveMiddleControlPoint(dc
, pt
, rpt
) 
1237         if pt
._type 
== CONTROL_POINT_ENDPOINT_FROM
: 
1239                 self
._canvas
.SetCursor(pt
._oldCursor
) 
1242                 self
.GetFrom().MoveLineToNewAttachment(dc
, self
, x
, y
) 
1244         if pt
._type 
== CONTROL_POINT_ENDPOINT_TO
: 
1246                 self
._canvas
.SetCursor(pt
._oldCursor
) 
1249                 self
.GetTo().MoveLineToNewAttachment(dc
, self
, x
, y
) 
1251     # This is called only when a non-end control point is moved 
1252     def OnMoveMiddleControlPoint(self
, dc
, lpt
, pt
): 
1256         lpt
._point
[0] = pt
[0] 
1257         lpt
._point
[1] = pt
[1] 
1259         self
.GetEventHandler().OnMoveLink(dc
) 
1263     def AddArrow(self
, type, end 
= ARROW_POSITION_END
, size 
= 10.0, xOffset 
= 0.0, name 
= "", mf 
= None, arrowId 
= -1): 
1264         """Add an arrow (or annotation) to the line. 
1266         type may currently be one of: 
1273           Conventional arrowhead.  
1274         ARROW_SINGLE_OBLIQUE 
1275           Single oblique stroke.  
1276         ARROW_DOUBLE_OBLIQUE 
1277           Double oblique stroke.  
1278         ARROW_DOUBLE_METAFILE 
1281         end may currently be one of: 
1284           Arrow appears at the end.  
1285         ARROW_POSITION_START 
1286           Arrow appears at the start.  
1288         arrowSize specifies the length of the arrow. 
1290         xOffset specifies the offset from the end of the line. 
1292         name specifies a name for the arrow. 
1294         mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows 
1297         arrowId is the id for the arrow. 
1299         arrow 
= ArrowHead(type, end
, size
, xOffset
, name
, mf
, arrowId
) 
1300         self
._arcArrows
.append(arrow
) 
1303     # Add arrowhead at a particular position in the arrowhead list 
1304     def AddArrowOrdered(self
, arrow
, referenceList
, end
): 
1305         """Add an arrowhead in the position indicated by the reference list 
1306         of arrowheads, which contains all legal arrowheads for this line, in 
1307         the correct order. E.g. 
1309         Reference list:      a b c d e 
1310         Current line list:   a d 
1312         Add c, then line list is: a c d. 
1314         If no legal arrowhead position, return FALSE. Assume reference list 
1315         is for one end only, since it potentially defines the ordering for 
1316         any one of the 3 positions. So we don't check the reference list for 
1319         if not referenceList
: 
1322         targetName 
= arrow
.GetName() 
1324         # First check whether we need to insert in front of list, 
1325         # because this arrowhead is the first in the reference 
1326         # list and should therefore be first in the current list. 
1327         refArrow 
= referenceList
[0] 
1328         if refArrow
.GetName() == targetName
: 
1329             self
._arcArrows
.insert(0, arrow
) 
1333         while i1 
< len(referenceList
) and i2 
< len(self
._arcArrows
): 
1334             refArrow 
= referenceList
[i1
] 
1335             currArrow 
= self
._arcArrows
[i2
] 
1337             # Matching: advance current arrow pointer 
1338             if currArrow
.GetArrowEnd() == end 
and currArrow
.GetName() == refArrow
.GetName(): 
1341             # Check if we're at the correct position in the 
1343             if targetName 
== refArrow
.GetName(): 
1344                 if i2 
< len(self
._arcArrows
): 
1345                     self
._arcArrows
.insert(i2
, arrow
) 
1347                     self
._arcArrows
.append(arrow
) 
1351         self
._arcArrows
.append(arrow
) 
1354     def ClearArrowsAtPosition(self
, end
): 
1355         """Delete the arrows at the specified position, or at any position 
1359             self
._arcArrows 
= [] 
1362         for arrow 
in self
._arcArrows
: 
1363             if arrow
.GetArrowEnd() == end
: 
1364                 self
._arcArrows
.remove(arrow
) 
1366     def ClearArrow(self
, name
): 
1367         """Delete the arrow with the given name.""" 
1368         for arrow 
in self
._arcArrows
: 
1369             if arrow
.GetName() == name
: 
1370                 self
._arcArrows
.remove(arrow
) 
1374     def FindArrowHead(self
, position
, name
): 
1375         """Find arrowhead by position and name. 
1377         if position is -1, matches any position. 
1379         for arrow 
in self
._arcArrows
: 
1380             if (position 
== -1 or position 
== arrow
.GetArrowEnd()) and arrow
.GetName() == name
: 
1385     def FindArrowHeadId(self
, arrowId
): 
1386         """Find arrowhead by id.""" 
1387         for arrow 
in self
._arcArrows
: 
1388             if arrowId 
== arrow
.GetId(): 
1393     def DeleteArrowHead(self
, position
, name
): 
1394         """Delete arrowhead by position and name. 
1396         if position is -1, matches any position. 
1398         for arrow 
in self
._arcArrows
: 
1399             if (position 
== -1 or position 
== arrow
.GetArrowEnd()) and arrow
.GetName() == name
: 
1400                 self
._arcArrows
.remove(arrow
) 
1404     def DeleteArrowHeadId(self
, id): 
1405         """Delete arrowhead by id.""" 
1406         for arrow 
in self
._arcArrows
: 
1407             if arrowId 
== arrow
.GetId(): 
1408                 self
._arcArrows
.remove(arrow
) 
1412     # Calculate the minimum width a line 
1413     # occupies, for the purposes of drawing lines in tools. 
1414     def FindMinimumWidth(self
): 
1415         """Find the horizontal width for drawing a line with arrows in 
1416         minimum space. Assume arrows at end only. 
1419         for arrowHead 
in self
._arcArrows
: 
1420             minWidth 
+= arrowHead
.GetSize() 
1421             if arrowHead 
!= self
._arcArrows
[-1]: 
1422                 minWidth 
+= arrowHead 
+ GetSpacing
 
1424         # We have ABSOLUTE minimum now. So 
1425         # scale it to give it reasonable aesthetics 
1426         # when drawing with line. 
1428             minWidth 
= minWidth 
* 1.4 
1432         self
.SetEnds(0.0, 0.0, minWidth
, 0.0) 
1437     def FindLinePosition(self
, x
, y
): 
1438         """Find which position we're talking about at this x, y. 
1440         Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END. 
1442         startX
, startY
, endX
, endY 
= self
.GetEnds() 
1444         # Find distances from centre, start and end. The smallest wins 
1445         centreDistance 
= math
.sqrt((x 
- self
._xpos
) * (x 
- self
._xpos
) + (y 
- self
._ypos
) * (y 
- self
._ypos
)) 
1446         startDistance 
= math
.sqrt((x 
- startX
) * (x 
- startX
) + (y 
- startY
) * (y 
- startY
)) 
1447         endDistance 
= math
.sqrt((x 
- endX
) * (x 
- endX
) + (y 
- endY
) * (y 
- endY
)) 
1449         if centreDistance 
< startDistance 
and centreDistance 
< endDistance
: 
1450             return ARROW_POSITION_MIDDLE
 
1451         elif startDistance 
< endDistance
: 
1452             return ARROW_POSITION_START
 
1454             return ARROW_POSITION_END
 
1456     def SetAlignmentOrientation(self
, isEnd
, isHoriz
): 
1458             if isHoriz 
and self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
!= LINE_ALIGNMENT_HORIZ
: 
1459                 self
._alignmentEnd 
!= LINE_ALIGNMENT_HORIZ
 
1460             elif not isHoriz 
and self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
: 
1461                 self
._alignmentEnd 
-= LINE_ALIGNMENT_HORIZ
 
1463             if isHoriz 
and self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
!= LINE_ALIGNMENT_HORIZ
: 
1464                 self
._alignmentStart 
!= LINE_ALIGNMENT_HORIZ
 
1465             elif not isHoriz 
and self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
: 
1466                 self
._alignmentStart 
-= LINE_ALIGNMENT_HORIZ
 
1468     def SetAlignmentType(self
, isEnd
, alignType
): 
1470             if alignType 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1471                 if self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
!= LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1472                     self
._alignmentEnd |
= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1473             elif self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1474                 self
._alignmentEnd 
-= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1476             if alignType 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1477                 if self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
!= LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1478                     self
._alignmentStart |
= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1479             elif self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE 
== LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1480                 self
._alignmentStart 
-= LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1482     def GetAlignmentOrientation(self
, isEnd
): 
1484             return self
._alignmentEnd 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
 
1486             return self
._alignmentStart 
& LINE_ALIGNMENT_HORIZ 
== LINE_ALIGNMENT_HORIZ
 
1488     def GetAlignmentType(self
, isEnd
): 
1490             return self
._alignmentEnd 
& LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1492             return self
._alignmentStart 
& LINE_ALIGNMENT_TO_NEXT_HANDLE
 
1494     def GetNextControlPoint(self
, shape
): 
1495         """Find the next control point in the line after the start / end point, 
1496         depending on whether the shape is at the start or end. 
1498         n 
= len(self
._lineControlPoints
) 
1499         if self
._to 
== shape
: 
1500             # Must be END of line, so we want (n - 1)th control point. 
1501             # But indexing ends at n-1, so subtract 2. 
1505         if nn 
< len(self
._lineControlPoints
): 
1506             return self
._lineControlPoints
[nn
] 
1509     def OnCreateLabelShape(self
, parent
, region
, w
, h
): 
1510         return LabelShape(parent
, region
, w
, h
) 
1513     def OnLabelMovePre(self
, dc
, labelShape
, x
, y
, old_x
, old_y
, display
): 
1514         labelShape
._shapeRegion
.SetSize(labelShape
.GetWidth(), labelShape
.GetHeight()) 
1516         # Find position in line's region list 
1517         i 
= self
._regions
.index(labelShape
._shapeRegion
) 
1519         xx
, yy 
= self
.GetLabelPosition(i
) 
1520         # Set the region's offset, relative to the default position for 
1522         labelShape
._shapeRegion
.SetPosition(x 
- xx
, y 
- yy
) 
1526         # Need to reformat to fit region 
1527         if labelShape
._shapeRegion
.GetText(): 
1528             s 
= labelShape
._shapeRegion
.GetText() 
1529             labelShape
.FormatText(dc
, s
, i
) 
1530             self
.DrawRegion(dc
, labelShape
._shapeRegion
, xx
, yy
)