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