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