]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ogl/_lines.py
Patch from Andrea that fixes the following problems/issues:
[wxWidgets.git] / wxPython / wx / lib / ogl / _lines.py
1 # -*- coding: iso-8859-1 -*-
2 #----------------------------------------------------------------------------
3 # Name: lines.py
4 # Purpose: LineShape class
5 #
6 # Author: Pierre Hjälm (from C++ original by Julian Smart)
7 #
8 # Created: 2004-05-08
9 # RCS-ID: $Id$
10 # Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
11 # Licence: wxWindows license
12 #----------------------------------------------------------------------------
13
14 import sys
15 import math
16
17 from _basic import Shape, ShapeRegion, ShapeTextLine, ControlPoint, RectangleShape
18 from _oglmisc import *
19
20 # Line alignment flags
21 # Vertical by default
22 LINE_ALIGNMENT_HORIZ = 1
23 LINE_ALIGNMENT_VERT = 0
24 LINE_ALIGNMENT_TO_NEXT_HANDLE = 2
25 LINE_ALIGNMENT_NONE = 0
26
27
28
29 class LineControlPoint(ControlPoint):
30 def __init__(self, theCanvas = None, object = None, size = 0.0, x = 0.0, y = 0.0, the_type = 0):
31 ControlPoint.__init__(self, theCanvas, object, size, x, y, the_type)
32 self._xpos = x
33 self._ypos = y
34 self._type = the_type
35 self._point = None
36 self._originalPos = None
37
38 def OnDraw(self, dc):
39 RectangleShape.OnDraw(self, dc)
40
41 # Implement movement of Line point
42 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
43 self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
44
45 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
46 self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
47
48 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
49 self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
50
51
52
53 class ArrowHead(object):
54 def __init__(self, type = 0, end = 0, size = 0.0, dist = 0.0, name = "", mf = None, arrowId = -1):
55 if isinstance(type, ArrowHead):
56 pass
57 else:
58 self._arrowType = type
59 self._arrowEnd = end
60 self._arrowSize = size
61 self._xOffset = dist
62 self._yOffset = 0.0
63 self._spacing = 5.0
64
65 self._arrowName = name
66 self._metaFile = mf
67 self._id = arrowId
68 if self._id == -1:
69 self._id = wx.NewId()
70
71 def _GetType(self):
72 return self._arrowType
73
74 def GetPosition(self):
75 return self._arrowEnd
76
77 def SetPosition(self, pos):
78 self._arrowEnd = pos
79
80 def GetXOffset(self):
81 return self._xOffset
82
83 def GetYOffset(self):
84 return self._yOffset
85
86 def GetSpacing(self):
87 return self._spacing
88
89 def GetSize(self):
90 return self._arrowSize
91
92 def SetSize(self, size):
93 self._arrowSize = size
94 if self._arrowType == ARROW_METAFILE and self._metaFile:
95 oldWidth = self._metaFile._width
96 if oldWidth == 0:
97 return
98
99 scale = float(size) / oldWidth
100 if scale != 1:
101 self._metaFile.Scale(scale, scale)
102
103 def GetName(self):
104 return self._arrowName
105
106 def SetXOffset(self, x):
107 self._xOffset = x
108
109 def SetYOffset(self, y):
110 self._yOffset = y
111
112 def GetMetaFile(self):
113 return self._metaFile
114
115 def GetId(self):
116 return self._id
117
118 def GetArrowEnd(self):
119 return self._arrowEnd
120
121 def GetArrowSize(self):
122 return self._arrowSize
123
124 def SetSpacing(self, sp):
125 self._spacing = sp
126
127
128
129 class LabelShape(RectangleShape):
130 def __init__(self, parent, region, w, h):
131 RectangleShape.__init__(self, w, h)
132 self._lineShape = parent
133 self._shapeRegion = region
134 self.SetPen(wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT))
135
136 def OnDraw(self, dc):
137 if self._lineShape and not self._lineShape.GetDrawHandles():
138 return
139
140 x1 = self._xpos - self._width / 2.0
141 y1 = self._ypos - self._height / 2.0
142
143 if self._pen:
144 if self._pen.GetWidth() == 0:
145 dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT))
146 else:
147 dc.SetPen(self._pen)
148 dc.SetBrush(wx.TRANSPARENT_BRUSH)
149
150 if self._cornerRadius > 0:
151 dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
152 else:
153 dc.DrawRectangle(x1, y1, self._width, self._height)
154
155 def OnDrawContents(self, dc):
156 pass
157
158 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
159 RectangleShape.OnDragLeft(self, draw, x, y, keys, attachment)
160
161 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
162 RectangleShape.OnBeginDragLeft(self, x, y, keys, attachment)
163
164 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
165 RectangleShape.OnEndDragLeft(self, x, y, keys, attachment)
166
167 def OnMovePre(self, dc, x, y, old_x, old_y, display):
168 return self._lineShape.OnLabelMovePre(dc, self, x, y, old_x, old_y, display)
169
170 # Divert left and right clicks to line object
171 def OnLeftClick(self, x, y, keys = 0, attachment = 0):
172 self._lineShape.GetEventHandler().OnLeftClick(x, y, keys, attachment)
173
174 def OnRightClick(self, x, y, keys = 0, attachment = 0):
175 self._lineShape.GetEventHandler().OnRightClick(x, y, keys, attachment)
176
177
178
179 class LineShape(Shape):
180 """LineShape may be attached to two nodes;
181 it may be segmented, in which case a control point is drawn for each joint.
182
183 A wxLineShape may have arrows at the beginning, end and centre.
184
185 Derived from:
186 Shape
187 """
188 def __init__(self):
189 Shape.__init__(self)
190
191 self._sensitivity = OP_CLICK_LEFT | OP_CLICK_RIGHT
192 self._draggable = False
193 self._attachmentTo = 0
194 self._attachmentFrom = 0
195 self._from = None
196 self._to = None
197 self._erasing = False
198 self._arrowSpacing = 5.0
199 self._ignoreArrowOffsets = False
200 self._isSpline = False
201 self._maintainStraightLines = False
202 self._alignmentStart = 0
203 self._alignmentEnd = 0
204
205 self._lineControlPoints = None
206
207 # Clear any existing regions (created in an earlier constructor)
208 # and make the three line regions.
209 self.ClearRegions()
210 for name in ["Middle","Start","End"]:
211 newRegion = ShapeRegion()
212 newRegion.SetName(name)
213 newRegion.SetSize(150, 50)
214 self._regions.append(newRegion)
215
216 self._labelObjects = [None, None, None]
217 self._lineOrientations = []
218 self._lineControlPoints = []
219 self._arcArrows = []
220
221 def GetFrom(self):
222 """Return the 'from' object."""
223 return self._from
224
225 def GetTo(self):
226 """Return the 'to' object."""
227 return self._to
228
229 def GetAttachmentFrom(self):
230 """Return the attachment point on the 'from' node."""
231 return self._attachmentFrom
232
233 def GetAttachmentTo(self):
234 """Return the attachment point on the 'to' node."""
235 return self._attachmentTo
236
237 def GetLineControlPoints(self):
238 return self._lineControlPoints
239
240 def SetSpline(self, spline):
241 """Specifies whether a spline is to be drawn through the control points."""
242 self._isSpline = spline
243
244 def IsSpline(self):
245 """TRUE if a spline is drawn through the control points."""
246 return self._isSpline
247
248 def SetAttachmentFrom(self, attach):
249 """Set the 'from' shape attachment."""
250 self._attachmentFrom = attach
251
252 def SetAttachmentTo(self, attach):
253 """Set the 'to' shape attachment."""
254 self._attachmentTo = attach
255
256 # This is really to distinguish between lines and other images.
257 # For lines, want to pass drag to canvas, since lines tend to prevent
258 # dragging on a canvas (they get in the way.)
259 def Draggable(self):
260 return False
261
262 def SetIgnoreOffsets(self, ignore):
263 """Set whether to ignore offsets from the end of the line when drawing."""
264 self._ignoreArrowOffsets = ignore
265
266 def GetArrows(self):
267 return self._arcArrows
268
269 def GetAlignmentStart(self):
270 return self._alignmentStart
271
272 def GetAlignmentEnd(self):
273 return self._alignmentEnd
274
275 def IsEnd(self, nodeObject):
276 """TRUE if shape is at the end of the line."""
277 return self._to == nodeObject
278
279 def MakeLineControlPoints(self, n):
280 """Make a given number of control points (minimum of two)."""
281 self._lineControlPoints = []
282
283 for _ in range(n):
284 point = wx.RealPoint(-999, -999)
285 self._lineControlPoints.append(point)
286
287 # pi: added _initialised to keep track of when we have set
288 # the middle points to something other than (-999, -999)
289 self._initialised = False
290
291 def InsertLineControlPoint(self, dc = None, point = None):
292 """Insert a control point at an optional given position."""
293 if dc:
294 self.Erase(dc)
295
296 if point:
297 line_x, line_y = point
298 else:
299 last_point = self._lineControlPoints[-1]
300 second_last_point = self._lineControlPoints[-2]
301
302 line_x = (last_point[0] + second_last_point[0]) / 2.0
303 line_y = (last_point[1] + second_last_point[1]) / 2.0
304
305 point = wx.RealPoint(line_x, line_y)
306 self._lineControlPoints.insert(len(self._lineControlPoints)-1, point)
307
308 def DeleteLineControlPoint(self):
309 """Delete an arbitary point on the line."""
310 if len(self._lineControlPoints) < 3:
311 return False
312
313 del self._lineControlPoints[-2]
314 return True
315
316 def Initialise(self):
317 """Initialise the line object."""
318 if self._lineControlPoints:
319 # Just move the first and last control points
320 first_point = self._lineControlPoints[0]
321 last_point = self._lineControlPoints[-1]
322
323 # If any of the line points are at -999, we must
324 # initialize them by placing them half way between the first
325 # and the last.
326
327 for i in range(1,len(self._lineControlPoints)):
328 point = self._lineControlPoints[i]
329 if point[0] == -999:
330 if first_point[0] < last_point[0]:
331 x1 = first_point[0]
332 x2 = last_point[0]
333 else:
334 x2 = first_point[0]
335 x1 = last_point[0]
336 if first_point[1] < last_point[1]:
337 y1 = first_point[1]
338 y2 = last_point[1]
339 else:
340 y2 = first_point[1]
341 y1 = last_point[1]
342 self._lineControlPoints[i] = wx.RealPoint((x2 - x1) / 2.0 + x1, (y2 - y1) / 2.0 + y1)
343 self._initialised = True
344
345 def FormatText(self, dc, s, i):
346 """Format a text string according to the region size, adding
347 strings with positions to region text list.
348 """
349 self.ClearText(i)
350
351 if len(self._regions) == 0 or i >= len(self._regions):
352 return
353
354 region = self._regions[i]
355 region.SetText(s)
356 dc.SetFont(region.GetFont())
357
358 w, h = region.GetSize()
359 # Initialize the size if zero
360 if (w == 0 or h == 0) and s:
361 w, h = 100, 50
362 region.SetSize(w, h)
363
364 string_list = FormatText(dc, s, w - 5, h - 5, region.GetFormatMode())
365 for s in string_list:
366 line = ShapeTextLine(0.0, 0.0, s)
367 region.GetFormattedText().append(line)
368
369 actualW = w
370 actualH = h
371 if region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS:
372 actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText(), self._xpos, self._ypos, w, h)
373 if actualW != w or actualH != h:
374 xx, yy = self.GetLabelPosition(i)
375 self.EraseRegion(dc, region, xx, yy)
376 if len(self._labelObjects) < i:
377 self._labelObjects[i].Select(False, dc)
378 self._labelObjects[i].Erase(dc)
379 self._labelObjects[i].SetSize(actualW, actualH)
380
381 region.SetSize(actualW, actualH)
382
383 if len(self._labelObjects) < i:
384 self._labelObjects[i].Select(True, dc)
385 self._labelObjects[i].Draw(dc)
386
387 CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW, actualH, region.GetFormatMode())
388 self._formatted = True
389
390 def DrawRegion(self, dc, region, x, y):
391 """Format one region at this position."""
392 if self.GetDisableLabel():
393 return
394
395 w, h = region.GetSize()
396
397 # Get offset from x, y
398 xx, yy = region.GetPosition()
399
400 xp = xx + x
401 yp = yy + y
402
403 # First, clear a rectangle for the text IF there is any
404 if len(region.GetFormattedText()):
405 dc.SetPen(self.GetBackgroundPen())
406 dc.SetBrush(self.GetBackgroundBrush())
407
408 # Now draw the text
409 if region.GetFont():
410 dc.SetFont(region.GetFont())
411 dc.DrawRectangle(xp - w / 2.0, yp - h / 2.0, w, h)
412
413 if self._pen:
414 dc.SetPen(self._pen)
415 dc.SetTextForeground(region.GetActualColourObject())
416
417 DrawFormattedText(dc, region.GetFormattedText(), xp, yp, w, h, region.GetFormatMode())
418
419 def EraseRegion(self, dc, region, x, y):
420 """Erase one region at this position."""
421 if self.GetDisableLabel():
422 return
423
424 w, h = region.GetSize()
425
426 # Get offset from x, y
427 xx, yy = region.GetPosition()
428
429 xp = xx + x
430 yp = yy + y
431
432 if region.GetFormattedText():
433 dc.SetPen(self.GetBackgroundPen())
434 dc.SetBrush(self.GetBackgroundBrush())
435
436 dc.DrawRectangle(xp - w / 2.0, yp - h / 2.0, w, h)
437
438 def GetLabelPosition(self, position):
439 """Get the reference point for a label.
440
441 Region x and y are offsets from this.
442 position is 0 (middle), 1 (start), 2 (end).
443 """
444 if position == 0:
445 # Want to take the middle section for the label
446 half_way = int(len(self._lineControlPoints) / 2.0)
447
448 # Find middle of this line
449 point = self._lineControlPoints[half_way - 1]
450 next_point = self._lineControlPoints[half_way]
451
452 dx = next_point[0] - point[0]
453 dy = next_point[1] - point[1]
454
455 return point[0] + dx / 2.0, point[1] + dy / 2.0
456 elif position == 1:
457 return self._lineControlPoints[0][0], self._lineControlPoints[0][1]
458 elif position == 2:
459 return self._lineControlPoints[-1][0], self._lineControlPoints[-1][1]
460
461 def Straighten(self, dc = None):
462 """Straighten verticals and horizontals."""
463 if len(self._lineControlPoints) < 3:
464 return
465
466 if dc:
467 self.Erase(dc)
468
469 GraphicsStraightenLine(self._lineControlPoints[-1], self._lineControlPoints[-2])
470
471 for i in range(len(self._lineControlPoints) - 2):
472 GraphicsStraightenLine(self._lineControlPoints[i], self._lineControlPoints[i + 1])
473
474 if dc:
475 self.Draw(dc)
476
477 def Unlink(self):
478 """Unlink the line from the nodes at either end."""
479 if self._to:
480 self._to.GetLines().remove(self)
481 if self._from:
482 self._from.GetLines().remove(self)
483 self._to = None
484 self._from = None
485 for i in range(3):
486 if self._labelObjects[i]:
487 self._labelObjects[i].Select(False)
488 self._labelObjects[i].RemoveFromCanvas(self._canvas)
489 self.ClearArrowsAtPosition(-1)
490
491 def SetEnds(self, x1, y1, x2, y2):
492 """Set the end positions of the line."""
493 self._lineControlPoints[0] = wx.RealPoint(x1, y1)
494 self._lineControlPoints[-1] = wx.RealPoint(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 and dx == 0:
552 continue
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 # Do each end - nothing in the middle. User has to move other points
908 # manually if necessary
909 end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints()
910
911 oldX, oldY = self._xpos, self._ypos
912
913 # pi: The first time we go through FindLineEndPoints we can't
914 # use the middle points (since they don't have sane values),
915 # so we just do what we do for a normal line. Then we call
916 # Initialise to set the middle points, and then FindLineEndPoints
917 # again, but this time (and from now on) we use the middle
918 # points to calculate the end points.
919 # This was buggy in the C++ version too.
920
921 self.SetEnds(end_x, end_y, other_end_x, other_end_y)
922
923 if len(self._lineControlPoints) > 2:
924 self.Initialise()
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[0] += x_offset
939 point[1] += 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 # pi: If we have a segmented line and this is the first time,
959 # do this as a straight line.
960 if len(self._lineControlPoints) > 2 and self._initialised:
961 if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
962 nth, no_arcs = self.FindNth(self._from, False) # Not incoming
963 end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
964 else:
965 end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), second_point[0], second_point[1])
966
967 if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
968 nth, no_arch = self.FindNth(self._to, True) # Incoming
969 other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arch, self)
970 else:
971 other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), second_last_point[0], second_last_point[1])
972 else:
973 fromX = self._from.GetX()
974 fromY = self._from.GetY()
975 toX = self._to.GetX()
976 toY = self._to.GetY()
977
978 if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
979 nth, no_arcs = self.FindNth(self._from, False)
980 end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
981 fromX = end_x
982 fromY = end_y
983
984 if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
985 nth, no_arcs = self.FindNth(self._to, True)
986 other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arcs, self)
987 toX = other_end_x
988 toY = other_end_y
989
990 if self._from.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
991 end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), toX, toY)
992
993 if self._to.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
994 other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), fromX, fromY)
995
996 return end_x, end_y, other_end_x, other_end_y
997
998
999 def OnDraw(self, dc):
1000 if not self._lineControlPoints:
1001 return
1002
1003 if self._pen:
1004 dc.SetPen(self._pen)
1005 if self._brush:
1006 dc.SetBrush(self._brush)
1007
1008 points = []
1009 for point in self._lineControlPoints:
1010 points.append(wx.Point(point[0], point[1]))
1011
1012 if self._isSpline:
1013 dc.DrawSpline(points)
1014 else:
1015 dc.DrawLines(points)
1016
1017 if sys.platform[:3] == "win":
1018 # For some reason, last point isn't drawn under Windows
1019 pt = points[-1]
1020 dc.DrawPoint(pt[0], pt[1])
1021
1022 # Problem with pen - if not a solid pen, does strange things
1023 # to the arrowhead. So make (get) a new pen that's solid.
1024 if self._pen and self._pen.GetStyle() != wx.SOLID:
1025 solid_pen = wx.Pen(self._pen.GetColour(), 1, wx.SOLID)
1026 if solid_pen:
1027 dc.SetPen(solid_pen)
1028
1029 self.DrawArrows(dc)
1030
1031 def OnDrawControlPoints(self, dc):
1032 if not self._drawHandles:
1033 return
1034
1035 # Draw temporary label rectangles if necessary
1036 for i in range(3):
1037 if self._labelObjects[i]:
1038 self._labelObjects[i].Draw(dc)
1039
1040 Shape.OnDrawControlPoints(self, dc)
1041
1042 def OnEraseControlPoints(self, dc):
1043 # Erase temporary label rectangles if necessary
1044
1045 for i in range(3):
1046 if self._labelObjects[i]:
1047 self._labelObjects[i].Erase(dc)
1048
1049 Shape.OnEraseControlPoints(self, dc)
1050
1051 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
1052 pass
1053
1054 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1055 pass
1056
1057 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1058 pass
1059
1060 def OnDrawContents(self, dc):
1061 if self.GetDisableLabel():
1062 return
1063
1064 for i in range(3):
1065 if self._regions[i]:
1066 x, y = self.GetLabelPosition(i)
1067 self.DrawRegion(dc, self._regions[i], x, y)
1068
1069 def SetTo(self, object):
1070 """Set the 'to' object for the line."""
1071 self._to = object
1072
1073 def SetFrom(self, object):
1074 """Set the 'from' object for the line."""
1075 self._from = object
1076
1077 def MakeControlPoints(self):
1078 """Make handle control points."""
1079 if self._canvas and self._lineControlPoints:
1080 first = self._lineControlPoints[0]
1081 last = self._lineControlPoints[-1]
1082
1083 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, first[0], first[1], CONTROL_POINT_ENDPOINT_FROM)
1084 control._point = first
1085 self._canvas.AddShape(control)
1086 self._controlPoints.append(control)
1087
1088 for point in self._lineControlPoints[1:-1]:
1089 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point[0], point[1], CONTROL_POINT_LINE)
1090 control._point = point
1091 self._canvas.AddShape(control)
1092 self._controlPoints.append(control)
1093
1094 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, last[0], last[1], CONTROL_POINT_ENDPOINT_TO)
1095 control._point = last
1096 self._canvas.AddShape(control)
1097 self._controlPoints.append(control)
1098
1099 def ResetControlPoints(self):
1100 if self._canvas and self._lineControlPoints and self._controlPoints:
1101 for i in range(min(len(self._controlPoints), len(self._lineControlPoints))):
1102 point = self._lineControlPoints[i]
1103 control = self._controlPoints[i]
1104 control.SetX(point[0])
1105 control.SetY(point[1])
1106
1107 # Override select, to create / delete temporary label-moving objects
1108 def Select(self, select, dc = None):
1109 Shape.Select(self, select, dc)
1110 if select:
1111 for i in range(3):
1112 if self._regions[i]:
1113 region = self._regions[i]
1114 if region._formattedText:
1115 w, h = region.GetSize()
1116 x, y = region.GetPosition()
1117 xx, yy = self.GetLabelPosition(i)
1118
1119 if self._labelObjects[i]:
1120 self._labelObjects[i].Select(False)
1121 self._labelObjects[i].RemoveFromCanvas(self._canvas)
1122
1123 self._labelObjects[i] = self.OnCreateLabelShape(self, region, w, h)
1124 self._labelObjects[i].AddToCanvas(self._canvas)
1125 self._labelObjects[i].Show(True)
1126 if dc:
1127 self._labelObjects[i].Move(dc, x + xx, y + yy)
1128 self._labelObjects[i].Select(True, dc)
1129 else:
1130 for i in range(3):
1131 if self._labelObjects[i]:
1132 self._labelObjects[i].Select(False, dc)
1133 self._labelObjects[i].Erase(dc)
1134 self._labelObjects[i].RemoveFromCanvas(self._canvas)
1135 self._labelObjects[i] = None
1136
1137 # Control points ('handles') redirect control to the actual shape, to
1138 # make it easier to override sizing behaviour.
1139 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
1140 dc = wx.ClientDC(self.GetCanvas())
1141 self.GetCanvas().PrepareDC(dc)
1142
1143 dc.SetLogicalFunction(OGLRBLF)
1144
1145 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1146 dc.SetPen(dottedPen)
1147 dc.SetBrush(wx.TRANSPARENT_BRUSH)
1148
1149 if pt._type == CONTROL_POINT_LINE:
1150 x, y = self._canvas.Snap(x, y)
1151
1152 pt.SetX(x)
1153 pt.SetY(y)
1154 pt._point[0] = x
1155 pt._point[1] = y
1156
1157 old_pen = self.GetPen()
1158 old_brush = self.GetBrush()
1159
1160 self.SetPen(dottedPen)
1161 self.SetBrush(wx.TRANSPARENT_BRUSH)
1162
1163 self.GetEventHandler().OnMoveLink(dc, False)
1164
1165 self.SetPen(old_pen)
1166 self.SetBrush(old_brush)
1167
1168 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
1169 dc = wx.ClientDC(self.GetCanvas())
1170 self.GetCanvas().PrepareDC(dc)
1171
1172 if pt._type == CONTROL_POINT_LINE:
1173 pt._originalPos = pt._point
1174 x, y = self._canvas.Snap(x, y)
1175
1176 self.Erase(dc)
1177
1178 # Redraw start and end objects because we've left holes
1179 # when erasing the line
1180 self.GetFrom().OnDraw(dc)
1181 self.GetFrom().OnDrawContents(dc)
1182 self.GetTo().OnDraw(dc)
1183 self.GetTo().OnDrawContents(dc)
1184
1185 self.SetDisableLabel(True)
1186 dc.SetLogicalFunction(OGLRBLF)
1187
1188 pt._xpos = x
1189 pt._ypos = y
1190 pt._point[0] = x
1191 pt._point[1] = y
1192
1193 old_pen = self.GetPen()
1194 old_brush = self.GetBrush()
1195
1196 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1197 self.SetPen(dottedPen)
1198 self.SetBrush(wx.TRANSPARENT_BRUSH)
1199
1200 self.GetEventHandler().OnMoveLink(dc, False)
1201
1202 self.SetPen(old_pen)
1203 self.SetBrush(old_brush)
1204
1205 if pt._type == CONTROL_POINT_ENDPOINT_FROM or pt._type == CONTROL_POINT_ENDPOINT_TO:
1206 self._canvas.SetCursor(wx.StockCursor(wx.CURSOR_BULLSEYE))
1207 pt._oldCursor = wx.STANDARD_CURSOR
1208
1209 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
1210 dc = wx.ClientDC(self.GetCanvas())
1211 self.GetCanvas().PrepareDC(dc)
1212
1213 self.SetDisableLabel(False)
1214
1215 if pt._type == CONTROL_POINT_LINE:
1216 x, y = self._canvas.Snap(x, y)
1217
1218 rpt = wx.RealPoint(x, y)
1219
1220 # Move the control point back to where it was;
1221 # MoveControlPoint will move it to the new position
1222 # if it decides it wants. We only moved the position
1223 # during user feedback so we could redraw the line
1224 # as it changed shape.
1225 pt._xpos = pt._originalPos[0]
1226 pt._ypos = pt._originalPos[1]
1227 pt._point[0] = pt._originalPos[0]
1228 pt._point[1] = pt._originalPos[1]
1229
1230 self.OnMoveMiddleControlPoint(dc, pt, rpt)
1231
1232 if pt._type == CONTROL_POINT_ENDPOINT_FROM:
1233 if pt._oldCursor:
1234 self._canvas.SetCursor(pt._oldCursor)
1235
1236 if self.GetFrom():
1237 self.GetFrom().MoveLineToNewAttachment(dc, self, x, y)
1238
1239 if pt._type == CONTROL_POINT_ENDPOINT_TO:
1240 if pt._oldCursor:
1241 self._canvas.SetCursor(pt._oldCursor)
1242
1243 if self.GetTo():
1244 self.GetTo().MoveLineToNewAttachment(dc, self, x, y)
1245
1246 # This is called only when a non-end control point is moved
1247 def OnMoveMiddleControlPoint(self, dc, lpt, pt):
1248 lpt._xpos = pt[0]
1249 lpt._ypos = pt[1]
1250
1251 lpt._point[0] = pt[0]
1252 lpt._point[1] = pt[1]
1253
1254 self.GetEventHandler().OnMoveLink(dc)
1255
1256 return True
1257
1258 def AddArrow(self, type, end = ARROW_POSITION_END, size = 10.0, xOffset = 0.0, name = "", mf = None, arrowId = -1):
1259 """Add an arrow (or annotation) to the line.
1260
1261 type may currently be one of:
1262
1263 ARROW_HOLLOW_CIRCLE
1264 Hollow circle.
1265 ARROW_FILLED_CIRCLE
1266 Filled circle.
1267 ARROW_ARROW
1268 Conventional arrowhead.
1269 ARROW_SINGLE_OBLIQUE
1270 Single oblique stroke.
1271 ARROW_DOUBLE_OBLIQUE
1272 Double oblique stroke.
1273 ARROW_DOUBLE_METAFILE
1274 Custom arrowhead.
1275
1276 end may currently be one of:
1277
1278 ARROW_POSITION_END
1279 Arrow appears at the end.
1280 ARROW_POSITION_START
1281 Arrow appears at the start.
1282
1283 arrowSize specifies the length of the arrow.
1284
1285 xOffset specifies the offset from the end of the line.
1286
1287 name specifies a name for the arrow.
1288
1289 mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows
1290 metafile.
1291
1292 arrowId is the id for the arrow.
1293 """
1294 arrow = ArrowHead(type, end, size, xOffset, name, mf, arrowId)
1295 self._arcArrows.append(arrow)
1296 return arrow
1297
1298 # Add arrowhead at a particular position in the arrowhead list
1299 def AddArrowOrdered(self, arrow, referenceList, end):
1300 """Add an arrowhead in the position indicated by the reference list
1301 of arrowheads, which contains all legal arrowheads for this line, in
1302 the correct order. E.g.
1303
1304 Reference list: a b c d e
1305 Current line list: a d
1306
1307 Add c, then line list is: a c d.
1308
1309 If no legal arrowhead position, return FALSE. Assume reference list
1310 is for one end only, since it potentially defines the ordering for
1311 any one of the 3 positions. So we don't check the reference list for
1312 arrowhead position.
1313 """
1314 if not referenceList:
1315 return False
1316
1317 targetName = arrow.GetName()
1318
1319 # First check whether we need to insert in front of list,
1320 # because this arrowhead is the first in the reference
1321 # list and should therefore be first in the current list.
1322 refArrow = referenceList[0]
1323 if refArrow.GetName() == targetName:
1324 self._arcArrows.insert(0, arrow)
1325 return True
1326
1327 i1 = i2 = 0
1328 while i1 < len(referenceList) and i2 < len(self._arcArrows):
1329 refArrow = referenceList[i1]
1330 currArrow = self._arcArrows[i2]
1331
1332 # Matching: advance current arrow pointer
1333 if currArrow.GetArrowEnd() == end and currArrow.GetName() == refArrow.GetName():
1334 i2 += 1
1335
1336 # Check if we're at the correct position in the
1337 # reference list
1338 if targetName == refArrow.GetName():
1339 if i2 < len(self._arcArrows):
1340 self._arcArrows.insert(i2, arrow)
1341 else:
1342 self._arcArrows.append(arrow)
1343 return True
1344 i1 += 1
1345
1346 self._arcArrows.append(arrow)
1347 return True
1348
1349 def ClearArrowsAtPosition(self, end):
1350 """Delete the arrows at the specified position, or at any position
1351 if position is -1.
1352 """
1353 if end == -1:
1354 self._arcArrows = []
1355 return
1356
1357 for arrow in self._arcArrows:
1358 if arrow.GetArrowEnd() == end:
1359 self._arcArrows.remove(arrow)
1360
1361 def ClearArrow(self, name):
1362 """Delete the arrow with the given name."""
1363 for arrow in self._arcArrows:
1364 if arrow.GetName() == name:
1365 self._arcArrows.remove(arrow)
1366 return True
1367 return False
1368
1369 def FindArrowHead(self, position, name):
1370 """Find arrowhead by position and name.
1371
1372 if position is -1, matches any position.
1373 """
1374 for arrow in self._arcArrows:
1375 if (position == -1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
1376 return arrow
1377
1378 return None
1379
1380 def FindArrowHeadId(self, arrowId):
1381 """Find arrowhead by id."""
1382 for arrow in self._arcArrows:
1383 if arrowId == arrow.GetId():
1384 return arrow
1385
1386 return None
1387
1388 def DeleteArrowHead(self, position, name):
1389 """Delete arrowhead by position and name.
1390
1391 if position is -1, matches any position.
1392 """
1393 for arrow in self._arcArrows:
1394 if (position == -1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
1395 self._arcArrows.remove(arrow)
1396 return True
1397 return False
1398
1399 def DeleteArrowHeadId(self, id):
1400 """Delete arrowhead by id."""
1401 for arrow in self._arcArrows:
1402 if arrowId == arrow.GetId():
1403 self._arcArrows.remove(arrow)
1404 return True
1405 return False
1406
1407 # Calculate the minimum width a line
1408 # occupies, for the purposes of drawing lines in tools.
1409 def FindMinimumWidth(self):
1410 """Find the horizontal width for drawing a line with arrows in
1411 minimum space. Assume arrows at end only.
1412 """
1413 minWidth = 0.0
1414 for arrowHead in self._arcArrows:
1415 minWidth += arrowHead.GetSize()
1416 if arrowHead != self._arcArrows[-1]:
1417 minWidth += arrowHead + GetSpacing
1418
1419 # We have ABSOLUTE minimum now. So
1420 # scale it to give it reasonable aesthetics
1421 # when drawing with line.
1422 if minWidth > 0:
1423 minWidth = minWidth * 1.4
1424 else:
1425 minWidth = 20.0
1426
1427 self.SetEnds(0.0, 0.0, minWidth, 0.0)
1428 self.Initialise()
1429
1430 return minWidth
1431
1432 def FindLinePosition(self, x, y):
1433 """Find which position we're talking about at this x, y.
1434
1435 Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END.
1436 """
1437 startX, startY, endX, endY = self.GetEnds()
1438
1439 # Find distances from centre, start and end. The smallest wins
1440 centreDistance = math.sqrt((x - self._xpos) * (x - self._xpos) + (y - self._ypos) * (y - self._ypos))
1441 startDistance = math.sqrt((x - startX) * (x - startX) + (y - startY) * (y - startY))
1442 endDistance = math.sqrt((x - endX) * (x - endX) + (y - endY) * (y - endY))
1443
1444 if centreDistance < startDistance and centreDistance < endDistance:
1445 return ARROW_POSITION_MIDDLE
1446 elif startDistance < endDistance:
1447 return ARROW_POSITION_START
1448 else:
1449 return ARROW_POSITION_END
1450
1451 def SetAlignmentOrientation(self, isEnd, isHoriz):
1452 if isEnd:
1453 if isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
1454 self._alignmentEnd != LINE_ALIGNMENT_HORIZ
1455 elif not isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
1456 self._alignmentEnd -= LINE_ALIGNMENT_HORIZ
1457 else:
1458 if isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
1459 self._alignmentStart != LINE_ALIGNMENT_HORIZ
1460 elif not isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
1461 self._alignmentStart -= LINE_ALIGNMENT_HORIZ
1462
1463 def SetAlignmentType(self, isEnd, alignType):
1464 if isEnd:
1465 if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1466 if self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
1467 self._alignmentEnd |= LINE_ALIGNMENT_TO_NEXT_HANDLE
1468 elif self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1469 self._alignmentEnd -= LINE_ALIGNMENT_TO_NEXT_HANDLE
1470 else:
1471 if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1472 if self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
1473 self._alignmentStart |= LINE_ALIGNMENT_TO_NEXT_HANDLE
1474 elif self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1475 self._alignmentStart -= LINE_ALIGNMENT_TO_NEXT_HANDLE
1476
1477 def GetAlignmentOrientation(self, isEnd):
1478 if isEnd:
1479 return self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
1480 else:
1481 return self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
1482
1483 def GetAlignmentType(self, isEnd):
1484 if isEnd:
1485 return self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE
1486 else:
1487 return self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE
1488
1489 def GetNextControlPoint(self, shape):
1490 """Find the next control point in the line after the start / end point,
1491 depending on whether the shape is at the start or end.
1492 """
1493 n = len(self._lineControlPoints)
1494 if self._to == shape:
1495 # Must be END of line, so we want (n - 1)th control point.
1496 # But indexing ends at n-1, so subtract 2.
1497 nn = n - 2
1498 else:
1499 nn = 1
1500 if nn < len(self._lineControlPoints):
1501 return self._lineControlPoints[nn]
1502 return None
1503
1504 def OnCreateLabelShape(self, parent, region, w, h):
1505 return LabelShape(parent, region, w, h)
1506
1507
1508 def OnLabelMovePre(self, dc, labelShape, x, y, old_x, old_y, display):
1509 labelShape._shapeRegion.SetSize(labelShape.GetWidth(), labelShape.GetHeight())
1510
1511 # Find position in line's region list
1512 i = self._regions.index(labelShape._shapeRegion)
1513
1514 xx, yy = self.GetLabelPosition(i)
1515 # Set the region's offset, relative to the default position for
1516 # each region.
1517 labelShape._shapeRegion.SetPosition(x - xx, y - yy)
1518 labelShape.SetX(x)
1519 labelShape.SetY(y)
1520
1521 # Need to reformat to fit region
1522 if labelShape._shapeRegion.GetText():
1523 s = labelShape._shapeRegion.GetText()
1524 labelShape.FormatText(dc, s, i)
1525 self.DrawRegion(dc, labelShape._shapeRegion, xx, yy)
1526 return True
1527