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