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