]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ogl/_lines.py
OGL fixes from Davide Salomoni
[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, 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.ThePenList.FindOrCreatePen(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):
302 """Insert a control point at an arbitrary position."""
303 if dc:
304 self.Erase(dc)
305
306 last_point = self._lineControlPoints[-1]
307 second_last_point = self._lineControlPoints[-2]
308
309 line_x = (last_point[0] + second_last_point[0]) / 2.0
310 line_y = (last_point[1] + second_last_point[1]) / 2.0
311
312 point = wx.RealPoint(line_x, line_y)
313 self._lineControlPoints.insert(len(self._lineControlPoints), point)
314
315 def DeleteLineControlPoint(self):
316 """Delete an arbitary point on the line."""
317 if len(self._lineControlPoints) < 3:
318 return False
319
320 del self._lineControlPoints[-2]
321 return True
322
323 def Initialise(self):
324 """Initialise the line object."""
325 if self._lineControlPoints:
326 # Just move the first and last control points
327 first_point = self._lineControlPoints[0]
328 last_point = self._lineControlPoints[-1]
329
330 # If any of the line points are at -999, we must
331 # initialize them by placing them half way between the first
332 # and the last.
333
334 for i in range(1,len(self._lineControlPoints)):
335 point = self._lineControlPoints[i]
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 self._lineControlPoints[i] = wx.RealPoint((x2 - x1) / 2.0 + x1, (y2 - y1) / 2.0 + y1)
350 self._initialised = True
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.0, yp - h / 2.0, 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.0, yp - h / 2.0, 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.0)
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.0, point[1] + dy / 2.0
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 self._lineControlPoints[0] = wx.RealPoint(x1, y1)
496 self._lineControlPoints[-1] = wx.RealPoint(x2, y2)
497
498 # Find centre point
499 self._xpos = (x1 + x2) / 2.0
500 self._ypos = (y1 + y2) / 2.0
501
502 # Get absolute positions of ends
503 def GetEnds(self):
504 """Get the visible endpoints of the lines for drawing between two objects."""
505 first_point = self._lineControlPoints[0]
506 last_point = self._lineControlPoints[-1]
507
508 return first_point[0], first_point[1], last_point[0], last_point[1]
509
510 def SetAttachments(self, from_attach, to_attach):
511 """Specify which object attachment points should be used at each end
512 of the line.
513 """
514 self._attachmentFrom = from_attach
515 self._attachmentTo = to_attach
516
517 def HitTest(self, x, y):
518 if not self._lineControlPoints:
519 return False
520
521 # Look at label regions in case mouse is over a label
522 inLabelRegion = False
523 for i in range(3):
524 if self._regions[i]:
525 region = self._regions[i]
526 if len(region._formattedText):
527 xp, yp = self.GetLabelPosition(i)
528 # Offset region from default label position
529 cx, cy = region.GetPosition()
530 cw, ch = region.GetSize()
531 cx += xp
532 cy += yp
533
534 rLeft = cx - cw / 2.0
535 rTop = cy - ch / 2.0
536 rRight = cx + cw / 2.0
537 rBottom = cy + ch / 2.0
538 if x > rLeft and x < rRight and y > rTop and y < rBottom:
539 inLabelRegion = True
540 break
541
542 for i in range(len(self._lineControlPoints) - 1):
543 point1 = self._lineControlPoints[i]
544 point2 = self._lineControlPoints[i + 1]
545
546 # For inaccurate mousing allow 8 pixel corridor
547 extra = 4
548
549 dx = point2[0] - point1[0]
550 dy = point2[1] - point1[1]
551
552 seg_len = math.sqrt(dx * dx + dy * dy)
553 if dy == 0 and dx == 0:
554 continue
555 distance_from_seg = seg_len * float((x - point1[0]) * dy - (y - point1[1]) * dx) / (dy * dy + dx * dx)
556 distance_from_prev = seg_len * float((y - point1[1]) * dy + (x - point1[0]) * dx) / (dy * dy + dx * dx)
557
558 if abs(distance_from_seg) < extra and distance_from_prev >= 0 and distance_from_prev <= seg_len or inLabelRegion:
559 return 0, distance_from_seg
560
561 return False
562
563 def DrawArrows(self, dc):
564 """Draw all arrows."""
565 # Distance along line of each arrow: space them out evenly
566 startArrowPos = 0.0
567 endArrowPos = 0.0
568 middleArrowPos = 0.0
569
570 for arrow in self._arcArrows:
571 ah = arrow.GetArrowEnd()
572 if ah == ARROW_POSITION_START:
573 if arrow.GetXOffset() and not self._ignoreArrowOffsets:
574 # If specified, x offset is proportional to line length
575 self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
576 else:
577 self.DrawArrow(dc, arrow, startArrowPos, False)
578 startArrowPos += arrow.GetSize() + arrow.GetSpacing()
579 elif ah == ARROW_POSITION_END:
580 if arrow.GetXOffset() and not self._ignoreArrowOffsets:
581 self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
582 else:
583 self.DrawArrow(dc, arrow, endArrowPos, False)
584 endArrowPos += arrow.GetSize() + arrow.GetSpacing()
585 elif ah == ARROW_POSITION_MIDDLE:
586 arrow.SetXOffset(middleArrowPos)
587 if arrow.GetXOffset() and not self._ignoreArrowOffsets:
588 self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
589 else:
590 self.DrawArrow(dc, arrow, middleArrowPos, False)
591 middleArrowPos += arrow.GetSize() + arrow.GetSpacing()
592
593 def DrawArrow(self, dc, arrow, XOffset, proportionalOffset):
594 """Draw the given arrowhead (or annotation)."""
595 first_line_point = self._lineControlPoints[0]
596 second_line_point = self._lineControlPoints[1]
597
598 last_line_point = self._lineControlPoints[-1]
599 second_last_line_point = self._lineControlPoints[-2]
600
601 # Position of start point of line, at the end of which we draw the arrow
602 startPositionX, startPositionY = 0.0, 0.0
603
604 ap = arrow.GetPosition()
605 if ap == ARROW_POSITION_START:
606 # If we're using a proportional offset, calculate just where this
607 # will be on the line.
608 realOffset = XOffset
609 if proportionalOffset:
610 totalLength = math.sqrt((second_line_point[0] - first_line_point[0]) * (second_line_point[0] - first_line_point[0]) + (second_line_point[1] - first_line_point[1]) * (second_line_point[1] - first_line_point[1]))
611 realOffset = XOffset * totalLength
612
613 positionOnLineX, positionOnLineY = GetPointOnLine(second_line_point[0], second_line_point[1], first_line_point[0], first_line_point[1], realOffset)
614
615 startPositionX = second_line_point[0]
616 startPositionY = second_line_point[1]
617 elif ap == ARROW_POSITION_END:
618 # If we're using a proportional offset, calculate just where this
619 # will be on the line.
620 realOffset = XOffset
621 if proportionalOffset:
622 totalLength = math.sqrt((second_last_line_point[0] - last_line_point[0]) * (second_last_line_point[0] - last_line_point[0]) + (second_last_line_point[1] - last_line_point[1]) * (second_last_line_point[1] - last_line_point[1]));
623 realOffset = XOffset * totalLength
624
625 positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], last_line_point[0], last_line_point[1], realOffset)
626
627 startPositionX = second_last_line_point[0]
628 startPositionY = second_last_line_point[1]
629 elif ap == ARROW_POSITION_MIDDLE:
630 # Choose a point half way between the last and penultimate points
631 x = (last_line_point[0] + second_last_line_point[0]) / 2.0
632 y = (last_line_point[1] + second_last_line_point[1]) / 2.0
633
634 # If we're using a proportional offset, calculate just where this
635 # will be on the line.
636 realOffset = XOffset
637 if proportionalOffset:
638 totalLength = math.sqrt((second_last_line_point[0] - x) * (second_last_line_point[0] - x) + (second_last_line_point[1] - y) * (second_last_line_point[1] - y));
639 realOffset = XOffset * totalLength
640
641 positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], x, y, realOffset)
642 startPositionX = second_last_line_point[0]
643 startPositionY = second_last_line_point[1]
644
645 # Add yOffset to arrow, if any
646
647 # The translation that the y offset may give
648 deltaX = 0.0
649 deltaY = 0.0
650 if arrow.GetYOffset and not self._ignoreArrowOffsets:
651 # |(x4, y4)
652 # |d
653 # |
654 # (x1, y1)--------------(x3, y3)------------------(x2, y2)
655 # x4 = x3 - d * math.sin(theta)
656 # y4 = y3 + d * math.cos(theta)
657 #
658 # Where theta = math.tan(-1) of (y3-y1) / (x3-x1)
659 x1 = startPositionX
660 y1 = startPositionY
661 x3 = float(positionOnLineX)
662 y3 = float(positionOnLineY)
663 d = -arrow.GetYOffset() # Negate so +offset is above line
664
665 if x3 == x1:
666 theta = math.pi / 2.0
667 else:
668 theta = math.atan((y3 - y1) / (x3 - x1))
669
670 x4 = x3 - d * math.sin(theta)
671 y4 = y3 + d * math.cos(theta)
672
673 deltaX = x4 - positionOnLineX
674 deltaY = y4 - positionOnLineY
675
676 at = arrow._GetType()
677 if at == ARROW_ARROW:
678 arrowLength = arrow.GetSize()
679 arrowWidth = arrowLength / 3.0
680
681 tip_x, tip_y, side1_x, side1_y, side2_x, side2_y = GetArrowPoints(startPositionX + deltaX, startPositionY + deltaY, positionOnLineX + deltaX, positionOnLineY + deltaY, arrowLength, arrowWidth)
682
683 points = [[tip_x, tip_y],
684 [side1_x, side1_y],
685 [side2_x, side2_y],
686 [tip_x, tip_y]]
687
688 dc.SetPen(self._pen)
689 dc.SetBrush(self._brush)
690 dc.DrawPolygon(points)
691 elif at in [ARROW_HOLLOW_CIRCLE, ARROW_FILLED_CIRCLE]:
692 # Find point on line of centre of circle, which is a radius away
693 # from the end position
694 diameter = arrow.GetSize()
695 x, y = GetPointOnLine(startPositionX + deltaX, startPositionY + deltaY,
696 positionOnLineX + deltaX, positionOnLineY + deltaY,
697 diameter / 2.0)
698 x1 = x - diameter / 2.0
699 y1 = y - diameter / 2.0
700 dc.SetPen(self._pen)
701 if arrow._GetType() == ARROW_HOLLOW_CIRCLE:
702 dc.SetBrush(self.GetBackgroundBrush())
703 else:
704 dc.SetBrush(self._brush)
705
706 dc.DrawEllipse(x1, y1, diameter, diameter)
707 elif at == ARROW_SINGLE_OBLIQUE:
708 pass
709 elif at == ARROW_METAFILE:
710 if arrow.GetMetaFile():
711 # Find point on line of centre of object, which is a half-width away
712 # from the end position
713 #
714 # width
715 # <-- start pos <-----><-- positionOnLineX
716 # _____
717 # --------------| x | <-- e.g. rectangular arrowhead
718 # -----
719 #
720 x, y = GetPointOnLine(startPositionX, startPositionY,
721 positionOnLineX, positionOnLineY,
722 arrow.GetMetaFile()._width / 2.0)
723 # Calculate theta for rotating the metafile.
724 #
725 # |
726 # | o(x2, y2) 'o' represents the arrowhead.
727 # | /
728 # | /
729 # | /theta
730 # | /(x1, y1)
731 # |______________________
732 #
733 theta = 0.0
734 x1 = startPositionX
735 y1 = startPositionY
736 x2 = float(positionOnLineX)
737 y2 = float(positionOnLineY)
738
739 if x1 == x2 and y1 == y2:
740 theta = 0.0
741 elif x1 == x2 and y1 > y2:
742 theta = 3.0 * math.pi / 2.0
743 elif x1 == x2 and y2 > y1:
744 theta = math.pi / 2.0
745 elif x2 > x1 and y2 >= y1:
746 theta = math.atan((y2 - y1) / (x2 - x1))
747 elif x2 < x1:
748 theta = math.pi + math.atan((y2 - y1) / (x2 - x1))
749 elif x2 > x1 and y2 < y1:
750 theta = 2 * math.pi + math.atan((y2 - y1) / (x2 - x1))
751 else:
752 raise "Unknown arrowhead rotation case"
753
754 # Rotate about the centre of the object, then place
755 # the object on the line.
756 if arrow.GetMetaFile().GetRotateable():
757 arrow.GetMetaFile().Rotate(0.0, 0.0, theta)
758
759 if self._erasing:
760 # If erasing, just draw a rectangle
761 minX, minY, maxX, maxY = arrow.GetMetaFile().GetBounds()
762 # Make erasing rectangle slightly bigger or you get droppings
763 extraPixels = 4
764 dc.DrawRectangle(deltaX + x + minX - extraPixels / 2.0, deltaY + y + minY - extraPixels / 2.0, maxX - minX + extraPixels, maxY - minY + extraPixels)
765 else:
766 arrow.GetMetaFile().Draw(dc, x + deltaX, y + deltaY)
767
768 def OnErase(self, dc):
769 old_pen = self._pen
770 old_brush = self._brush
771
772 bg_pen = self.GetBackgroundPen()
773 bg_brush = self.GetBackgroundBrush()
774 self.SetPen(bg_pen)
775 self.SetBrush(bg_brush)
776
777 bound_x, bound_y = self.GetBoundingBoxMax()
778 if self._font:
779 dc.SetFont(self._font)
780
781 # Undraw text regions
782 for i in range(3):
783 if self._regions[i]:
784 x, y = self.GetLabelPosition(i)
785 self.EraseRegion(dc, self._regions[i], x, y)
786
787 # Undraw line
788 dc.SetPen(self.GetBackgroundPen())
789 dc.SetBrush(self.GetBackgroundBrush())
790
791 # Drawing over the line only seems to work if the line has a thickness
792 # of 1.
793 if old_pen and old_pen.GetWidth() > 1:
794 dc.DrawRectangle(self._xpos - bound_x / 2.0 - 2, self._ypos - bound_y / 2.0 - 2,
795 bound_x + 4, bound_y + 4)
796 else:
797 self._erasing = True
798 self.GetEventHandler().OnDraw(dc)
799 self.GetEventHandler().OnEraseControlPoints(dc)
800 self._erasing = False
801
802 if old_pen:
803 self.SetPen(old_pen)
804 if old_brush:
805 self.SetBrush(old_brush)
806
807 def GetBoundingBoxMin(self):
808 x1, y1 = 10000, 10000
809 x2, y2 = -10000, -10000
810
811 for point in self._lineControlPoints:
812 if point[0] < x1:
813 x1 = point[0]
814 if point[1] < y1:
815 y1 = point[1]
816 if point[0] > x2:
817 x2 = point[0]
818 if point[1] > y2:
819 y2 = point[1]
820
821 return x2 - x1, y2 - y1
822
823 # For a node image of interest, finds the position of this arc
824 # amongst all the arcs which are attached to THIS SIDE of the node image,
825 # and the number of same.
826 def FindNth(self, image, incoming):
827 """Find the position of the line on the given object.
828
829 Specify whether incoming or outgoing lines are being considered
830 with incoming.
831 """
832 n = -1
833 num = 0
834
835 if image == self._to:
836 this_attachment = self._attachmentTo
837 else:
838 this_attachment = self._attachmentFrom
839
840 # Find number of lines going into / out of this particular attachment point
841 for line in image.GetLines():
842 if line._from == image:
843 # This is the nth line attached to 'image'
844 if line == self and not incoming:
845 n = num
846
847 # Increment num count if this is the same side (attachment number)
848 if line._attachmentFrom == this_attachment:
849 num += 1
850
851 if line._to == image:
852 # This is the nth line attached to 'image'
853 if line == self and incoming:
854 n = num
855
856 # Increment num count if this is the same side (attachment number)
857 if line._attachmentTo == this_attachment:
858 num += 1
859
860 return n, num
861
862 def OnDrawOutline(self, dc, x, y, w, h):
863 old_pen = self._pen
864 old_brush = self._brush
865
866 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
867 self.SetPen(dottedPen)
868 self.SetBrush(wx.TRANSPARENT_BRUSH)
869
870 self.GetEventHandler().OnDraw(dc)
871
872 if old_pen:
873 self.SetPen(old_pen)
874 else:
875 self.SetPen(None)
876 if old_brush:
877 self.SetBrush(old_brush)
878 else:
879 self.SetBrush(None)
880
881 def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
882 x_offset = x - old_x
883 y_offset = y - old_y
884
885 if self._lineControlPoints and not (x_offset == 0 and y_offset == 0):
886 for point in self._lineControlPoints:
887 point[0] += x_offset
888 point[1] += y_offset
889
890 # Move temporary label rectangles if necessary
891 for i in range(3):
892 if self._labelObjects[i]:
893 self._labelObjects[i].Erase(dc)
894 xp, yp = self.GetLabelPosition(i)
895 if i < len(self._regions):
896 xr, yr = self._regions[i].GetPosition()
897 else:
898 xr, yr = 0, 0
899 self._labelObjects[i].Move(dc, xp + xr, yp + yr)
900 return True
901
902 def OnMoveLink(self, dc, moveControlPoints = True):
903 """Called when a connected object has moved, to move the link to
904 correct position
905 """
906 if not self._from or not self._to:
907 return
908
909 # Do each end - nothing in the middle. User has to move other points
910 # manually if necessary
911 end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints()
912
913 oldX, oldY = self._xpos, self._ypos
914
915 # pi: The first time we go through FindLineEndPoints we can't
916 # use the middle points (since they don't have sane values),
917 # so we just do what we do for a normal line. Then we call
918 # Initialise to set the middle points, and then FindLineEndPoints
919 # again, but this time (and from now on) we use the middle
920 # points to calculate the end points.
921 # This was buggy in the C++ version too.
922
923 self.SetEnds(end_x, end_y, other_end_x, other_end_y)
924
925 if len(self._lineControlPoints) > 2:
926 self.Initialise()
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[0] += x_offset
941 point[1] += 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 # pi: If we have a segmented line and this is the first time,
961 # do this as a straight line.
962 if len(self._lineControlPoints) > 2 and self._initialised:
963 if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
964 nth, no_arcs = self.FindNth(self._from, False) # Not incoming
965 end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
966 else:
967 end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), second_point[0], second_point[1])
968
969 if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
970 nth, no_arch = self.FindNth(self._to, True) # Incoming
971 other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arch, self)
972 else:
973 other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), second_last_point[0], second_last_point[1])
974 else:
975 fromX = self._from.GetX()
976 fromY = self._from.GetY()
977 toX = self._to.GetX()
978 toY = self._to.GetY()
979
980 if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
981 nth, no_arcs = self.FindNth(self._from, False)
982 end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
983 fromX = end_x
984 fromY = end_y
985
986 if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
987 nth, no_arcs = self.FindNth(self._to, True)
988 other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arcs, self)
989 toX = other_end_x
990 toY = other_end_y
991
992 if self._from.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
993 end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), toX, toY)
994
995 if self._to.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
996 other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), fromX, fromY)
997
998 return end_x, end_y, other_end_x, other_end_y
999
1000
1001 def OnDraw(self, dc):
1002 if not self._lineControlPoints:
1003 return
1004
1005 if self._pen:
1006 dc.SetPen(self._pen)
1007 if self._brush:
1008 dc.SetBrush(self._brush)
1009
1010 points = []
1011 for point in self._lineControlPoints:
1012 points.append(wx.Point(point[0], point[1]))
1013
1014 if self._isSpline:
1015 dc.DrawSpline(points)
1016 else:
1017 dc.DrawLines(points)
1018
1019 if sys.platform[:3] == "win":
1020 # For some reason, last point isn't drawn under Windows
1021 pt = points[-1]
1022 dc.DrawPoint(pt[0], pt[1])
1023
1024 # Problem with pen - if not a solid pen, does strange things
1025 # to the arrowhead. So make (get) a new pen that's solid.
1026 if self._pen and self._pen.GetStyle() != wx.SOLID:
1027 solid_pen = wx.ThePenList.FindOrCreatePen(self._pen.GetColour(), 1, wx.SOLID)
1028 if solid_pen:
1029 dc.SetPen(solid_pen)
1030
1031 self.DrawArrows(dc)
1032
1033 def OnDrawControlPoints(self, dc):
1034 if not self._drawHandles:
1035 return
1036
1037 # Draw temporary label rectangles if necessary
1038 for i in range(3):
1039 if self._labelObjects[i]:
1040 self._labelObjects[i].Draw(dc)
1041
1042 Shape.OnDrawControlPoints(self, dc)
1043
1044 def OnEraseControlPoints(self, dc):
1045 # Erase temporary label rectangles if necessary
1046
1047 for i in range(3):
1048 if self._labelObjects[i]:
1049 self._labelObjects[i].Erase(dc)
1050
1051 Shape.OnEraseControlPoints(self, dc)
1052
1053 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
1054 pass
1055
1056 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1057 pass
1058
1059 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1060 pass
1061
1062 def OnDrawContents(self, dc):
1063 if self.GetDisableLabel():
1064 return
1065
1066 for i in range(3):
1067 if self._regions[i]:
1068 x, y = self.GetLabelPosition(i)
1069 self.DrawRegion(dc, self._regions[i], x, y)
1070
1071 def SetTo(self, object):
1072 """Set the 'to' object for the line."""
1073 self._to = object
1074
1075 def SetFrom(self, object):
1076 """Set the 'from' object for the line."""
1077 self._from = object
1078
1079 def MakeControlPoints(self):
1080 """Make handle control points."""
1081 if self._canvas and self._lineControlPoints:
1082 first = self._lineControlPoints[0]
1083 last = self._lineControlPoints[-1]
1084
1085 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, first[0], first[1], CONTROL_POINT_ENDPOINT_FROM)
1086 control._point = first
1087 self._canvas.AddShape(control)
1088 self._controlPoints.append(control)
1089
1090 for point in self._lineControlPoints[1:-1]:
1091 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point[0], point[1], CONTROL_POINT_LINE)
1092 control._point = point
1093 self._canvas.AddShape(control)
1094 self._controlPoints.append(control)
1095
1096 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, last[0], last[1], CONTROL_POINT_ENDPOINT_TO)
1097 control._point = last
1098 self._canvas.AddShape(control)
1099 self._controlPoints.append(control)
1100
1101 def ResetControlPoints(self):
1102 if self._canvas and self._lineControlPoints and self._controlPoints:
1103 for i in range(min(len(self._controlPoints), len(self._lineControlPoints))):
1104 point = self._lineControlPoints[i]
1105 control = self._controlPoints[i]
1106 control.SetX(point[0])
1107 control.SetY(point[1])
1108
1109 # Override select, to create / delete temporary label-moving objects
1110 def Select(self, select, dc = None):
1111 Shape.Select(self, select, dc)
1112 if select:
1113 for i in range(3):
1114 if self._regions[i]:
1115 region = self._regions[i]
1116 if region._formattedText:
1117 w, h = region.GetSize()
1118 x, y = region.GetPosition()
1119 xx, yy = self.GetLabelPosition(i)
1120
1121 if self._labelObjects[i]:
1122 self._labelObjects[i].Select(False)
1123 self._labelObjects[i].RemoveFromCanvas(self._canvas)
1124
1125 self._labelObjects[i] = self.OnCreateLabelShape(self, region, w, h)
1126 self._labelObjects[i].AddToCanvas(self._canvas)
1127 self._labelObjects[i].Show(True)
1128 if dc:
1129 self._labelObjects[i].Move(dc, x + xx, y + yy)
1130 self._labelObjects[i].Select(True, dc)
1131 else:
1132 for i in range(3):
1133 if self._labelObjects[i]:
1134 self._labelObjects[i].Select(False, dc)
1135 self._labelObjects[i].Erase(dc)
1136 self._labelObjects[i].RemoveFromCanvas(self._canvas)
1137 self._labelObjects[i] = None
1138
1139 # Control points ('handles') redirect control to the actual shape, to
1140 # make it easier to override sizing behaviour.
1141 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
1142 dc = wx.ClientDC(self.GetCanvas())
1143 self.GetCanvas().PrepareDC(dc)
1144
1145 dc.SetLogicalFunction(OGLRBLF)
1146
1147 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1148 dc.SetPen(dottedPen)
1149 dc.SetBrush(wx.TRANSPARENT_BRUSH)
1150
1151 if pt._type == CONTROL_POINT_LINE:
1152 x, y = self._canvas.Snap(x, y)
1153
1154 pt.SetX(x)
1155 pt.SetY(y)
1156 pt._point[0] = x
1157 pt._point[1] = y
1158
1159 old_pen = self.GetPen()
1160 old_brush = self.GetBrush()
1161
1162 self.SetPen(dottedPen)
1163 self.SetBrush(wx.TRANSPARENT_BRUSH)
1164
1165 self.GetEventHandler().OnMoveLink(dc, False)
1166
1167 self.SetPen(old_pen)
1168 self.SetBrush(old_brush)
1169
1170 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
1171 dc = wx.ClientDC(self.GetCanvas())
1172 self.GetCanvas().PrepareDC(dc)
1173
1174 if pt._type == CONTROL_POINT_LINE:
1175 pt._originalPos = pt._point
1176 x, y = self._canvas.Snap(x, y)
1177
1178 self.Erase(dc)
1179
1180 # Redraw start and end objects because we've left holes
1181 # when erasing the line
1182 self.GetFrom().OnDraw(dc)
1183 self.GetFrom().OnDrawContents(dc)
1184 self.GetTo().OnDraw(dc)
1185 self.GetTo().OnDrawContents(dc)
1186
1187 self.SetDisableLabel(True)
1188 dc.SetLogicalFunction(OGLRBLF)
1189
1190 pt._xpos = x
1191 pt._ypos = y
1192 pt._point[0] = x
1193 pt._point[1] = y
1194
1195 old_pen = self.GetPen()
1196 old_brush = self.GetBrush()
1197
1198 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1199 self.SetPen(dottedPen)
1200 self.SetBrush(wx.TRANSPARENT_BRUSH)
1201
1202 self.GetEventHandler().OnMoveLink(dc, False)
1203
1204 self.SetPen(old_pen)
1205 self.SetBrush(old_brush)
1206
1207 if pt._type == CONTROL_POINT_ENDPOINT_FROM or pt._type == CONTROL_POINT_ENDPOINT_TO:
1208 self._canvas.SetCursor(wx.StockCursor(wx.CURSOR_BULLSEYE))
1209 pt._oldCursor = wx.STANDARD_CURSOR
1210
1211 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
1212 dc = wx.ClientDC(self.GetCanvas())
1213 self.GetCanvas().PrepareDC(dc)
1214
1215 self.SetDisableLabel(False)
1216
1217 if pt._type == CONTROL_POINT_LINE:
1218 x, y = self._canvas.Snap(x, y)
1219
1220 rpt = wx.RealPoint(x, y)
1221
1222 # Move the control point back to where it was;
1223 # MoveControlPoint will move it to the new position
1224 # if it decides it wants. We only moved the position
1225 # during user feedback so we could redraw the line
1226 # as it changed shape.
1227 pt._xpos = pt._originalPos[0]
1228 pt._ypos = pt._originalPos[1]
1229 pt._point[0] = pt._originalPos[0]
1230 pt._point[1] = pt._originalPos[1]
1231
1232 self.OnMoveMiddleControlPoint(dc, pt, rpt)
1233
1234 if pt._type == CONTROL_POINT_ENDPOINT_FROM:
1235 if pt._oldCursor:
1236 self._canvas.SetCursor(pt._oldCursor)
1237
1238 if self.GetFrom():
1239 self.GetFrom().MoveLineToNewAttachment(dc, self, x, y)
1240
1241 if pt._type == CONTROL_POINT_ENDPOINT_TO:
1242 if pt._oldCursor:
1243 self._canvas.SetCursor(pt._oldCursor)
1244
1245 if self.GetTo():
1246 self.GetTo().MoveLineToNewAttachment(dc, self, x, y)
1247
1248 # This is called only when a non-end control point is moved
1249 def OnMoveMiddleControlPoint(self, dc, lpt, pt):
1250 lpt._xpos = pt[0]
1251 lpt._ypos = pt[1]
1252
1253 lpt._point[0] = pt[0]
1254 lpt._point[1] = pt[1]
1255
1256 self.GetEventHandler().OnMoveLink(dc)
1257
1258 return True
1259
1260 def AddArrow(self, type, end = ARROW_POSITION_END, size = 10.0, xOffset = 0.0, name = "", mf = None, arrowId = -1):
1261 """Add an arrow (or annotation) to the line.
1262
1263 type may currently be one of:
1264
1265 ARROW_HOLLOW_CIRCLE
1266 Hollow circle.
1267 ARROW_FILLED_CIRCLE
1268 Filled circle.
1269 ARROW_ARROW
1270 Conventional arrowhead.
1271 ARROW_SINGLE_OBLIQUE
1272 Single oblique stroke.
1273 ARROW_DOUBLE_OBLIQUE
1274 Double oblique stroke.
1275 ARROW_DOUBLE_METAFILE
1276 Custom arrowhead.
1277
1278 end may currently be one of:
1279
1280 ARROW_POSITION_END
1281 Arrow appears at the end.
1282 ARROW_POSITION_START
1283 Arrow appears at the start.
1284
1285 arrowSize specifies the length of the arrow.
1286
1287 xOffset specifies the offset from the end of the line.
1288
1289 name specifies a name for the arrow.
1290
1291 mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows
1292 metafile.
1293
1294 arrowId is the id for the arrow.
1295 """
1296 arrow = ArrowHead(type, end, size, xOffset, name, mf, arrowId)
1297 self._arcArrows.append(arrow)
1298 return arrow
1299
1300 # Add arrowhead at a particular position in the arrowhead list
1301 def AddArrowOrdered(self, arrow, referenceList, end):
1302 """Add an arrowhead in the position indicated by the reference list
1303 of arrowheads, which contains all legal arrowheads for this line, in
1304 the correct order. E.g.
1305
1306 Reference list: a b c d e
1307 Current line list: a d
1308
1309 Add c, then line list is: a c d.
1310
1311 If no legal arrowhead position, return FALSE. Assume reference list
1312 is for one end only, since it potentially defines the ordering for
1313 any one of the 3 positions. So we don't check the reference list for
1314 arrowhead position.
1315 """
1316 if not referenceList:
1317 return False
1318
1319 targetName = arrow.GetName()
1320
1321 # First check whether we need to insert in front of list,
1322 # because this arrowhead is the first in the reference
1323 # list and should therefore be first in the current list.
1324 refArrow = referenceList[0]
1325 if refArrow.GetName() == targetName:
1326 self._arcArrows.insert(0, arrow)
1327 return True
1328
1329 i1 = i2 = 0
1330 while i1 < len(referenceList) and i2 < len(self._arcArrows):
1331 refArrow = referenceList[i1]
1332 currArrow = self._arcArrows[i2]
1333
1334 # Matching: advance current arrow pointer
1335 if currArrow.GetArrowEnd() == end and currArrow.GetName() == refArrow.GetName():
1336 i2 += 1
1337
1338 # Check if we're at the correct position in the
1339 # reference list
1340 if targetName == refArrow.GetName():
1341 if i2 < len(self._arcArrows):
1342 self._arcArrows.insert(i2, arrow)
1343 else:
1344 self._arcArrows.append(arrow)
1345 return True
1346 i1 += 1
1347
1348 self._arcArrows.append(arrow)
1349 return True
1350
1351 def ClearArrowsAtPosition(self, end):
1352 """Delete the arrows at the specified position, or at any position
1353 if position is -1.
1354 """
1355 if end == -1:
1356 self._arcArrows = []
1357 return
1358
1359 for arrow in self._arcArrows:
1360 if arrow.GetArrowEnd() == end:
1361 self._arcArrows.remove(arrow)
1362
1363 def ClearArrow(self, name):
1364 """Delete the arrow with the given name."""
1365 for arrow in self._arcArrows:
1366 if arrow.GetName() == name:
1367 self._arcArrows.remove(arrow)
1368 return True
1369 return False
1370
1371 def FindArrowHead(self, position, name):
1372 """Find arrowhead by position and name.
1373
1374 if position is -1, matches any position.
1375 """
1376 for arrow in self._arcArrows:
1377 if (position == -1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
1378 return arrow
1379
1380 return None
1381
1382 def FindArrowHeadId(self, arrowId):
1383 """Find arrowhead by id."""
1384 for arrow in self._arcArrows:
1385 if arrowId == arrow.GetId():
1386 return arrow
1387
1388 return None
1389
1390 def DeleteArrowHead(self, position, name):
1391 """Delete arrowhead by position and name.
1392
1393 if position is -1, matches any position.
1394 """
1395 for arrow in self._arcArrows:
1396 if (position == -1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
1397 self._arcArrows.remove(arrow)
1398 return True
1399 return False
1400
1401 def DeleteArrowHeadId(self, id):
1402 """Delete arrowhead by id."""
1403 for arrow in self._arcArrows:
1404 if arrowId == arrow.GetId():
1405 self._arcArrows.remove(arrow)
1406 return True
1407 return False
1408
1409 # Calculate the minimum width a line
1410 # occupies, for the purposes of drawing lines in tools.
1411 def FindMinimumWidth(self):
1412 """Find the horizontal width for drawing a line with arrows in
1413 minimum space. Assume arrows at end only.
1414 """
1415 minWidth = 0.0
1416 for arrowHead in self._arcArrows:
1417 minWidth += arrowHead.GetSize()
1418 if arrowHead != self._arcArrows[-1]:
1419 minWidth += arrowHead + GetSpacing
1420
1421 # We have ABSOLUTE minimum now. So
1422 # scale it to give it reasonable aesthetics
1423 # when drawing with line.
1424 if minWidth > 0:
1425 minWidth = minWidth * 1.4
1426 else:
1427 minWidth = 20.0
1428
1429 self.SetEnds(0.0, 0.0, minWidth, 0.0)
1430 self.Initialise()
1431
1432 return minWidth
1433
1434 def FindLinePosition(self, x, y):
1435 """Find which position we're talking about at this x, y.
1436
1437 Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END.
1438 """
1439 startX, startY, endX, endY = self.GetEnds()
1440
1441 # Find distances from centre, start and end. The smallest wins
1442 centreDistance = math.sqrt((x - self._xpos) * (x - self._xpos) + (y - self._ypos) * (y - self._ypos))
1443 startDistance = math.sqrt((x - startX) * (x - startX) + (y - startY) * (y - startY))
1444 endDistance = math.sqrt((x - endX) * (x - endX) + (y - endY) * (y - endY))
1445
1446 if centreDistance < startDistance and centreDistance < endDistance:
1447 return ARROW_POSITION_MIDDLE
1448 elif startDistance < endDistance:
1449 return ARROW_POSITION_START
1450 else:
1451 return ARROW_POSITION_END
1452
1453 def SetAlignmentOrientation(self, isEnd, isHoriz):
1454 if isEnd:
1455 if isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
1456 self._alignmentEnd != LINE_ALIGNMENT_HORIZ
1457 elif not isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
1458 self._alignmentEnd -= LINE_ALIGNMENT_HORIZ
1459 else:
1460 if isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
1461 self._alignmentStart != LINE_ALIGNMENT_HORIZ
1462 elif not isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
1463 self._alignmentStart -= LINE_ALIGNMENT_HORIZ
1464
1465 def SetAlignmentType(self, isEnd, alignType):
1466 if isEnd:
1467 if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1468 if self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
1469 self._alignmentEnd |= LINE_ALIGNMENT_TO_NEXT_HANDLE
1470 elif self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1471 self._alignmentEnd -= LINE_ALIGNMENT_TO_NEXT_HANDLE
1472 else:
1473 if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1474 if self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
1475 self._alignmentStart |= LINE_ALIGNMENT_TO_NEXT_HANDLE
1476 elif self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1477 self._alignmentStart -= LINE_ALIGNMENT_TO_NEXT_HANDLE
1478
1479 def GetAlignmentOrientation(self, isEnd):
1480 if isEnd:
1481 return self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
1482 else:
1483 return self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
1484
1485 def GetAlignmentType(self, isEnd):
1486 if isEnd:
1487 return self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE
1488 else:
1489 return self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE
1490
1491 def GetNextControlPoint(self, shape):
1492 """Find the next control point in the line after the start / end point,
1493 depending on whether the shape is at the start or end.
1494 """
1495 n = len(self._lineControlPoints)
1496 if self._to == shape:
1497 # Must be END of line, so we want (n - 1)th control point.
1498 # But indexing ends at n-1, so subtract 2.
1499 nn = n - 2
1500 else:
1501 nn = 1
1502 if nn < len(self._lineControlPoints):
1503 return self._lineControlPoints[nn]
1504 return None
1505
1506 def OnCreateLabelShape(self, parent, region, w, h):
1507 return LabelShape(parent, region, w, h)
1508
1509
1510 def OnLabelMovePre(self, dc, labelShape, x, y, old_x, old_y, display):
1511 labelShape._shapeRegion.SetSize(labelShape.GetWidth(), labelShape.GetHeight())
1512
1513 # Find position in line's region list
1514 i = 0
1515 for region in self.GetRegions():
1516 if labelShape._shapeRegion == region:
1517 self.GetRegions().remove(region)
1518 else:
1519 i += 1
1520
1521 xx, yy = self.GetLabelPosition(i)
1522 # Set the region's offset, relative to the default position for
1523 # each region.
1524 labelShape._shapeRegion.SetPosition(x - xx, y - yy)
1525 labelShape.SetX(x)
1526 labelShape.SetY(y)
1527
1528 # Need to reformat to fit region
1529 if labelShape._shapeRegion.GetText():
1530 s = labelShape._shapeRegion.GetText()
1531 labelShape.FormatText(dc, s, i)
1532 self.DrawRegion(dc, labelShape._shapeRegion, xx, yy)
1533 return True
1534