]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ogl/_lines.py
Can't call ShiftDown when it is a CommandEvent
[wxWidgets.git] / wxPython / wx / lib / ogl / _lines.py
1 # -*- coding: iso-8859-1 -*-
2 #----------------------------------------------------------------------------
3 # Name: lines.py
4 # Purpose: LineShape class
5 #
6 # Author: Pierre Hjälm (from C++ original by Julian Smart)
7 #
8 # Created: 2004-05-08
9 # RCS-ID: $Id$
10 # Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
11 # Licence: wxWindows license
12 #----------------------------------------------------------------------------
13
14 from __future__ import division
15
16 import sys
17 import math
18
19 from _basic import Shape, ShapeRegion, ControlPoint, RectangleShape
20 from _oglmisc import *
21
22 # Line alignment flags
23 # Vertical by default
24 LINE_ALIGNMENT_HORIZ= 1
25 LINE_ALIGNMENT_VERT= 0
26 LINE_ALIGNMENT_TO_NEXT_HANDLE= 2
27 LINE_ALIGNMENT_NONE= 0
28
29
30
31 class LineControlPoint(ControlPoint):
32 def __init__(self, theCanvas = None, object = None, size = 0.0, x = 0.0, y = 0.0, the_type = 0):
33 ControlPoint.__init__(self, theCanvas, object, size, x, y, the_type)
34 self._xpos = x
35 self._ypos = y
36 self._type = the_type
37 self._point = None
38 self._originalPos = None
39
40 def OnDraw(self, dc):
41 RectangleShape.OnDraw(self, dc)
42
43 # Implement movement of Line point
44 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
45 self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
46
47 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
48 self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
49
50 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
51 self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
52
53
54
55 class ArrowHead(object):
56 def __init__(self, type = 0, end = 0, size = 0.0, dist = 0.0, name="",mf = None, arrowId=-1):
57 if isinstance(type, ArrowHead):
58 pass
59 else:
60 self._arrowType = type
61 self._arrowEnd = end
62 self._arrowSize = size
63 self._xOffset = dist
64 self._yOffset = 0.0
65 self._spacing = 5.0
66
67 self._arrowName = name
68 self._metaFile = mf
69 self._id = arrowId
70 if self._id==-1:
71 self._id = wx.NewId()
72
73 def _GetType(self):
74 return self._arrowType
75
76 def GetPosition(self):
77 return self._arrowEnd
78
79 def SetPosition(self, pos):
80 self._arrowEnd = pos
81
82 def GetXOffset(self):
83 return self._xOffset
84
85 def GetYOffset(self):
86 return self._yOffset
87
88 def GetSpacing(self):
89 return self._spacing
90
91 def GetSize(self):
92 return self._arrowSize
93
94 def SetSize(self, size):
95 self._arrowSize = size
96 if self._arrowType == ARROW_METAFILE and self._metaFile:
97 oldWidth = self._metaFile._width
98 if oldWidth == 0:
99 return
100
101 scale = size / oldWidth
102 if scale != 1:
103 self._metaFile.Scale(scale, scale)
104
105 def GetName(self):
106 return self._arrowName
107
108 def SetXOffset(self, x):
109 self._xOffset = x
110
111 def SetYOffset(self, y):
112 self._yOffset = y
113
114 def GetMetaFile(self):
115 return self._metaFile
116
117 def GetId(self):
118 return self._id
119
120 def GetArrowEnd(self):
121 return self._arrowEnd
122
123 def GetArrowSize(self):
124 return self._arrowSize
125
126 def SetSpacing(self, sp):
127 self._spacing = sp
128
129
130
131 class LabelShape(RectangleShape):
132 def __init__(self, parent, region, w, h):
133 RectangleShape.__init__(self, w, h)
134 self._lineShape = parent
135 self._shapeRegion = region
136 self.SetPen(wx.ThePenList.FindOrCreatePen(wx.Colour(0, 0, 0), 1, wx.DOT))
137
138 def OnDraw(self, dc):
139 if self._lineShape and not self._lineShape.GetDrawHandles():
140 return
141
142 x1 = self._xpos-self._width / 2
143 y1 = self._ypos-self._height / 2
144
145 if self._pen:
146 if self._pen.GetWidth() == 0:
147 dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT))
148 else:
149 dc.SetPen(self._pen)
150 dc.SetBrush(wx.TRANSPARENT_BRUSH)
151
152 if self._cornerRadius>0:
153 dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
154 else:
155 dc.DrawRectangle(x1, y1, self._width, self._height)
156
157 def OnDrawContents(self, dc):
158 pass
159
160 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
161 RectangleShape.OnDragLeft(self, draw, x, y, keys, attachment)
162
163 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
164 RectangleShape.OnBeginDragLeft(self, x, y, keys, attachment)
165
166 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
167 RectangleShape.OnEndDragLeft(self, x, y, keys, attachment)
168
169 def OnMovePre(self, dc, x, y, old_x, old_y, display):
170 return self._lineShape.OnLabelMovePre(dc, self, x, y, old_x, old_y, display)
171
172 # Divert left and right clicks to line object
173 def OnLeftClick(self, x, y, keys = 0, attachment = 0):
174 self._lineShape.GetEventHandler().OnLeftClick(x, y, keys, attachment)
175
176 def OnRightClick(self, x, y, keys = 0, attachment = 0):
177 self._lineShape.GetEventHandler().OnRightClick(x, y, keys, attachment)
178
179
180
181 class LineShape(Shape):
182 """LineShape may be attached to two nodes;
183 it may be segmented, in which case a control point is drawn for each joint.
184
185 A wxLineShape may have arrows at the beginning, end and centre.
186
187 Derived from:
188 Shape
189 """
190 def __init__(self):
191 Shape.__init__(self)
192
193 self._sensitivity = OP_CLICK_LEFT | OP_CLICK_RIGHT
194 self._draggable = False
195 self._attachmentTo = 0
196 self._attachmentFrom = 0
197 self._from = None
198 self._to = None
199 self._erasing = False
200 self._arrowSpacing = 5.0
201 self._ignoreArrowOffsets = False
202 self._isSpline = False
203 self._maintainStraightLines = False
204 self._alignmentStart = 0
205 self._alignmentEnd = 0
206
207 self._lineControlPoints = None
208
209 # Clear any existing regions (created in an earlier constructor)
210 # and make the three line regions.
211 self.ClearRegions()
212 for name in ["Middle","Start","End"]:
213 newRegion = ShapeRegion()
214 newRegion.SetName(name)
215 newRegion.SetSize(150, 50)
216 self._regions.append(newRegion)
217
218 self._labelObjects = [None, None, None]
219 self._lineOrientations = []
220 self._lineControlPoints = []
221 self._arcArrows = []
222
223 def __del__(self):
224 if self._lineControlPoints:
225 self.ClearPointList(self._lineControlPoints)
226 self._lineControlPoints = []
227 for i in range(3):
228 if self._labelObjects[i]:
229 self._labelObjects[i].Select(False)
230 self._labelObjects[i].RemoveFromCanvas(self._canvas)
231 self._labelObjects = []
232 self.ClearArrowsAtPosition(-1)
233
234 def GetFrom(self):
235 """Return the 'from' object."""
236 return self._from
237
238 def GetTo(self):
239 """Return the 'to' object."""
240 return self._to
241
242 def GetAttachmentFrom(self):
243 """Return the attachment point on the 'from' node."""
244 return self._attachmentFrom
245
246 def GetAttachmentTo(self):
247 """Return the attachment point on the 'to' node."""
248 return self._attachmentTo
249
250 def GetLineControlPoints(self):
251 return self._lineControlPoints
252
253 def SetSpline(self, spline):
254 """Specifies whether a spline is to be drawn through the control points."""
255 self._isSpline = spline
256
257 def IsSpline(self):
258 """TRUE if a spline is drawn through the control points."""
259 return self._isSpline
260
261 def SetAttachmentFrom(self, attach):
262 """Set the 'from' shape attachment."""
263 self._attachmentFrom = attach
264
265 def SetAttachmentTo(self, attach):
266 """Set the 'to' shape attachment."""
267 self._attachmentTo = attach
268
269 # This is really to distinguish between lines and other images.
270 # For lines, want to pass drag to canvas, since lines tend to prevent
271 # dragging on a canvas (they get in the way.)
272 def Draggable(self):
273 return False
274
275 def SetIgnoreOffsets(self, ignore):
276 """Set whether to ignore offsets from the end of the line when drawing."""
277 self._ignoreArrowOffsets = ignore
278
279 def GetArrows(self):
280 return self._arcArrows
281
282 def GetAlignmentStart(self):
283 return self._alignmentStart
284
285 def GetAlignmentEnd(self):
286 return self._alignmentEnd
287
288 def IsEnd(self, nodeObject):
289 """TRUE if shape is at the end of the line."""
290 return self._to == nodeObject
291
292 def MakeLineControlPoints(self, n):
293 """Make a given number of control points (minimum of two)."""
294 if self._lineControlPoints:
295 self.ClearPointList(self._lineControlPoints)
296 self._lineControlPoints = []
297
298 for _ in range(n):
299 point = wx.RealPoint(-999,-999)
300 self._lineControlPoints.append(point)
301
302 def InsertLineControlPoint(self, dc = None):
303 """Insert a control point at an arbitrary position."""
304 if dc:
305 self.Erase(dc)
306
307 last_point = self._lineControlPoints[-1]
308 second_last_point = self._lineControlPoints[-2]
309
310 line_x = (last_point[0] + second_last_point[0]) / 2
311 line_y = (last_point[1] + second_last_point[1]) / 2
312
313 point = wx.RealPoint(line_x, line_y)
314 self._lineControlPoints.insert(len(self._lineControlPoints), point)
315
316 def DeleteLineControlPoint(self):
317 """Delete an arbitary point on the line."""
318 if len(self._lineControlPoints)<3:
319 return False
320
321 del self._lineControlPoints[-2]
322 return True
323
324 def Initialise(self):
325 """Initialise the line object."""
326 if self._lineControlPoints:
327 # Just move the first and last control points
328 first_point = self._lineControlPoints[0]
329 last_point = self._lineControlPoints[-1]
330
331 # If any of the line points are at -999, we must
332 # initialize them by placing them half way between the first
333 # and the last.
334
335 for point in self._lineControlPoints[1:]:
336 if point[0]==-999:
337 if first_point[0]<last_point[0]:
338 x1 = first_point[0]
339 x2 = last_point[0]
340 else:
341 x2 = first_point[0]
342 x1 = last_point[0]
343 if first_point[1]<last_point[1]:
344 y1 = first_point[1]
345 y2 = last_point[1]
346 else:
347 y2 = first_point[1]
348 y1 = last_point[1]
349 point[0] = (x2-x1) / 2 + x1
350 point[1] = (y2-y1) / 2 + y1
351
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.
355 """
356 self.ClearText(i)
357
358 if len(self._regions) == 0 or i >= len(self._regions):
359 return
360
361 region = self._regions[i]
362 region.SetText(s)
363 dc.SetFont(region.GetFont())
364
365 w, h = region.GetSize()
366 # Initialize the size if zero
367 if (w == 0 or h == 0) and s:
368 w, h = 100, 50
369 region.SetSize(w, h)
370
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)
375
376 actualW = w
377 actualH = h
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)
387
388 region.SetSize(actualW, actualH)
389
390 if len(self._labelObjects)<i:
391 self._labelObjects[i].Select(True, dc)
392 self._labelObjects[i].Draw(dc)
393
394 CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW, actualH, region.GetFormatMode())
395 self._formatted = True
396
397 def DrawRegion(self, dc, region, x, y):
398 """Format one region at this position."""
399 if self.GetDisableLabel():
400 return
401
402 w, h = region.GetSize()
403
404 # Get offset from x, y
405 xx, yy = region.GetPosition()
406
407 xp = xx + x
408 yp = yy + y
409
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())
414
415 # Now draw the text
416 if region.GetFont():
417 dc.SetFont(region.GetFont())
418 dc.DrawRectangle(xp-w / 2, yp-h / 2, w, h)
419
420 if self._pen:
421 dc.SetPen(self._pen)
422 dc.SetTextForeground(region.GetActualColourObject())
423
424 DrawFormattedText(dc, region.GetFormattedText(), xp, yp, w, h, region.GetFormatMode())
425
426 def EraseRegion(self, dc, region, x, y):
427 """Erase one region at this position."""
428 if self.GetDisableLabel():
429 return
430
431 w, h = region.GetSize()
432
433 # Get offset from x, y
434 xx, yy = region.GetPosition()
435
436 xp = xx + x
437 yp = yy + y
438
439 if region.GetFormattedText():
440 dc.SetPen(self.GetBackgroundPen())
441 dc.SetBrush(self.GetBackgroundBrush())
442
443 dc.DrawRectangle(xp-w / 2, yp-h / 2, w, h)
444
445 def GetLabelPosition(self, position):
446 """Get the reference point for a label.
447
448 Region x and y are offsets from this.
449 position is 0 (middle), 1 (start), 2 (end).
450 """
451 if position == 0:
452 # Want to take the middle section for the label
453 half_way = int(len(self._lineControlPoints) / 2)
454
455 # Find middle of this line
456 point = self._lineControlPoints[half_way-1]
457 next_point = self._lineControlPoints[half_way]
458
459 dx = next_point[0]-point[0]
460 dy = next_point[1]-point[1]
461
462 return point[0] + dx / 2, point[1] + dy / 2
463 elif position == 1:
464 return self._lineControlPoints[0][0], self._lineControlPoints[0][1]
465 elif position == 2:
466 return self._lineControlPoints[-1][0], self._lineControlPoints[-1][1]
467
468 def Straighten(self, dc = None):
469 """Straighten verticals and horizontals."""
470 if len(self._lineControlPoints)<3:
471 return
472
473 if dc:
474 self.Erase(dc)
475
476 GraphicsStraightenLine(self._lineControlPoints[-1], self._lineControlPoints[-2])
477
478 for i in range(len(self._lineControlPoints)-2):
479 GraphicsStraightenLine(self._lineControlPoints[i], self._lineControlPoints[i + 1])
480
481 if dc:
482 self.Draw(dc)
483
484 def Unlink(self):
485 """Unlink the line from the nodes at either end."""
486 if self._to:
487 self._to.GetLines().remove(self)
488 if self._from:
489 self._from.GetLines().remove(self)
490 self._to = None
491 self._from = None
492
493 def SetEnds(self, x1, y1, x2, y2):
494 """Set the end positions of the line."""
495 # Find centre point
496 first_point = self._lineControlPoints[0]
497 last_point = self._lineControlPoints[-1]
498
499 first_point[0] = x1
500 first_point[1] = y1
501 last_point[0] = x2
502 last_point[1] = y2
503
504 self._xpos = (x1 + x2) / 2
505 self._ypos = (y1 + y2) / 2
506
507 # Get absolute positions of ends
508 def GetEnds(self):
509 """Get the visible endpoints of the lines for drawing between two objects."""
510 first_point = self._lineControlPoints[0]
511 last_point = self._lineControlPoints[-1]
512
513 return (first_point[0], first_point[1]), (last_point[0], last_point[1])
514
515 def SetAttachments(self, from_attach, to_attach):
516 """Specify which object attachment points should be used at each end
517 of the line.
518 """
519 self._attachmentFrom = from_attach
520 self._attachmentTo = to_attach
521
522 def HitTest(self, x, y):
523 if not self._lineControlPoints:
524 return False
525
526 # Look at label regions in case mouse is over a label
527 inLabelRegion = False
528 for i in range(3):
529 if self._regions[i]:
530 region = self._regions[i]
531 if len(region._formattedText):
532 xp, yp = self.GetLabelPosition(i)
533 # Offset region from default label position
534 cx, cy = region.GetPosition()
535 cw, ch = region.GetSize()
536 cx += xp
537 cy += yp
538
539 rLeft = cx-cw / 2
540 rTop = cy-ch / 2
541 rRight = cx + cw / 2
542 rBottom = cy + ch / 2
543 if x>rLeft and x<rRight and y>rTop and y<rBottom:
544 inLabelRegion = True
545 break
546
547 for i in range(len(self._lineControlPoints)-1):
548 point1 = self._lineControlPoints[i]
549 point2 = self._lineControlPoints[i + 1]
550
551 # For inaccurate mousing allow 8 pixel corridor
552 extra = 4
553
554 dx = point2[0]-point1[0]
555 dy = point2[1]-point1[1]
556
557 seg_len = math.sqrt(dx * dx + dy * dy)
558 if dy == 0 or dx == 0:
559 return False
560 distance_from_seg = seg_len * ((x-point1[0]) * dy-(y-point1[1]) * dx) / (dy * dy + dx * dx)
561 distance_from_prev = seg_len * ((y-point1[1]) * dy + (x-point1[0]) * dx) / (dy * dy + dx * dx)
562
563 if abs(distance_from_seg)<extra and distance_from_prev >= 0 and distance_from_prev <= seg_len or inLabelRegion:
564 return 0, distance_from_seg
565
566 return False
567
568 def DrawArrows(self, dc):
569 """Draw all arrows."""
570 # Distance along line of each arrow: space them out evenly
571 startArrowPos = 0.0
572 endArrowPos = 0.0
573 middleArrowPos = 0.0
574
575 for arrow in self._arcArrows:
576 ah = arrow.GetArrowEnd()
577 if ah == ARROW_POSITION_START:
578 if arrow.GetXOffset() and not self._ignoreArrowOffsets:
579 # If specified, x offset is proportional to line length
580 self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
581 else:
582 self.DrawArrow(dc, arrow, startArrowPos, False)
583 startArrowPos += arrow.GetSize() + arrow.GetSpacing()
584 elif ah == ARROW_POSITION_END:
585 if arrow.GetXOffset() and not self._ignoreArrowOffsets:
586 self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
587 else:
588 self.DrawArrow(dc, arrow, endArrowPos, False)
589 endArrowPos += arrow.GetSize() + arrow.GetSpacing()
590 elif ah == ARROW_POSITION_MIDDLE:
591 arrow.SetXOffset(middleArrowPos)
592 if arrow.GetXOffset() and not self._ignoreArrowOffsets:
593 self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
594 else:
595 self.DrawArrow(dc, arrow, middleArrowPos, False)
596 middleArrowPos += arrow.GetSize() + arrow.GetSpacing()
597
598 def DrawArrow(self, dc, arrow, XOffset, proportionalOffset):
599 """Draw the given arrowhead (or annotation)."""
600 first_line_point = self._lineControlPoints[0]
601 second_line_point = self._lineControlPoints[1]
602
603 last_line_point = self._lineControlPoints[-1]
604 second_last_line_point = self._lineControlPoints[-2]
605
606 # Position of start point of line, at the end of which we draw the arrow
607 startPositionX, startPositionY = 0.0, 0.0
608
609 ap = arrow.GetPosition()
610 if ap == ARROW_POSITION_START:
611 # If we're using a proportional offset, calculate just where this
612 # will be on the line.
613 realOffset = XOffset
614 if proportionalOffset:
615 totalLength = math.sqrt((second_line_point[0]-first_line_point[0]) * (second_line_point[0]-first_line_point[0]) + (second_line_point[1]-first_line_point[1]) * (second_line_point[1]-first_line_point[1]))
616 realOffset = XOffset * totalLength
617
618 positionOnLineX, positionOnLineY = GetPointOnLine(second_line_point[0], second_line_point[1], first_line_point[0], first_line_point[1], realOffset)
619
620 startPositionX = second_line_point[0]
621 startPositionY = second_line_point[1]
622 elif ap == ARROW_POSITION_END:
623 # If we're using a proportional offset, calculate just where this
624 # will be on the line.
625 realOffset = XOffset
626 if proportionalOffset:
627 totalLength = math.sqrt((second_last_line_point[0]-last_line_point[0]) * (second_last_line_point[0]-last_line_point[0]) + (second_last_line_point[1]-last_line_point[1]) * (second_last_line_point[1]-last_line_point[1]));
628 realOffset = XOffset * totalLength
629
630 positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], last_line_point[0], last_line_point[1], realOffset)
631
632 startPositionX = second_last_line_point[0]
633 startPositionY = second_last_line_point[1]
634 elif ap == ARROW_POSITION_MIDDLE:
635 # Choose a point half way between the last and penultimate points
636 x = (last_line_point[0] + second_last_line_point[0]) / 2
637 y = (last_line_point[1] + second_last_line_point[1]) / 2
638
639 # If we're using a proportional offset, calculate just where this
640 # will be on the line.
641 realOffset = XOffset
642 if proportionalOffset:
643 totalLength = math.sqrt((second_last_line_point[0]-x) * (second_last_line_point[0]-x) + (second_last_line_point[1]-y) * (second_last_line_point[1]-y));
644 realOffset = XOffset * totalLength
645
646 positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], x, y, realOffset)
647 startPositionX = second_last_line_point[0]
648 startPositionY = second_last_line_point[1]
649
650 # Add yOffset to arrow, if any
651
652 # The translation that the y offset may give
653 deltaX = 0.0
654 deltaY = 0.0
655 if arrow.GetYOffset and not self._ignoreArrowOffsets:
656 # |(x4, y4)
657 # |d
658 # |
659 # (x1, y1)--------------(x3, y3)------------------(x2, y2)
660 # x4 = x3 - d * math.sin(theta)
661 # y4 = y3 + d * math.cos(theta)
662 #
663 # Where theta = math.tan(-1) of (y3-y1) / (x3-x1)
664 x1 = startPositionX
665 y1 = startPositionY
666 x3 = positionOnLineX
667 y3 = positionOnLineY
668 d=-arrow.GetYOffset() # Negate so +offset is above line
669
670 if x3 == x1:
671 theta = math.pi / 2
672 else:
673 theta = math.atan((y3-y1) / (x3-x1))
674
675 x4 = x3-d * math.sin(theta)
676 y4 = y3 + d * math.cos(theta)
677
678 deltaX = x4-positionOnLineX
679 deltaY = y4-positionOnLineY
680
681 at = arrow._GetType()
682 if at == ARROW_ARROW:
683 arrowLength = arrow.GetSize()
684 arrowWidth = arrowLength / 3
685
686 tip_x, tip_y, side1_x, side1_y, side2_x, side2_y = GetArrowPoints(startPositionX + deltaX, startPositionY + deltaY, positionOnLineX + deltaX, positionOnLineY + deltaY, arrowLength, arrowWidth)
687
688 points = [[tip_x, tip_y],
689 [side1_x, side1_y],
690 [side2_x, side2_y],
691 [tip_x, tip_y]]
692
693 dc.SetPen(self._pen)
694 dc.SetBrush(self._brush)
695 dc.DrawPolygon(points)
696 elif at in [ARROW_HOLLOW_CIRCLE, ARROW_FILLED_CIRCLE]:
697 # Find point on line of centre of circle, which is a radius away
698 # from the end position
699 diameter = arrow.GetSize()
700 x, y = GetPointOnLine(startPositionX + deltaX, startPositionY + deltaY,
701 positionOnLineX + deltaX, positionOnLineY + deltaY,
702 diameter / 2)
703 x1 = x-diameter / 2
704 y1 = y-diameter / 2
705 dc.SetPen(self._pen)
706 if arrow._GetType() == ARROW_HOLLOW_CIRCLE:
707 dc.SetBrush(self.GetBackgroundBrush())
708 else:
709 dc.SetBrush(self._brush)
710
711 dc.DrawEllipse(x1, y1, diameter, diameter)
712 elif at == ARROW_SINGLE_OBLIQUE:
713 pass
714 elif at == ARROW_METAFILE:
715 if arrow.GetMetaFile():
716 # Find point on line of centre of object, which is a half-width away
717 # from the end position
718 #
719 # width
720 # <-- start pos <-----><-- positionOnLineX
721 # _____
722 # --------------| x | <-- e.g. rectangular arrowhead
723 # -----
724 #
725 x, y = GetPointOnLine(startPositionX, startPositionY,
726 positionOnLineX, positionOnLineY,
727 arrow.GetMetaFile()._width / 2)
728 # Calculate theta for rotating the metafile.
729 #
730 # |
731 # | o(x2, y2) 'o' represents the arrowhead.
732 # | /
733 # | /
734 # | /theta
735 # | /(x1, y1)
736 # |______________________
737 #
738 theta = 0.0
739 x1 = startPositionX
740 y1 = startPositionY
741 x2 = positionOnLineX
742 y2 = positionOnLineY
743
744 if x1 == x2 and y1 == y2:
745 theta = 0.0
746 elif x1 == x2 and y1>y2:
747 theta = 3.0 * math.pi / 2
748 elif x1 == x2 and y2>y1:
749 theta = math.pi / 2
750 elif x2>x1 and y2 >= y1:
751 theta = math.atan((y2-y1) / (x2-x1))
752 elif x2<x1:
753 theta = math.pi + math.atan((y2-y1) / (x2-x1))
754 elif x2>x1 and y2<y1:
755 theta = 2 * math.pi + math.atan((y2-y1) / (x2-x1))
756 else:
757 raise "Unknown arrowhead rotation case"
758
759 # Rotate about the centre of the object, then place
760 # the object on the line.
761 if arrow.GetMetaFile().GetRotateable():
762 arrow.GetMetaFile().Rotate(0.0, 0.0, theta)
763
764 if self._erasing:
765 # If erasing, just draw a rectangle
766 minX, minY, maxX, maxY = arrow.GetMetaFile().GetBounds()
767 # Make erasing rectangle slightly bigger or you get droppings
768 extraPixels = 4
769 dc.DrawRectangle(deltaX + x + minX-extraPixels / 2, deltaY + y + minY-extraPixels / 2, maxX-minX + extraPixels, maxY-minY + extraPixels)
770 else:
771 arrow.GetMetaFile().Draw(dc, x + deltaX, y + deltaY)
772
773 def OnErase(self, dc):
774 old_pen = self._pen
775 old_brush = self._brush
776
777 bg_pen = self.GetBackgroundPen()
778 bg_brush = self.GetBackgroundBrush()
779 self.SetPen(bg_pen)
780 self.SetBrush(bg_brush)
781
782 bound_x, bound_y = self.GetBoundingBoxMax()
783 if self._font:
784 dc.SetFont(self._font)
785
786 # Undraw text regions
787 for i in range(3):
788 if self._regions[i]:
789 x, y = self.GetLabelPosition(i)
790 self.EraseRegion(dc, self._regions[i], x, y)
791
792 # Undraw line
793 dc.SetPen(self.GetBackgroundPen())
794 dc.SetBrush(self.GetBackgroundBrush())
795
796 # Drawing over the line only seems to work if the line has a thickness
797 # of 1.
798 if old_pen and old_pen.GetWidth()>1:
799 dc.DrawRectangle(self._xpos-bound_x / 2-2, self._ypos-bound_y / 2-2,
800 bound_x + 4, bound_y + 4)
801 else:
802 self._erasing = True
803 self.GetEventHandler().OnDraw(dc)
804 self.GetEventHandler().OnEraseControlPoints(dc)
805 self._erasing = False
806
807 if old_pen:
808 self.SetPen(old_pen)
809 if old_brush:
810 self.SetBrush(old_brush)
811
812 def GetBoundingBoxMin(self):
813 x1, y1 = 10000, 10000
814 x2, y2=-10000,-10000
815
816 for point in self._lineControlPoints:
817 if point[0]<x1:
818 x1 = point[0]
819 if point[1]<y1:
820 y1 = point[1]
821 if point[0]>x2:
822 x2 = point[0]
823 if point[1]>y2:
824 y2 = point[1]
825
826 return x2-x1, y2-y1
827
828 # For a node image of interest, finds the position of this arc
829 # amongst all the arcs which are attached to THIS SIDE of the node image,
830 # and the number of same.
831 def FindNth(self, image, incoming):
832 """Find the position of the line on the given object.
833
834 Specify whether incoming or outgoing lines are being considered
835 with incoming.
836 """
837 n=-1
838 num = 0
839
840 if image == self._to:
841 this_attachment = self._attachmentTo
842 else:
843 this_attachment = self._attachmentFrom
844
845 # Find number of lines going into / out of this particular attachment point
846 for line in image.GetLines():
847 if line._from == image:
848 # This is the nth line attached to 'image'
849 if line == self and not incoming:
850 n = num
851
852 # Increment num count if this is the same side (attachment number)
853 if line._attachmentFrom == this_attachment:
854 num += 1
855
856 if line._to == image:
857 # This is the nth line attached to 'image'
858 if line == self and incoming:
859 n = num
860
861 # Increment num count if this is the same side (attachment number)
862 if line._attachmentTo == this_attachment:
863 num += 1
864
865 return n, num
866
867 def OnDrawOutline(self, dc, x, y, w, h):
868 old_pen = self._pen
869 old_brush = self._brush
870
871 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
872 self.SetPen(dottedPen)
873 self.SetBrush(wx.TRANSPARENT_BRUSH)
874
875 self.GetEventHandler().OnDraw(dc)
876
877 if old_pen:
878 self.SetPen(old_pen)
879 else:
880 self.SetPen(None)
881 if old_brush:
882 self.SetBrush(old_brush)
883 else:
884 self.SetBrush(None)
885
886 def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
887 x_offset = x-old_x
888 y_offset = y-old_y
889
890 if self._lineControlPoints and not (x_offset == 0 and y_offset == 0):
891 for point in self._lineControlPoints:
892 point[0] += x_offset
893 point[1] += y_offset
894
895 # Move temporary label rectangles if necessary
896 for i in range(3):
897 if self._labelObjects[i]:
898 self._labelObjects[i].Erase(dc)
899 xp, yp = self.GetLabelPosition(i)
900 if i<len(self._regions):
901 xr, yr = self._regions[i].GetPosition()
902 else:
903 xr, yr = 0, 0
904 self._labelObjects[i].Move(dc, xp + xr, yp + yr)
905 return True
906
907 def OnMoveLink(self, dc, moveControlPoints = True):
908 """Called when a connected object has moved, to move the link to
909 correct position
910 """
911 if not self._from or not self._to:
912 return
913
914 if len(self._lineControlPoints)>2:
915 self.Initialise()
916
917 # Do each end - nothing in the middle. User has to move other points
918 # manually if necessary
919 end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints()
920
921 first = self._lineControlPoints[0]
922 last = self._lineControlPoints[-1]
923
924 oldX, oldY = self._xpos, self._ypos
925
926 self.SetEnds(end_x, end_y, other_end_x, other_end_y)
927
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)
931
932 # Try to move control points with the arc
933 x_offset = self._xpos-oldX
934 y_offset = self._ypos-oldY
935
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]:
940 point.x += x_offset
941 point.y += y_offset
942
943 self.Move(dc, self._xpos, self._ypos)
944
945 def FindLineEndPoints(self):
946 """Finds the x, y points at the two ends of the line.
947
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
950 drawn to / from.
951 """
952 if not self._from or not self._to:
953 return
954
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]
959
960 if len(self._lineControlPoints)>2:
961 if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
962 nth, no_arcs = self.FindNth(self._from, False) # Not incoming
963 end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
964 else:
965 end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), second_point[0], second_point[1])
966
967 if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
968 nth, no_arch = self.FindNth(self._to, True) # Incoming
969 other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arch, self)
970 else:
971 other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), second_last_point[0], second_last_point[1])
972 else:
973 fromX = self._from.GetX()
974 fromY = self._from.GetY()
975 toX = self._to.GetX()
976 toY = self._to.GetY()
977
978 if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
979 nth, no_arcs = self.FindNth(self._from, False)
980 end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
981 fromX = end_x
982 fromY = end_y
983
984 if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
985 nth, no_arcs = self.FindNth(self._to, True)
986 other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arcs, self)
987 toX = other_end_x
988 toY = other_end_y
989
990 if self._from.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
991 end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), toX, toY)
992
993 if self._to.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
994 other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), fromX, fromY)
995
996 #print type(self._from), type(self._to), end_x, end_y, other_end_x, other_end_y
997 return end_x, end_y, other_end_x, other_end_y
998
999 def OnDraw(self, dc):
1000 if not self._lineControlPoints:
1001 return
1002
1003 if self._pen:
1004 dc.SetPen(self._pen)
1005 if self._brush:
1006 dc.SetBrush(self._brush)
1007
1008 points = []
1009 for point in self._lineControlPoints:
1010 points.append(wx.Point(point.x, point.y))
1011
1012 #print points
1013 if self._isSpline:
1014 dc.DrawSpline(points)
1015 else:
1016 dc.DrawLines(points)
1017
1018 if sys.platform[:3]=="win":
1019 # For some reason, last point isn't drawn under Windows
1020 pt = points[-1]
1021 dc.DrawPoint(pt.x, pt.y)
1022
1023 # Problem with pen - if not a solid pen, does strange things
1024 # to the arrowhead. So make (get) a new pen that's solid.
1025 if self._pen and self._pen.GetStyle() != wx.SOLID:
1026 solid_pen = wx.ThePenList().FindOrCreatePen(self._pen.GetColour(), 1, wx.SOLID)
1027 if solid_pen:
1028 dc.SetPen(solid_pen)
1029
1030 self.DrawArrows(dc)
1031
1032 def OnDrawControlPoints(self, dc):
1033 if not self._drawHandles:
1034 return
1035
1036 # Draw temporary label rectangles if necessary
1037 for i in range(3):
1038 if self._labelObjects[i]:
1039 self._labelObjects[i].Draw(dc)
1040
1041 Shape.OnDrawControlPoints(self, dc)
1042
1043 def OnEraseControlPoints(self, dc):
1044 # Erase temporary label rectangles if necessary
1045
1046 for i in range(3):
1047 if self._labelObjects[i]:
1048 self._labelObjects[i].Erase(dc)
1049
1050 Shape.OnEraseControlPoints(self, dc)
1051
1052 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
1053 pass
1054
1055 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1056 pass
1057
1058 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1059 pass
1060
1061 def OnDrawContents(self, dc):
1062 if self.GetDisableLabel():
1063 return
1064
1065 for i in range(3):
1066 if self._regions[i]:
1067 x, y = self.GetLabelPosition(i)
1068 self.DrawRegion(dc, self._regions[i], x, y)
1069
1070 def SetTo(self, object):
1071 """Set the 'to' object for the line."""
1072 self._to = object
1073
1074 def SetFrom(self, object):
1075 """Set the 'from' object for the line."""
1076 self._from = object
1077
1078 def MakeControlPoints(self):
1079 """Make handle control points."""
1080 if self._canvas and self._lineControlPoints:
1081 first = self._lineControlPoints[0]
1082 last = self._lineControlPoints[-1]
1083
1084 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, first[0], first[1], CONTROL_POINT_ENDPOINT_FROM)
1085 control._point = first
1086 self._canvas.AddShape(control)
1087 self._controlPoints.Append(control)
1088
1089 for point in self._lineControlPoints[1:-1]:
1090 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point[0], point[1], CONTROL_POINT_LINE)
1091 control._point = point
1092 self._canvas.AddShape(control)
1093 self._controlPoints.Append(control)
1094
1095 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, last[0], last[1], CONTROL_POINT_ENDPOINT_TO)
1096 control._point = last
1097 self._canvas.AddShape(control)
1098 self._controlPoints.Append(control)
1099
1100 def ResetControlPoints(self):
1101 if self._canvas and self._lineControlPoints:
1102 for i in range(min(len(self._controlPoints), len(self._lineControlPoints))):
1103 point = self._lineControlPoints[i]
1104 control = self._controlPoints[i]
1105 control.SetX(point[0])
1106 control.SetY(point[1])
1107
1108 # Override select, to create / delete temporary label-moving objects
1109 def Select(self, select, dc = None):
1110 Shape.Select(self, select, dc)
1111 if select:
1112 for i in range(3):
1113 if self._regions[i]:
1114 region = self._regions[i]
1115 if region._formattedText:
1116 w, h = region.GetSize()
1117 x, y = region.GetPosition()
1118 xx, yy = self.GetLabelPosition(i)
1119
1120 if self._labelObjects[i]:
1121 self._labelObjects[i].Select(False)
1122 self._labelObjects[i].RemoveFromCanvas(self._canvas)
1123
1124 self._labelObjects[i] = self.OnCreateLabelShape(self, region, w, h)
1125 self._labelObjects[i].AddToCanvas(self._canvas)
1126 self._labelObjects[i].Show(True)
1127 if dc:
1128 self._labelObjects[i].Move(dc, x + xx, y + yy)
1129 self._labelObjects[i].Select(True, dc)
1130 else:
1131 for i in range(3):
1132 if self._labelObjects[i]:
1133 self._labelObjects[i].Select(False, dc)
1134 self._labelObjects[i].Erase(dc)
1135 self._labelObjects[i].RemoveFromCanvas(self._canvas)
1136 self._labelObjects[i] = None
1137
1138 # Control points ('handles') redirect control to the actual shape, to
1139 # make it easier to override sizing behaviour.
1140 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
1141 dc = wx.ClientDC(self.GetCanvas())
1142 self.GetCanvas().PrepareDC(dc)
1143
1144 dc.SetLogicalFunction(OGLRBLF)
1145
1146 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1147 dc.SetPen(dottedPen)
1148 dc.SetBrush(wx.TRANSPARENT_BRUSH)
1149
1150 if pt._type == CONTROL_POINT_LINE:
1151 x, y = self._canvas.Snap()
1152
1153 pt.SetX(x)
1154 pt.SetY(y)
1155 pt._point[0] = x
1156 pt._point[1] = y
1157
1158 old_pen = self.GetPen()
1159 old_brush = self.GetBrush()
1160
1161 self.SetPen(dottedPen)
1162 self.SetBrush(wx.TRANSPARENT_BRUSH)
1163
1164 self.GetEventHandler().OnMoveLink(dc, False)
1165
1166 self.SetPen(old_pen)
1167 self.SetBrush(old_brush)
1168
1169 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
1170 dc = wx.ClientDC(self.GetCanvas())
1171 self.GetCanvas().PrepareDC(dc)
1172
1173 if pt._type == CONTROL_POINT_LINE:
1174 pt._originalPos = pt._point
1175 x, y = self._canvas.Snap()
1176
1177 self.Erase(dc)
1178
1179 # Redraw start and end objects because we've left holes
1180 # when erasing the line
1181 self.GetFrom().OnDraw(dc)
1182 self.GetFrom().OnDrawContents(dc)
1183 self.GetTo().OnDraw(dc)
1184 self.GetTo().OnDrawContents(dc)
1185
1186 self.SetDisableLabel(True)
1187 dc.SetLogicalFunction(OGLRBLF)
1188
1189 pt._xpos = x
1190 pt._ypos = y
1191 pt._point[0] = x
1192 pt._point[1] = y
1193
1194 old_pen = self.GetPen()
1195 old_brush = self.GetBrush()
1196
1197 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1198 self.SetPen(dottedPen)
1199 self.SetBrush(wx.TRANSPARENT_BRUSH)
1200
1201 self.GetEventHandler().OnMoveLink(dc, False)
1202
1203 self.SetPen(old_pen)
1204 self.SetBrush(old_brush)
1205
1206 if pt._type == CONTROL_POINT_ENDPOINT_FROM or pt._type == CONTROL_POINT_ENDPOINT_TO:
1207 self._canvas.SetCursor(wx.Cursor(wx.CURSOR_BULLSEYE))
1208 pt._oldCursor = wx.STANDARD_CURSOR
1209
1210 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
1211 dc = wx.ClientDC(self.GetCanvas())
1212 self.GetCanvas().PrepareDC(dc)
1213
1214 self.SetDisableLabel(False)
1215
1216 if pt._type == CONTROL_POINT_LINE:
1217 x, y = self._canvas.Snap()
1218
1219 rpt = wx.RealPoint(x, y)
1220
1221 # Move the control point back to where it was;
1222 # MoveControlPoint will move it to the new position
1223 # if it decides it wants. We only moved the position
1224 # during user feedback so we could redraw the line
1225 # as it changed shape.
1226 pt._xpos = pt._originalPos[0]
1227 pt._ypos = pt._originalPos[1]
1228 pt._point[0] = pt._originalPos[0]
1229 pt._point[1] = pt._originalPos[1]
1230
1231 self.OnMoveMiddleControlPoint(dc, pt, rpt)
1232
1233 if pt._type == CONTROL_POINT_ENDPOINT_FROM:
1234 if pt._oldCursor:
1235 self._canvas.SetCursor(pt._oldCursor)
1236
1237 if self.GetFrom():
1238 self.GetFrom().MoveLineToNewAttachment(dc, self, x, y)
1239
1240 if pt._type == CONTROL_POINT_ENDPOINT_TO:
1241 if pt._oldCursor:
1242 self._canvas.SetCursor(pt._oldCursor)
1243
1244 if self.GetTo():
1245 self.GetTo().MoveLineToNewAttachment(dc, self, x, y)
1246
1247 # This is called only when a non-end control point is moved
1248 def OnMoveMiddleControlPoint(self, dc, lpt, pt):
1249 lpt._xpos = pt[0]
1250 lpt._ypos = pt[1]
1251
1252 lpt._point[0] = pt[0]
1253 lpt._point[1] = pt[1]
1254
1255 self.GetEventHandler().OnMoveLink(dc)
1256
1257 return True
1258
1259 def AddArrow(self, type, end = ARROW_POSITION_END, size = 10.0, xOffset = 0.0, name="",mf = None, arrowId=-1):
1260 """Add an arrow (or annotation) to the line.
1261
1262 type may currently be one of:
1263
1264 ARROW_HOLLOW_CIRCLE
1265 Hollow circle.
1266 ARROW_FILLED_CIRCLE
1267 Filled circle.
1268 ARROW_ARROW
1269 Conventional arrowhead.
1270 ARROW_SINGLE_OBLIQUE
1271 Single oblique stroke.
1272 ARROW_DOUBLE_OBLIQUE
1273 Double oblique stroke.
1274 ARROW_DOUBLE_METAFILE
1275 Custom arrowhead.
1276
1277 end may currently be one of:
1278
1279 ARROW_POSITION_END
1280 Arrow appears at the end.
1281 ARROW_POSITION_START
1282 Arrow appears at the start.
1283
1284 arrowSize specifies the length of the arrow.
1285
1286 xOffset specifies the offset from the end of the line.
1287
1288 name specifies a name for the arrow.
1289
1290 mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows
1291 metafile.
1292
1293 arrowId is the id for the arrow.
1294 """
1295 arrow = ArrowHead(type, end, size, xOffset, name, mf, arrowId)
1296 self._arcArrows.append(arrow)
1297 return arrow
1298
1299 # Add arrowhead at a particular position in the arrowhead list
1300 def AddArrowOrdered(self, arrow, referenceList, end):
1301 """Add an arrowhead in the position indicated by the reference list
1302 of arrowheads, which contains all legal arrowheads for this line, in
1303 the correct order. E.g.
1304
1305 Reference list: a b c d e
1306 Current line list: a d
1307
1308 Add c, then line list is: a c d.
1309
1310 If no legal arrowhead position, return FALSE. Assume reference list
1311 is for one end only, since it potentially defines the ordering for
1312 any one of the 3 positions. So we don't check the reference list for
1313 arrowhead position.
1314 """
1315 if not referenceList:
1316 return False
1317
1318 targetName = arrow.GetName()
1319
1320 # First check whether we need to insert in front of list,
1321 # because this arrowhead is the first in the reference
1322 # list and should therefore be first in the current list.
1323 refArrow = referenceList[0]
1324 if refArrow.GetName() == targetName:
1325 self._arcArrows.insert(0, arrow)
1326 return True
1327
1328 i1 = i2 = 0
1329 while i1<len(referenceList) and i2<len(self._arcArrows):
1330 refArrow = referenceList[i1]
1331 currArrow = self._arcArrows[i2]
1332
1333 # Matching: advance current arrow pointer
1334 if currArrow.GetArrowEnd() == end and currArrow.GetName() == refArrow.GetName():
1335 i2 += 1
1336
1337 # Check if we're at the correct position in the
1338 # reference list
1339 if targetName == refArrow.GetName():
1340 if i2<len(self._arcArrows):
1341 self._arcArrows.insert(i2, arrow)
1342 else:
1343 self._arcArrows.append(arrow)
1344 return True
1345 i1 += 1
1346
1347 self._arcArrows.append(arrow)
1348 return True
1349
1350 def ClearArrowsAtPosition(self, end):
1351 """Delete the arrows at the specified position, or at any position
1352 if position is -1.
1353 """
1354 if end==-1:
1355 self._arcArrows = []
1356 return
1357
1358 for arrow in self._arcArrows:
1359 if arrow.GetArrowEnd() == end:
1360 self._arcArrows.remove(arrow)
1361
1362 def ClearArrow(self, name):
1363 """Delete the arrow with the given name."""
1364 for arrow in self._arcArrows:
1365 if arrow.GetName() == name:
1366 self._arcArrows.remove(arrow)
1367 return True
1368 return False
1369
1370 def FindArrowHead(self, position, name):
1371 """Find arrowhead by position and name.
1372
1373 if position is -1, matches any position.
1374 """
1375 for arrow in self._arcArrows:
1376 if (position==-1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
1377 return arow
1378
1379 return None
1380
1381 def FindArrowHeadId(self, arrowId):
1382 """Find arrowhead by id."""
1383 for arrow in self._arcArrows:
1384 if arrowId == arrow.GetId():
1385 return arrow
1386
1387 return None
1388
1389 def DeleteArrowHead(self, position, name):
1390 """Delete arrowhead by position and name.
1391
1392 if position is -1, matches any position.
1393 """
1394 for arrow in self._arcArrows:
1395 if (position==-1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
1396 self._arcArrows.remove(arrow)
1397 return True
1398 return False
1399
1400 def DeleteArrowHeadId(self, id):
1401 """Delete arrowhead by id."""
1402 for arrow in self._arcArrows:
1403 if arrowId == arrow.GetId():
1404 self._arcArrows.remove(arrow)
1405 return True
1406 return False
1407
1408 # Calculate the minimum width a line
1409 # occupies, for the purposes of drawing lines in tools.
1410 def FindMinimumWidth(self):
1411 """Find the horizontal width for drawing a line with arrows in
1412 minimum space. Assume arrows at end only.
1413 """
1414 minWidth = 0.0
1415 for arrowHead in self._arcArrows:
1416 minWidth += arrowHead.GetSize()
1417 if arrowHead != self._arcArrows[-1]:
1418 minWidth += arrowHead + GetSpacing
1419
1420 # We have ABSOLUTE minimum now. So
1421 # scale it to give it reasonable aesthetics
1422 # when drawing with line.
1423 if minWidth>0:
1424 minWidth = minWidth * 1.4
1425 else:
1426 minWidth = 20.0
1427
1428 self.SetEnds(0.0, 0.0, minWidth, 0.0)
1429 self.Initialise()
1430
1431 return minWidth
1432
1433 def FindLinePosition(self, x, y):
1434 """Find which position we're talking about at this x, y.
1435
1436 Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END.
1437 """
1438 startX, startY, endX, endY = self.GetEnds()
1439
1440 # Find distances from centre, start and end. The smallest wins
1441 centreDistance = math.sqrt((x-self._xpos) * (x-self._xpos) + (y-self._ypos) * (y-self._ypos))
1442 startDistance = math.sqrt((x-startX) * (x-startX) + (y-startY) * (y-startY))
1443 endDistance = math.sqrt((x-endX) * (x-endX) + (y-endY) * (y-endY))
1444
1445 if centreDistance<startDistance and centreDistance<endDistance:
1446 return ARROW_POSITION_MIDDLE
1447 elif startDistance<endDistance:
1448 return ARROW_POSITION_START
1449 else:
1450 return ARROW_POSITION_END
1451
1452 def SetAlignmentOrientation(self, isEnd, isHoriz):
1453 if isEnd:
1454 if isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
1455 self._alignmentEnd != LINE_ALIGNMENT_HORIZ
1456 elif not isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
1457 self._alignmentEnd -= LINE_ALIGNMENT_HORIZ
1458 else:
1459 if isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
1460 self._alignmentStart != LINE_ALIGNMENT_HORIZ
1461 elif not isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
1462 self._alignmentStart -= LINE_ALIGNMENT_HORIZ
1463
1464 def SetAlignmentType(self, isEnd, alignType):
1465 if isEnd:
1466 if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1467 if self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
1468 self._alignmentEnd |= LINE_ALIGNMENT_TO_NEXT_HANDLE
1469 elif self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1470 self._alignmentEnd -= LINE_ALIGNMENT_TO_NEXT_HANDLE
1471 else:
1472 if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1473 if self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
1474 self._alignmentStart |= LINE_ALIGNMENT_TO_NEXT_HANDLE
1475 elif self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1476 self._alignmentStart -= LINE_ALIGNMENT_TO_NEXT_HANDLE
1477
1478 def GetAlignmentOrientation(self, isEnd):
1479 if isEnd:
1480 return self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
1481 else:
1482 return self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
1483
1484 def GetAlignmentType(self, isEnd):
1485 if isEnd:
1486 return self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE
1487 else:
1488 return self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE
1489
1490 def GetNextControlPoint(self, shape):
1491 """Find the next control point in the line after the start / end point,
1492 depending on whether the shape is at the start or end.
1493 """
1494 n = len(self._lineControlPoints)
1495 if self._to == shape:
1496 # Must be END of line, so we want (n - 1)th control point.
1497 # But indexing ends at n-1, so subtract 2.
1498 nn = n-2
1499 else:
1500 nn = 1
1501 if nn<len(self._lineControlPoints):
1502 return self._lineControlPoints[nn]
1503 return None
1504
1505 def OnCreateLabelShape(self, parent, region, w, h):
1506 return LabelShape(parent, region, w, h)
1507
1508
1509 def OnLabelMovePre(self, dc, labelShape, x, y, old_x, old_y, display):
1510 labelShape._shapeRegion.SetSize(labelShape.GetWidth(), labelShape.GetHeight())
1511
1512 # Find position in line's region list
1513 i = 0
1514 for region in self.GetRegions():
1515 if labelShape._shapeRegion == region:
1516 self.GetRegions().remove(region)
1517 else:
1518 i += 1
1519
1520 xx, yy = self.GetLabelPosition(i)
1521 # Set the region's offset, relative to the default position for
1522 # each region.
1523 labelShape._shapeRegion.SetPosition(x-xx, y-yy)
1524 labelShape.SetX(x)
1525 labelShape.SetY(y)
1526
1527 # Need to reformat to fit region
1528 if labelShape._shapeRegion.GetText():
1529 s = labelShape._shapeRegion.GetText()
1530 labelShape.FormatText(dc, s, i)
1531 self.DrawRegion(dc, labelShape._shapeRegion, xx, yy)
1532 return True
1533