]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ogl/lines.py
cb02964527f2a061ca544822a268efd15bdc228f
[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: 20040508
9 # RCS-ID:
10 # Copyright: (c) 2004 Pierre Hjälm - 1998 Julian Smart
11 # Licence: wxWindows license
12 #----------------------------------------------------------------------------
13
14 from __future__ import division
15
16 import sys
17
18 from math import sqrt
19
20 from basic import Shape, ShapeRegion, ControlPoint, RectangleShape
21 from oglmisc import *
22
23 # Line alignment flags
24 # Vertical by default
25 LINE_ALIGNMENT_HORIZ= 1
26 LINE_ALIGNMENT_VERT= 0
27 LINE_ALIGNMENT_TO_NEXT_HANDLE= 2
28 LINE_ALIGNMENT_NONE= 0
29
30
31
32 class LineControlPoint(ControlPoint):
33 def __init__(self, theCanvas = None, object = None, size = 0.0, x = 0.0, y = 0.0, the_type = 0):
34 ControlPoint.__init__(self, theCanvas, object, size, x, y, the_type)
35 self._xpos = x
36 self._ypos = y
37 self._type = the_type
38 self._point = None
39 self._originalPos = None
40
41 def OnDraw(self, dc):
42 RectangleShape.OnDraw(self, dc)
43
44 # Implement movement of Line point
45 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
46 self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
47
48 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
49 self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
50
51 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
52 self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
53
54
55
56 class ArrowHead(object):
57 def __init__(self, type = 0, end = 0, size = 0.0, dist = 0.0, name="",mf = None, arrowId=-1):
58 if isinstance(type, ArrowHead):
59 pass
60 else:
61 self._arrowType = type
62 self._arrowEnd = end
63 self._arrowSize = size
64 self._xOffset = dist
65 self._yOffset = 0.0
66 self._spacing = 5.0
67
68 self._arrowName = name
69 self._metaFile = mf
70 self._id = arrowId
71 if self._id==-1:
72 self._id = wx.NewId()
73
74 def _GetType(self):
75 return self._arrowType
76
77 def GetPosition(self):
78 return self._arrowEnd
79
80 def SetPosition(self, pos):
81 self._arrowEnd = pos
82
83 def GetXOffset(self):
84 return self._xOffset
85
86 def GetYOffset(self):
87 return self._yOffset
88
89 def GetSpacing(self):
90 return self._spacing
91
92 def GetSize(self):
93 return self._arrowSize
94
95 def SetSize(self, size):
96 self._arrowSize = size
97 if self._arrowType == ARROW_METAFILE and self._metaFile:
98 oldWidth = self._metaFile._width
99 if oldWidth == 0:
100 return
101
102 scale = size / oldWidth
103 if scale != 1:
104 self._metaFile.Scale(scale, scale)
105
106 def GetName(self):
107 return self._arrowName
108
109 def SetXOffset(self, x):
110 self._xOffset = x
111
112 def SetYOffset(self, y):
113 self._yOffset = y
114
115 def GetMetaFile(self):
116 return self._metaFile
117
118 def GetId(self):
119 return self._id
120
121 def GetArrowEnd(self):
122 return self._arrowEnd
123
124 def GetArrowSize(self):
125 return self._arrowSize
126
127 def SetSpacing(self, sp):
128 self._spacing = sp
129
130
131
132 class LabelShape(RectangleShape):
133 def __init__(self, parent, region, w, h):
134 RectangleShape.__init__(self, w, h)
135 self._lineShape = parent
136 self._shapeRegion = region
137 self.SetPen(wx.ThePenList.FindOrCreatePen(wx.Colour(0, 0, 0), 1, wx.DOT))
138
139 def OnDraw(self, dc):
140 if self._lineShape and not self._lineShape.GetDrawHandles():
141 return
142
143 x1 = self._xpos-self._width / 2
144 y1 = self._ypos-self._height / 2
145
146 if self._pen:
147 if self._pen.GetWidth() == 0:
148 dc.SetPen(wx.Pen(wx.WHITE, 1, wx.TRANSPARENT))
149 else:
150 dc.SetPen(self._pen)
151 dc.SetBrush(wx.TRANSPARENT_BRUSH)
152
153 if self._cornerRadius>0:
154 dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
155 else:
156 dc.DrawRectangle(x1, y1, self._width, self._height)
157
158 def OnDrawContents(self, dc):
159 pass
160
161 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
162 RectangleShape.OnDragLeft(self, draw, x, y, keys, attachment)
163
164 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
165 RectangleShape.OnBeginDragLeft(self, x, y, keys, attachment)
166
167 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
168 RectangleShape.OnEndDragLeft(self, x, y, keys, attachment)
169
170 def OnMovePre(self, dc, x, y, old_x, old_y, display):
171 return self._lineShape.OnLabelMovePre(dc, self, x, y, old_x, old_y, display)
172
173 # Divert left and right clicks to line object
174 def OnLeftClick(self, x, y, keys = 0, attachment = 0):
175 self._lineShape.GetEventHandler().OnLeftClick(x, y, keys, attachment)
176
177 def OnRightClick(self, x, y, keys = 0, attachment = 0):
178 self._lineShape.GetEventHandler().OnRightClick(x, y, keys, attachment)
179
180
181
182 class LineShape(Shape):
183 """LineShape may be attached to two nodes;
184 it may be segmented, in which case a control point is drawn for each joint.
185
186 A wxLineShape may have arrows at the beginning, end and centre.
187
188 Derived from:
189 Shape
190 """
191 def __init__(self):
192 Shape.__init__(self)
193
194 self._sensitivity = OP_CLICK_LEFT | OP_CLICK_RIGHT
195 self._draggable = False
196 self._attachmentTo = 0
197 self._attachmentFrom = 0
198 self._from = None
199 self._to = None
200 self._erasing = False
201 self._arrowSpacing = 5.0
202 self._ignoreArrowOffsets = False
203 self._isSpline = False
204 self._maintainStraightLines = False
205 self._alignmentStart = 0
206 self._alignmentEnd = 0
207
208 self._lineControlPoints = None
209
210 # Clear any existing regions (created in an earlier constructor)
211 # and make the three line regions.
212 self.ClearRegions()
213 for name in ["Middle","Start","End"]:
214 newRegion = ShapeRegion()
215 newRegion.SetName(name)
216 newRegion.SetSize(150, 50)
217 self._regions.append(newRegion)
218
219 self._labelObjects = [None, None, None]
220 self._lineOrientations = []
221 self._lineControlPoints = []
222 self._arcArrows = []
223
224 def __del__(self):
225 if self._lineControlPoints:
226 self.ClearPointList(self._lineControlPoints)
227 self._lineControlPoints = []
228 for i in range(3):
229 if self._labelObjects[i]:
230 self._labelObjects[i].Select(False)
231 self._labelObjects[i].RemoveFromCanvas(self._canvas)
232 self._labelObjects = []
233 self.ClearArrowsAtPosition(-1)
234
235 def GetFrom(self):
236 """Return the 'from' object."""
237 return self._from
238
239 def GetTo(self):
240 """Return the 'to' object."""
241 return self._to
242
243 def GetAttachmentFrom(self):
244 """Return the attachment point on the 'from' node."""
245 return self._attachmentFrom
246
247 def GetAttachmentTo(self):
248 """Return the attachment point on the 'to' node."""
249 return self._attachmentTo
250
251 def GetLineControlPoints(self):
252 return self._lineControlPoints
253
254 def SetSpline(self, spline):
255 """Specifies whether a spline is to be drawn through the control points."""
256 self._isSpline = spline
257
258 def IsSpline(self):
259 """TRUE if a spline is drawn through the control points."""
260 return self._isSpline
261
262 def SetAttachmentFrom(self, attach):
263 """Set the 'from' shape attachment."""
264 self._attachmentFrom = attach
265
266 def SetAttachmentTo(self, attach):
267 """Set the 'to' shape attachment."""
268 self._attachmentTo = attach
269
270 # This is really to distinguish between lines and other images.
271 # For lines, want to pass drag to canvas, since lines tend to prevent
272 # dragging on a canvas (they get in the way.)
273 def Draggable(self):
274 return False
275
276 def SetIgnoreOffsets(self, ignore):
277 """Set whether to ignore offsets from the end of the line when drawing."""
278 self._ignoreArrowOffsets = ignore
279
280 def GetArrows(self):
281 return self._arcArrows
282
283 def GetAlignmentStart(self):
284 return self._alignmentStart
285
286 def GetAlignmentEnd(self):
287 return self._alignmentEnd
288
289 def IsEnd(self, nodeObject):
290 """TRUE if shape is at the end of the line."""
291 return self._to == nodeObject
292
293 def MakeLineControlPoints(self, n):
294 """Make a given number of control points (minimum of two)."""
295 if self._lineControlPoints:
296 self.ClearPointList(self._lineControlPoints)
297 self._lineControlPoints = []
298
299 for _ in range(n):
300 point = wx.RealPoint(-999,-999)
301 self._lineControlPoints.append(point)
302
303 def InsertLineControlPoint(self, dc = None):
304 """Insert a control point at an arbitrary position."""
305 if dc:
306 self.Erase(dc)
307
308 last_point = self._lineControlPoints[-1]
309 second_last_point = self._lineControlPoints[-2]
310
311 line_x = (last_point[0] + second_last_point[0]) / 2
312 line_y = (last_point[1] + second_last_point[1]) / 2
313
314 point = wx.RealPoint(line_x, line_y)
315 self._lineControlPoints.insert(len(self._lineControlPoints), point)
316
317 def DeleteLineControlPoint(self):
318 """Delete an arbitary point on the line."""
319 if len(self._lineControlPoints)<3:
320 return False
321
322 del self._lineControlPoints[-2]
323 return True
324
325 def Initialise(self):
326 """Initialise the line object."""
327 if self._lineControlPoints:
328 # Just move the first and last control points
329 first_point = self._lineControlPoints[0]
330 last_point = self._lineControlPoints[-1]
331
332 # If any of the line points are at -999, we must
333 # initialize them by placing them half way between the first
334 # and the last.
335
336 for point in self._lineControlPoints[1:]:
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 point[0] = (x2-x1) / 2 + x1
351 point[1] = (y2-y1) / 2 + y1
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, yp-h / 2, 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, yp-h / 2, 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)
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, point[1] + dy / 2
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 # Find centre point
497 first_point = self._lineControlPoints[0]
498 last_point = self._lineControlPoints[-1]
499
500 first_point[0] = x1
501 first_point[1] = y1
502 last_point[0] = x2
503 last_point[1] = y2
504
505 self._xpos = (x1 + x2) / 2
506 self._ypos = (y1 + y2) / 2
507
508 # Get absolute positions of ends
509 def GetEnds(self):
510 """Get the visible endpoints of the lines for drawing between two objects."""
511 first_point = self._lineControlPoints[0]
512 last_point = self._lineControlPoints[-1]
513
514 return (first_point[0], first_point[1]), (last_point[0], last_point[1])
515
516 def SetAttachments(self, from_attach, to_attach):
517 """Specify which object attachment points should be used at each end
518 of the line.
519 """
520 self._attachmentFrom = from_attach
521 self._attachmentTo = to_attach
522
523 def HitTest(self, x, y):
524 if not self._lineControlPoints:
525 return False
526
527 # Look at label regions in case mouse is over a label
528 inLabelRegion = False
529 for i in range(3):
530 if self._regions[i]:
531 region = self._regions[i]
532 if len(region._formattedText):
533 xp, yp = self.GetLabelPosition(i)
534 # Offset region from default label position
535 cx, cy = region.GetPosition()
536 cw, ch = region.GetSize()
537 cx += xp
538 cy += yp
539
540 rLeft = cx-cw / 2
541 rTop = cy-ch / 2
542 rRight = cx + cw / 2
543 rBottom = cy + ch / 2
544 if x>rLeft and x<rRight and y>rTop and y<rBottom:
545 inLabelRegion = True
546 break
547
548 for i in range(len(self._lineControlPoints)-1):
549 point1 = self._lineControlPoints[i]
550 point2 = self._lineControlPoints[i + 1]
551
552 # For inaccurate mousing allow 8 pixel corridor
553 extra = 4
554
555 dx = point2[0]-point1[0]
556 dy = point2[1]-point1[1]
557
558 seg_len = sqrt(dx * dx + dy * dy)
559 if dy == 0 or dx == 0:
560 return False
561 distance_from_seg = seg_len * ((x-point1[0]) * dy-(y-point1[1]) * dx) / (dy * dy + dx * dx)
562 distance_from_prev = seg_len * ((y-point1[1]) * dy + (x-point1[0]) * dx) / (dy * dy + dx * dx)
563
564 if abs(distance_from_seg)<extra and distance_from_prev >= 0 and distance_from_prev <= seg_len or inLabelRegion:
565 return 0, distance_from_seg
566
567 return False
568
569 def DrawArrows(self, dc):
570 """Draw all arrows."""
571 # Distance along line of each arrow: space them out evenly
572 startArrowPos = 0.0
573 endArrowPos = 0.0
574 middleArrowPos = 0.0
575
576 for arrow in self._arcArrows:
577 ah = arrow.GetArrowEnd()
578 if ah == ARROW_POSITION_START:
579 if arrow.GetXOffset() and not self._ignoreArrowOffsets:
580 # If specified, x offset is proportional to line length
581 self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
582 else:
583 self.DrawArrow(dc, arrow, startArrowPos, False)
584 startArrowPos += arrow.GetSize() + arrow.GetSpacing()
585 elif ah == ARROW_POSITION_END:
586 if arrow.GetXOffset() and not self._ignoreArrowOffsets:
587 self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
588 else:
589 self.DrawArrow(dc, arrow, endArrowPos, False)
590 endArrowPos += arrow.GetSize() + arrow.GetSpacing()
591 elif ah == ARROW_POSITION_MIDDLE:
592 arrow.SetXOffset(middleArrowPos)
593 if arrow.GetXOffset() and not self._ignoreArrowOffsets:
594 self.DrawArrow(dc, arrow, arrow.GetXOffset(), True)
595 else:
596 self.DrawArrow(dc, arrow, middleArrowPos, False)
597 middleArrowPos += arrow.GetSize() + arrow.GetSpacing()
598
599 def DrawArrow(self, dc, arrow, XOffset, proportionalOffset):
600 """Draw the given arrowhead (or annotation)."""
601 first_line_point = self._lineControlPoints[0]
602 second_line_point = self._lineControlPoints[1]
603
604 last_line_point = self._lineControlPoints[-1]
605 second_last_line_point = self._lineControlPoints[-2]
606
607 # Position of start point of line, at the end of which we draw the arrow
608 startPositionX, startPositionY = 0.0, 0.0
609
610 ap = arrow.GetPosition()
611 if ap == ARROW_POSITION_START:
612 # If we're using a proportional offset, calculate just where this
613 # will be on the line.
614 realOffset = XOffset
615 if proportionalOffset:
616 totalLength = 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]))
617 realOffset = XOffset * totalLength
618
619 positionOnLineX, positionOnLineY = GetPointOnLine(second_line_point[0], second_line_point[1], first_line_point[0], first_line_point[1], realOffset)
620
621 startPositionX = second_line_point[0]
622 startPositionY = second_line_point[1]
623 elif ap == ARROW_POSITION_END:
624 # If we're using a proportional offset, calculate just where this
625 # will be on the line.
626 realOffset = XOffset
627 if proportionalOffset:
628 totalLength = 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]));
629 realOffset = XOffset * totalLength
630
631 positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], last_line_point[0], last_line_point[1], realOffset)
632
633 startPositionX = second_last_line_point[0]
634 startPositionY = second_last_line_point[1]
635 elif ap == ARROW_POSITION_MIDDLE:
636 # Choose a point half way between the last and penultimate points
637 x = (last_line_point[0] + second_last_line_point[0]) / 2
638 y = (last_line_point[1] + second_last_line_point[1]) / 2
639
640 # If we're using a proportional offset, calculate just where this
641 # will be on the line.
642 realOffset = XOffset
643 if proportionalOffset:
644 totalLength = 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));
645 realOffset = XOffset * totalLength
646
647 positionOnLineX, positionOnLineY = GetPointOnLine(second_last_line_point[0], second_last_line_point[1], x, y, realOffset)
648 startPositionX = second_last_line_point[0]
649 startPositionY = second_last_line_point[1]
650
651 # Add yOffset to arrow, if any
652
653 # The translation that the y offset may give
654 deltaX = 0.0
655 deltaY = 0.0
656 if arrow.GetYOffset and not self._ignoreArrowOffsets:
657 # |(x4, y4)
658 # |d
659 # |
660 # (x1, y1)--------------(x3, y3)------------------(x2, y2)
661 # x4 = x3 - d * sin(theta)
662 # y4 = y3 + d * cos(theta)
663 #
664 # Where theta = tan(-1) of (y3-y1) / (x3-x1)
665 x1 = startPositionX
666 y1 = startPositionY
667 x3 = positionOnLineX
668 y3 = positionOnLineY
669 d=-arrow.GetYOffset() # Negate so +offset is above line
670
671 if x3 == x1:
672 theta = pi / 2
673 else:
674 theta = atan((y3-y1) / (x3-x1))
675
676 x4 = x3-d * sin(theta)
677 y4 = y3 + d * cos(theta)
678
679 deltaX = x4-positionOnLineX
680 deltaY = y4-positionOnLineY
681
682 at = arrow._GetType()
683 if at == ARROW_ARROW:
684 arrowLength = arrow.GetSize()
685 arrowWidth = arrowLength / 3
686
687 tip_x, tip_y, side1_x, side1_y, side2_x, side2_y = GetArrowPoints(startPositionX + deltaX, startPositionY + deltaY, positionOnLineX + deltaX, positionOnLineY + deltaY, arrowLength, arrowWidth)
688
689 points = [[tip_x, tip_y],
690 [side1_x, side1_y],
691 [side2_x, side2_y],
692 [tip_x, tip_y]]
693
694 dc.SetPen(self._pen)
695 dc.SetBrush(self._brush)
696 dc.DrawPolygon(points)
697 elif at in [ARROW_HOLLOW_CIRCLE, ARROW_FILLED_CIRCLE]:
698 # Find point on line of centre of circle, which is a radius away
699 # from the end position
700 diameter = arrow.GetSize()
701 x, y = GetPointOnLine(startPositionX + deltaX, startPositionY + deltaY,
702 positionOnLineX + deltaX, positionOnLineY + deltaY,
703 diameter / 2)
704 x1 = x-diameter / 2
705 y1 = y-diameter / 2
706 dc.SetPen(self._pen)
707 if arrow._GetType() == ARROW_HOLLOW_CIRCLE:
708 dc.SetBrush(self.GetBackgroundBrush())
709 else:
710 dc.SetBrush(self._brush)
711
712 dc.DrawEllipse(x1, y1, diameter, diameter)
713 elif at == ARROW_SINGLE_OBLIQUE:
714 pass
715 elif at == ARROW_METAFILE:
716 if arrow.GetMetaFile():
717 # Find point on line of centre of object, which is a half-width away
718 # from the end position
719 #
720 # width
721 # <-- start pos <-----><-- positionOnLineX
722 # _____
723 # --------------| x | <-- e.g. rectangular arrowhead
724 # -----
725 #
726 x, y = GetPointOnLine(startPositionX, startPositionY,
727 positionOnLineX, positionOnLineY,
728 arrow.GetMetaFile()._width / 2)
729 # Calculate theta for rotating the metafile.
730 #
731 # |
732 # | o(x2, y2) 'o' represents the arrowhead.
733 # | /
734 # | /
735 # | /theta
736 # | /(x1, y1)
737 # |______________________
738 #
739 theta = 0.0
740 x1 = startPositionX
741 y1 = startPositionY
742 x2 = positionOnLineX
743 y2 = positionOnLineY
744
745 if x1 == x2 and y1 == y2:
746 theta = 0.0
747 elif x1 == x2 and y1>y2:
748 theta = 3.0 * pi / 2
749 elif x1 == x2 and y2>y1:
750 theta = pi / 2
751 elif x2>x1 and y2 >= y1:
752 theta = atan((y2-y1) / (x2-x1))
753 elif x2<x1:
754 theta = pi + atan((y2-y1) / (x2-x1))
755 elif x2>x1 and y2<y1:
756 theta = 2 * pi + atan((y2-y1) / (x2-x1))
757 else:
758 raise "Unknown arrowhead rotation case"
759
760 # Rotate about the centre of the object, then place
761 # the object on the line.
762 if arrow.GetMetaFile().GetRotateable():
763 arrow.GetMetaFile().Rotate(0.0, 0.0, theta)
764
765 if self._erasing:
766 # If erasing, just draw a rectangle
767 minX, minY, maxX, maxY = arrow.GetMetaFile().GetBounds()
768 # Make erasing rectangle slightly bigger or you get droppings
769 extraPixels = 4
770 dc.DrawRectangle(deltaX + x + minX-extraPixels / 2, deltaY + y + minY-extraPixels / 2, maxX-minX + extraPixels, maxY-minY + extraPixels)
771 else:
772 arrow.GetMetaFile().Draw(dc, x + deltaX, y + deltaY)
773
774 def OnErase(self, dc):
775 old_pen = self._pen
776 old_brush = self._brush
777
778 bg_pen = self.GetBackgroundPen()
779 bg_brush = self.GetBackgroundBrush()
780 self.SetPen(bg_pen)
781 self.SetBrush(bg_brush)
782
783 bound_x, bound_y = self.GetBoundingBoxMax()
784 if self._font:
785 dc.SetFont(self._font)
786
787 # Undraw text regions
788 for i in range(3):
789 if self._regions[i]:
790 x, y = self.GetLabelPosition(i)
791 self.EraseRegion(dc, self._regions[i], x, y)
792
793 # Undraw line
794 dc.SetPen(self.GetBackgroundPen())
795 dc.SetBrush(self.GetBackgroundBrush())
796
797 # Drawing over the line only seems to work if the line has a thickness
798 # of 1.
799 if old_pen and old_pen.GetWidth()>1:
800 dc.DrawRectangle(self._xpos-bound_x / 2-2, self._ypos-bound_y / 2-2,
801 bound_x + 4, bound_y + 4)
802 else:
803 self._erasing = True
804 self.GetEventHandler().OnDraw(dc)
805 self.GetEventHandler().OnEraseControlPoints(dc)
806 self._erasing = False
807
808 if old_pen:
809 self.SetPen(old_pen)
810 if old_brush:
811 self.SetBrush(old_brush)
812
813 def GetBoundingBoxMin(self):
814 x1, y1 = 10000, 10000
815 x2, y2=-10000,-10000
816
817 for point in self._lineControlPoints:
818 if point[0]<x1:
819 x1 = point[0]
820 if point[1]<y1:
821 y1 = point[1]
822 if point[0]>x2:
823 x2 = point[0]
824 if point[1]>y2:
825 y2 = point[1]
826
827 return x2-x1, y2-y1
828
829 # For a node image of interest, finds the position of this arc
830 # amongst all the arcs which are attached to THIS SIDE of the node image,
831 # and the number of same.
832 def FindNth(self, image, incoming):
833 """Find the position of the line on the given object.
834
835 Specify whether incoming or outgoing lines are being considered
836 with incoming.
837 """
838 n=-1
839 num = 0
840
841 if image == self._to:
842 this_attachment = self._attachmentTo
843 else:
844 this_attachment = self._attachmentFrom
845
846 # Find number of lines going into / out of this particular attachment point
847 for line in image.GetLines():
848 if line._from == image:
849 # This is the nth line attached to 'image'
850 if line == self and not incoming:
851 n = num
852
853 # Increment num count if this is the same side (attachment number)
854 if line._attachmentFrom == this_attachment:
855 num += 1
856
857 if line._to == image:
858 # This is the nth line attached to 'image'
859 if line == self and incoming:
860 n = num
861
862 # Increment num count if this is the same side (attachment number)
863 if line._attachmentTo == this_attachment:
864 num += 1
865
866 return n, num
867
868 def OnDrawOutline(self, dc, x, y, w, h):
869 old_pen = self._pen
870 old_brush = self._brush
871
872 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
873 self.SetPen(dottedPen)
874 self.SetBrush(wx.TRANSPARENT_BRUSH)
875
876 self.GetEventHandler().OnDraw(dc)
877
878 if old_pen:
879 self.SetPen(old_pen)
880 else:
881 self.SetPen(None)
882 if old_brush:
883 self.SetBrush(old_brush)
884 else:
885 self.SetBrush(None)
886
887 def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
888 x_offset = x-old_x
889 y_offset = y-old_y
890
891 if self._lineControlPoints and not (x_offset == 0 and y_offset == 0):
892 for point in self._lineControlPoints:
893 point[0] += x_offset
894 point[1] += y_offset
895
896 # Move temporary label rectangles if necessary
897 for i in range(3):
898 if self._labelObjects[i]:
899 self._labelObjects[i].Erase(dc)
900 xp, yp = self.GetLabelPosition(i)
901 if i<len(self._regions):
902 xr, yr = self._regions[i].GetPosition()
903 else:
904 xr, yr = 0, 0
905 self._labelObjects[i].Move(dc, xp + xr, yp + yr)
906 return True
907
908 def OnMoveLink(self, dc, moveControlPoints = True):
909 """Called when a connected object has moved, to move the link to
910 correct position
911 """
912 if not self._from or not self._to:
913 return
914
915 if len(self._lineControlPoints)>2:
916 self.Initialise()
917
918 # Do each end - nothing in the middle. User has to move other points
919 # manually if necessary
920 end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints()
921
922 first = self._lineControlPoints[0]
923 last = self._lineControlPoints[-1]
924
925 oldX, oldY = self._xpos, self._ypos
926
927 self.SetEnds(end_x, end_y, other_end_x, other_end_y)
928
929 # Do a second time, because one may depend on the other
930 end_x, end_y, other_end_x, other_end_y = self.FindLineEndPoints()
931 self.SetEnds(end_x, end_y, other_end_x, other_end_y)
932
933 # Try to move control points with the arc
934 x_offset = self._xpos-oldX
935 y_offset = self._ypos-oldY
936
937 # Only move control points if it's a self link. And only works
938 # if attachment mode is ON
939 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):
940 for point in self._lineControlPoints[1:-1]:
941 point.x += x_offset
942 point.y += y_offset
943
944 self.Move(dc, self._xpos, self._ypos)
945
946 def FindLineEndPoints(self):
947 """Finds the x, y points at the two ends of the line.
948
949 This function can be used by e.g. line-routing routines to
950 get the actual points on the two node images where the lines will be
951 drawn to / from.
952 """
953 if not self._from or not self._to:
954 return
955
956 # Do each end - nothing in the middle. User has to move other points
957 # manually if necessary.
958 second_point = self._lineControlPoints[1]
959 second_last_point = self._lineControlPoints[-2]
960
961 if len(self._lineControlPoints)>2:
962 if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
963 nth, no_arcs = self.FindNth(self._from, False) # Not incoming
964 end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
965 else:
966 end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), second_point[0], second_point[1])
967
968 if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
969 nth, no_arch = self.FindNth(self._to, True) # Incoming
970 other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arch, self)
971 else:
972 other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), second_last_point[0], second_last_point[1])
973 else:
974 fromX = self._from.GetX()
975 fromY = self._from.GetY()
976 toX = self._to.GetX()
977 toY = self._to.GetY()
978
979 if self._from.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
980 nth, no_arcs = self.FindNth(self._from, False)
981 end_x, end_y = self._from.GetAttachmentPosition(self._attachmentFrom, nth, no_arcs, self)
982 fromX = end_x
983 fromY = end_y
984
985 if self._to.GetAttachmentMode() != ATTACHMENT_MODE_NONE:
986 nth, no_arcs = self.FindNth(self._to, True)
987 other_end_x, other_end_y = self._to.GetAttachmentPosition(self._attachmentTo, nth, no_arcs, self)
988 toX = other_end_x
989 toY = other_end_y
990
991 if self._from.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
992 end_x, end_y = self._from.GetPerimeterPoint(self._from.GetX(), self._from.GetY(), toX, toY)
993
994 if self._to.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
995 other_end_x, other_end_y = self._to.GetPerimeterPoint(self._to.GetX(), self._to.GetY(), fromX, fromY)
996
997 #print type(self._from), type(self._to), end_x, end_y, other_end_x, other_end_y
998 return end_x, end_y, other_end_x, other_end_y
999
1000 def OnDraw(self, dc):
1001 if not self._lineControlPoints:
1002 return
1003
1004 if self._pen:
1005 dc.SetPen(self._pen)
1006 if self._brush:
1007 dc.SetBrush(self._brush)
1008
1009 points = []
1010 for point in self._lineControlPoints:
1011 points.append(wx.Point(point.x, point.y))
1012
1013 #print points
1014 if self._isSpline:
1015 dc.DrawSpline(points)
1016 else:
1017 dc.DrawLines(points)
1018
1019 if sys.platform[:3]=="win":
1020 # For some reason, last point isn't drawn under Windows
1021 dc.DrawPoint(points[-1])
1022
1023 # Problem with pen - if not a solid pen, does strange things
1024 # to the arrowhead. So make (get) a new pen that's solid.
1025 if self._pen and self._pen.GetStyle() != wx.SOLID:
1026 solid_pen = wx.ThePenList().FindOrCreatePen(self._pen.GetColour(), 1, wx.SOLID)
1027 if solid_pen:
1028 dc.SetPen(solid_pen)
1029
1030 self.DrawArrows(dc)
1031
1032 def OnDrawControlPoints(self, dc):
1033 if not self._drawHandles:
1034 return
1035
1036 # Draw temporary label rectangles if necessary
1037 for i in range(3):
1038 if self._labelObjects[i]:
1039 self._labelObjects[i].Draw(dc)
1040
1041 Shape.OnDrawControlPoints(self, dc)
1042
1043 def OnEraseControlPoints(self, dc):
1044 # Erase temporary label rectangles if necessary
1045
1046 for i in range(3):
1047 if self._labelObjects[i]:
1048 self._labelObjects[i].Erase(dc)
1049
1050 Shape.OnEraseControlPoints(self, dc)
1051
1052 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
1053 pass
1054
1055 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1056 pass
1057
1058 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1059 pass
1060
1061 def OnDrawContents(self, dc):
1062 if self.GetDisableLabel():
1063 return
1064
1065 for i in range(3):
1066 if self._regions[i]:
1067 x, y = self.GetLabelPosition(i)
1068 self.DrawRegion(dc, self._regions[i], x, y)
1069
1070 def SetTo(self, object):
1071 """Set the 'to' object for the line."""
1072 self._to = object
1073
1074 def SetFrom(self, object):
1075 """Set the 'from' object for the line."""
1076 self._from = object
1077
1078 def MakeControlPoints(self):
1079 """Make handle control points."""
1080 if self._canvas and self._lineControlPoints:
1081 first = self._lineControlPoints[0]
1082 last = self._lineControlPoints[-1]
1083
1084 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, first[0], first[1], CONTROL_POINT_ENDPOINT_FROM)
1085 control._point = first
1086 self._canvas.AddShape(control)
1087 self._controlPoints.Append(control)
1088
1089 for point in self._lineControlPoints[1:-1]:
1090 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point[0], point[1], CONTROL_POINT_LINE)
1091 control._point = point
1092 self._canvas.AddShape(control)
1093 self._controlPoints.Append(control)
1094
1095 control = LineControlPoint(self._canvas, self, CONTROL_POINT_SIZE, last[0], last[1], CONTROL_POINT_ENDPOINT_TO)
1096 control._point = last
1097 self._canvas.AddShape(control)
1098 self._controlPoints.Append(control)
1099
1100 def ResetControlPoints(self):
1101 if self._canvas and self._lineControlPoints:
1102 for i in range(min(len(self._controlPoints), len(self._lineControlPoints))):
1103 point = self._lineControlPoints[i]
1104 control = self._controlPoints[i]
1105 control.SetX(point[0])
1106 control.SetY(point[1])
1107
1108 # Override select, to create / delete temporary label-moving objects
1109 def Select(self, select, dc = None):
1110 Shape.Select(self, select, dc)
1111 if select:
1112 for i in range(3):
1113 if self._regions[i]:
1114 region = self._regions[i]
1115 if region._formattedText:
1116 w, h = region.GetSize()
1117 x, y = region.GetPosition()
1118 xx, yy = self.GetLabelPosition(i)
1119
1120 if self._labelObjects[i]:
1121 self._labelObjects[i].Select(False)
1122 self._labelObjects[i].RemoveFromCanvas(self._canvas)
1123
1124 self._labelObjects[i] = self.OnCreateLabelShape(self, region, w, h)
1125 self._labelObjects[i].AddToCanvas(self._canvas)
1126 self._labelObjects[i].Show(True)
1127 if dc:
1128 self._labelObjects[i].Move(dc, x + xx, y + yy)
1129 self._labelObjects[i].Select(True, dc)
1130 else:
1131 for i in range(3):
1132 if self._labelObjects[i]:
1133 self._labelObjects[i].Select(False, dc)
1134 self._labelObjects[i].Erase(dc)
1135 self._labelObjects[i].RemoveFromCanvas(self._canvas)
1136 self._labelObjects[i] = None
1137
1138 # Control points ('handles') redirect control to the actual shape, to
1139 # make it easier to override sizing behaviour.
1140 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
1141 dc = wx.ClientDC(self.GetCanvas())
1142 self.GetCanvas().PrepareDC(dc)
1143
1144 dc.SetLogicalFunction(OGLRBLF)
1145
1146 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1147 dc.SetPen(dottedPen)
1148 dc.SetBrush(wx.TRANSPARENT_BRUSH)
1149
1150 if pt._type == CONTROL_POINT_LINE:
1151 x, y = self._canvas.Snap()
1152
1153 pt.SetX(x)
1154 pt.SetY(y)
1155 pt._point[0] = x
1156 pt._point[1] = y
1157
1158 old_pen = self.GetPen()
1159 old_brush = self.GetBrush()
1160
1161 self.SetPen(dottedPen)
1162 self.SetBrush(wx.TRANSPARENT_BRUSH)
1163
1164 self.GetEventHandler().OnMoveLink(dc, False)
1165
1166 self.SetPen(old_pen)
1167 self.SetBrush(old_brush)
1168
1169 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
1170 dc = wx.ClientDC(self.GetCanvas())
1171 self.GetCanvas().PrepareDC(dc)
1172
1173 if pt._type == CONTROL_POINT_LINE:
1174 pt._originalPos = pt._point
1175 x, y = self._canvas.Snap()
1176
1177 self.Erase(dc)
1178
1179 # Redraw start and end objects because we've left holes
1180 # when erasing the line
1181 self.GetFrom().OnDraw(dc)
1182 self.GetFrom().OnDrawContents(dc)
1183 self.GetTo().OnDraw(dc)
1184 self.GetTo().OnDrawContents(dc)
1185
1186 self.SetDisableLabel(True)
1187 dc.SetLogicalFunction(OGLRBLF)
1188
1189 pt._xpos = x
1190 pt._ypos = y
1191 pt._point[0] = x
1192 pt._point[1] = y
1193
1194 old_pen = self.GetPen()
1195 old_brush = self.GetBrush()
1196
1197 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1198 self.SetPen(dottedPen)
1199 self.SetBrush(wx.TRANSPARENT_BRUSH)
1200
1201 self.GetEventHandler().OnMoveLink(dc, False)
1202
1203 self.SetPen(old_pen)
1204 self.SetBrush(old_brush)
1205
1206 if pt._type == CONTROL_POINT_ENDPOINT_FROM or pt._type == CONTROL_POINT_ENDPOINT_TO:
1207 self._canvas.SetCursor(wx.Cursor(wx.CURSOR_BULLSEYE))
1208 pt._oldCursor = wx.STANDARD_CURSOR
1209
1210 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
1211 dc = wx.ClientDC(self.GetCanvas())
1212 self.GetCanvas().PrepareDC(dc)
1213
1214 self.SetDisableLabel(False)
1215
1216 if pt._type == CONTROL_POINT_LINE:
1217 x, y = self._canvas.Snap()
1218
1219 rpt = wx.RealPoint(x, y)
1220
1221 # Move the control point back to where it was;
1222 # MoveControlPoint will move it to the new position
1223 # if it decides it wants. We only moved the position
1224 # during user feedback so we could redraw the line
1225 # as it changed shape.
1226 pt._xpos = pt._originalPos[0]
1227 pt._ypos = pt._originalPos[1]
1228 pt._point[0] = pt._originalPos[0]
1229 pt._point[1] = pt._originalPos[1]
1230
1231 self.OnMoveMiddleControlPoint(dc, pt, rpt)
1232
1233 if pt._type == CONTROL_POINT_ENDPOINT_FROM:
1234 if pt._oldCursor:
1235 self._canvas.SetCursor(pt._oldCursor)
1236
1237 if self.GetFrom():
1238 self.GetFrom().MoveLineToNewAttachment(dc, self, x, y)
1239
1240 if pt._type == CONTROL_POINT_ENDPOINT_TO:
1241 if pt._oldCursor:
1242 self._canvas.SetCursor(pt._oldCursor)
1243
1244 if self.GetTo():
1245 self.GetTo().MoveLineToNewAttachment(dc, self, x, y)
1246
1247 # This is called only when a non-end control point is moved
1248 def OnMoveMiddleControlPoint(self, dc, lpt, pt):
1249 lpt._xpos = pt[0]
1250 lpt._ypos = pt[1]
1251
1252 lpt._point[0] = pt[0]
1253 lpt._point[1] = pt[1]
1254
1255 self.GetEventHandler().OnMoveLink(dc)
1256
1257 return True
1258
1259 def AddArrow(self, type, end = ARROW_POSITION_END, size = 10.0, xOffset = 0.0, name="",mf = None, arrowId=-1):
1260 """Add an arrow (or annotation) to the line.
1261
1262 type may currently be one of:
1263
1264 ARROW_HOLLOW_CIRCLE
1265 Hollow circle.
1266 ARROW_FILLED_CIRCLE
1267 Filled circle.
1268 ARROW_ARROW
1269 Conventional arrowhead.
1270 ARROW_SINGLE_OBLIQUE
1271 Single oblique stroke.
1272 ARROW_DOUBLE_OBLIQUE
1273 Double oblique stroke.
1274 ARROW_DOUBLE_METAFILE
1275 Custom arrowhead.
1276
1277 end may currently be one of:
1278
1279 ARROW_POSITION_END
1280 Arrow appears at the end.
1281 ARROW_POSITION_START
1282 Arrow appears at the start.
1283
1284 arrowSize specifies the length of the arrow.
1285
1286 xOffset specifies the offset from the end of the line.
1287
1288 name specifies a name for the arrow.
1289
1290 mf can be a wxPseduoMetaFile, perhaps loaded from a simple Windows
1291 metafile.
1292
1293 arrowId is the id for the arrow.
1294 """
1295 arrow = ArrowHead(type, end, size, xOffset, name, mf, arrowId)
1296 self._arcArrows.append(arrow)
1297 return arrow
1298
1299 # Add arrowhead at a particular position in the arrowhead list
1300 def AddArrowOrdered(self, arrow, referenceList, end):
1301 """Add an arrowhead in the position indicated by the reference list
1302 of arrowheads, which contains all legal arrowheads for this line, in
1303 the correct order. E.g.
1304
1305 Reference list: a b c d e
1306 Current line list: a d
1307
1308 Add c, then line list is: a c d.
1309
1310 If no legal arrowhead position, return FALSE. Assume reference list
1311 is for one end only, since it potentially defines the ordering for
1312 any one of the 3 positions. So we don't check the reference list for
1313 arrowhead position.
1314 """
1315 if not referenceList:
1316 return False
1317
1318 targetName = arrow.GetName()
1319
1320 # First check whether we need to insert in front of list,
1321 # because this arrowhead is the first in the reference
1322 # list and should therefore be first in the current list.
1323 refArrow = referenceList[0]
1324 if refArrow.GetName() == targetName:
1325 self._arcArrows.insert(0, arrow)
1326 return True
1327
1328 i1 = i2 = 0
1329 while i1<len(referenceList) and i2<len(self._arcArrows):
1330 refArrow = referenceList[i1]
1331 currArrow = self._arcArrows[i2]
1332
1333 # Matching: advance current arrow pointer
1334 if currArrow.GetArrowEnd() == end and currArrow.GetName() == refArrow.GetName():
1335 i2 += 1
1336
1337 # Check if we're at the correct position in the
1338 # reference list
1339 if targetName == refArrow.GetName():
1340 if i2<len(self._arcArrows):
1341 self._arcArrows.insert(i2, arrow)
1342 else:
1343 self._arcArrows.append(arrow)
1344 return True
1345 i1 += 1
1346
1347 self._arcArrows.append(arrow)
1348 return True
1349
1350 def ClearArrowsAtPosition(self, end):
1351 """Delete the arrows at the specified position, or at any position
1352 if position is -1.
1353 """
1354 if end==-1:
1355 self._arcArrows = []
1356 return
1357
1358 for arrow in self._arcArrows:
1359 if arrow.GetArrowEnd() == end:
1360 self._arcArrows.remove(arrow)
1361
1362 def ClearArrow(self, name):
1363 """Delete the arrow with the given name."""
1364 for arrow in self._arcArrows:
1365 if arrow.GetName() == name:
1366 self._arcArrows.remove(arrow)
1367 return True
1368 return False
1369
1370 def FindArrowHead(self, position, name):
1371 """Find arrowhead by position and name.
1372
1373 if position is -1, matches any position.
1374 """
1375 for arrow in self._arcArrows:
1376 if (position==-1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
1377 return arow
1378
1379 return None
1380
1381 def FindArrowHeadId(self, arrowId):
1382 """Find arrowhead by id."""
1383 for arrow in self._arcArrows:
1384 if arrowId == arrow.GetId():
1385 return arrow
1386
1387 return None
1388
1389 def DeleteArrowHead(self, position, name):
1390 """Delete arrowhead by position and name.
1391
1392 if position is -1, matches any position.
1393 """
1394 for arrow in self._arcArrows:
1395 if (position==-1 or position == arrow.GetArrowEnd()) and arrow.GetName() == name:
1396 self._arcArrows.remove(arrow)
1397 return True
1398 return False
1399
1400 def DeleteArrowHeadId(self, id):
1401 """Delete arrowhead by id."""
1402 for arrow in self._arcArrows:
1403 if arrowId == arrow.GetId():
1404 self._arcArrows.remove(arrow)
1405 return True
1406 return False
1407
1408 # Calculate the minimum width a line
1409 # occupies, for the purposes of drawing lines in tools.
1410 def FindMinimumWidth(self):
1411 """Find the horizontal width for drawing a line with arrows in
1412 minimum space. Assume arrows at end only.
1413 """
1414 minWidth = 0.0
1415 for arrowHead in self._arcArrows:
1416 minWidth += arrowHead.GetSize()
1417 if arrowHead != self._arcArrows[-1]:
1418 minWidth += arrowHead + GetSpacing
1419
1420 # We have ABSOLUTE minimum now. So
1421 # scale it to give it reasonable aesthetics
1422 # when drawing with line.
1423 if minWidth>0:
1424 minWidth = minWidth * 1.4
1425 else:
1426 minWidth = 20.0
1427
1428 self.SetEnds(0.0, 0.0, minWidth, 0.0)
1429 self.Initialise()
1430
1431 return minWidth
1432
1433 def FindLinePosition(self, x, y):
1434 """Find which position we're talking about at this x, y.
1435
1436 Returns ARROW_POSITION_START, ARROW_POSITION_MIDDLE, ARROW_POSITION_END.
1437 """
1438 startX, startY, endX, endY = self.GetEnds()
1439
1440 # Find distances from centre, start and end. The smallest wins
1441 centreDistance = sqrt((x-self._xpos) * (x-self._xpos) + (y-self._ypos) * (y-self._ypos))
1442 startDistance = sqrt((x-startX) * (x-startX) + (y-startY) * (y-startY))
1443 endDistance = sqrt((x-endX) * (x-endX) + (y-endY) * (y-endY))
1444
1445 if centreDistance<startDistance and centreDistance<endDistance:
1446 return ARROW_POSITION_MIDDLE
1447 elif startDistance<endDistance:
1448 return ARROW_POSITION_START
1449 else:
1450 return ARROW_POSITION_END
1451
1452 def SetAlignmentOrientation(self, isEnd, isHoriz):
1453 if isEnd:
1454 if isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
1455 self._alignmentEnd != LINE_ALIGNMENT_HORIZ
1456 elif not isHoriz and self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
1457 self._alignmentEnd -= LINE_ALIGNMENT_HORIZ
1458 else:
1459 if isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ != LINE_ALIGNMENT_HORIZ:
1460 self._alignmentStart != LINE_ALIGNMENT_HORIZ
1461 elif not isHoriz and self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ:
1462 self._alignmentStart -= LINE_ALIGNMENT_HORIZ
1463
1464 def SetAlignmentType(self, isEnd, alignType):
1465 if isEnd:
1466 if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1467 if self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
1468 self._alignmentEnd |= LINE_ALIGNMENT_TO_NEXT_HANDLE
1469 elif self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1470 self._alignmentEnd -= LINE_ALIGNMENT_TO_NEXT_HANDLE
1471 else:
1472 if alignType == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1473 if self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE != LINE_ALIGNMENT_TO_NEXT_HANDLE:
1474 self._alignmentStart |= LINE_ALIGNMENT_TO_NEXT_HANDLE
1475 elif self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1476 self._alignmentStart -= LINE_ALIGNMENT_TO_NEXT_HANDLE
1477
1478 def GetAlignmentOrientation(self, isEnd):
1479 if isEnd:
1480 return self._alignmentEnd & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
1481 else:
1482 return self._alignmentStart & LINE_ALIGNMENT_HORIZ == LINE_ALIGNMENT_HORIZ
1483
1484 def GetAlignmentType(self, isEnd):
1485 if isEnd:
1486 return self._alignmentEnd & LINE_ALIGNMENT_TO_NEXT_HANDLE
1487 else:
1488 return self._alignmentStart & LINE_ALIGNMENT_TO_NEXT_HANDLE
1489
1490 def GetNextControlPoint(self, shape):
1491 """Find the next control point in the line after the start / end point,
1492 depending on whether the shape is at the start or end.
1493 """
1494 n = len(self._lineControlPoints)
1495 if self._to == shape:
1496 # Must be END of line, so we want (n - 1)th control point.
1497 # But indexing ends at n-1, so subtract 2.
1498 nn = n-2
1499 else:
1500 nn = 1
1501 if nn<len(self._lineControlPoints):
1502 return self._lineControlPoints[nn]
1503 return None
1504
1505 def OnCreateLabelShape(self, parent, region, w, h):
1506 return LabelShape(parent, region, w, h)
1507
1508
1509 def OnLabelMovePre(self, dc, labelShape, x, y, old_x, old_y, display):
1510 labelShape._shapeRegion.SetSize(labelShape.GetWidth(), labelShape.GetHeight())
1511
1512 # Find position in line's region list
1513 i = 0
1514 for region in self.GetRegions():
1515 if labelShape._shapeRegion == region:
1516 self.GetRegions().remove(region)
1517 else:
1518 i += 1
1519
1520 xx, yy = self.GetLabelPosition(i)
1521 # Set the region's offset, relative to the default position for
1522 # each region.
1523 labelShape._shapeRegion.SetPosition(x-xx, y-yy)
1524 labelShape.SetX(x)
1525 labelShape.SetY(y)
1526
1527 # Need to reformat to fit region
1528 if labelShape._shapeRegion.GetText():
1529 s = labelShape._shapeRegion.GetText()
1530 labelShape.FormatText(dc, s, i)
1531 self.DrawRegion(dc, labelShape._shapeRegion, xx, yy)
1532 return True
1533