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