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
)