]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/ogl/_basic.py
Native control on Mac draws its own arrows, so don't draw them ourselves in that...
[wxWidgets.git] / wxPython / wx / lib / ogl / _basic.py
1 # -*- coding: iso-8859-1 -*-
2 #----------------------------------------------------------------------------
3 # Name: basic.py
4 # Purpose: The basic OGL shapes
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 wx
15 import math
16
17 from _oglmisc import *
18
19 DragOffsetX = 0.0
20 DragOffsetY = 0.0
21
22 def OGLInitialize():
23 global WhiteBackgroundPen, WhiteBackgroundBrush, TransparentPen
24 global BlackForegroundPen, NormalFont
25
26 WhiteBackgroundPen = wx.Pen(wx.WHITE, 1, wx.SOLID)
27 WhiteBackgroundBrush = wx.Brush(wx.WHITE, wx.SOLID)
28
29 TransparentPen = wx.Pen(wx.WHITE, 1, wx.TRANSPARENT)
30 BlackForegroundPen = wx.Pen(wx.BLACK, 1, wx.SOLID)
31
32 NormalFont = wx.Font(10, wx.SWISS, wx.NORMAL, wx.NORMAL)
33
34
35 def OGLCleanUp():
36 pass
37
38
39 class ShapeTextLine(object):
40 def __init__(self, the_x, the_y, the_line):
41 self._x = the_x
42 self._y = the_y
43 self._line = the_line
44
45 def GetX(self):
46 return self._x
47
48 def GetY(self):
49 return self._y
50
51 def SetX(self, x):
52 self._x = x
53
54 def SetY(self, y):
55 self._y = y
56
57 def SetText(self, text):
58 self._line = text
59
60 def GetText(self):
61 return self._line
62
63
64
65 class ShapeEvtHandler(object):
66 def __init__(self, prev = None, shape = None):
67 self._previousHandler = prev
68 self._handlerShape = shape
69
70 def __del__(self):
71 pass
72
73 def SetShape(self, sh):
74 self._handlerShape = sh
75
76 def GetShape(self):
77 return self._handlerShape
78
79 def SetPreviousHandler(self, handler):
80 self._previousHandler = handler
81
82 def GetPreviousHandler(self):
83 return self._previousHandler
84
85 def OnDelete(self):
86 if self!=self.GetShape():
87 del self
88
89 def OnDraw(self, dc):
90 if self._previousHandler:
91 self._previousHandler.OnDraw(dc)
92
93 def OnMoveLinks(self, dc):
94 if self._previousHandler:
95 self._previousHandler.OnMoveLinks(dc)
96
97 def OnMoveLink(self, dc, moveControlPoints = True):
98 if self._previousHandler:
99 self._previousHandler.OnMoveLink(dc, moveControlPoints)
100
101 def OnDrawContents(self, dc):
102 if self._previousHandler:
103 self._previousHandler.OnDrawContents(dc)
104
105 def OnDrawBranches(self, dc, erase = False):
106 if self._previousHandler:
107 self._previousHandler.OnDrawBranches(dc, erase = erase)
108
109 def OnSize(self, x, y):
110 if self._previousHandler:
111 self._previousHandler.OnSize(x, y)
112
113 def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
114 if self._previousHandler:
115 return self._previousHandler.OnMovePre(dc, x, y, old_x, old_y, display)
116 else:
117 return True
118
119 def OnMovePost(self, dc, x, y, old_x, old_y, display = True):
120 if self._previousHandler:
121 return self._previousHandler.OnMovePost(dc, x, y, old_x, old_y, display)
122 else:
123 return True
124
125 def OnErase(self, dc):
126 if self._previousHandler:
127 self._previousHandler.OnErase(dc)
128
129 def OnEraseContents(self, dc):
130 if self._previousHandler:
131 self._previousHandler.OnEraseContents(dc)
132
133 def OnHighlight(self, dc):
134 if self._previousHandler:
135 self._previousHandler.OnHighlight(dc)
136
137 def OnLeftClick(self, x, y, keys, attachment):
138 if self._previousHandler:
139 self._previousHandler.OnLeftClick(x, y, keys, attachment)
140
141 def OnLeftDoubleClick(self, x, y, keys = 0, attachment = 0):
142 if self._previousHandler:
143 self._previousHandler.OnLeftDoubleClick(x, y, keys, attachment)
144
145 def OnRightClick(self, x, y, keys = 0, attachment = 0):
146 if self._previousHandler:
147 self._previousHandler.OnRightClick(x, y, keys, attachment)
148
149 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
150 if self._previousHandler:
151 self._previousHandler.OnDragLeft(draw, x, y, keys, attachment)
152
153 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
154 if self._previousHandler:
155 self._previousHandler.OnBeginDragLeft(x, y, keys, attachment)
156
157 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
158 if self._previousHandler:
159 self._previousHandler.OnEndDragLeft(x, y, keys, attachment)
160
161 def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
162 if self._previousHandler:
163 self._previousHandler.OnDragRight(draw, x, y, keys, attachment)
164
165 def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
166 if self._previousHandler:
167 self._previousHandler.OnBeginDragRight(x, y, keys, attachment)
168
169 def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
170 if self._previousHandler:
171 self._previousHandler.OnEndDragRight(x, y, keys, attachment)
172
173 # Control points ('handles') redirect control to the actual shape,
174 # to make it easier to override sizing behaviour.
175 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
176 if self._previousHandler:
177 self._previousHandler.OnSizingDragLeft(pt, draw, x, y, keys, attachment)
178
179 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
180 if self._previousHandler:
181 self._previousHandler.OnSizingBeginDragLeft(pt, x, y, keys, attachment)
182
183 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
184 if self._previousHandler:
185 self._previousHandler.OnSizingEndDragLeft(pt, x, y, keys, attachment)
186
187 def OnBeginSize(self, w, h):
188 pass
189
190 def OnEndSize(self, w, h):
191 pass
192
193 def OnDrawOutline(self, dc, x, y, w, h):
194 if self._previousHandler:
195 self._previousHandler.OnDrawOutline(dc, x, y, w, h)
196
197 def OnDrawControlPoints(self, dc):
198 if self._previousHandler:
199 self._previousHandler.OnDrawControlPoints(dc)
200
201 def OnEraseControlPoints(self, dc):
202 if self._previousHandler:
203 self._previousHandler.OnEraseControlPoints(dc)
204
205 # Can override this to prevent or intercept line reordering.
206 def OnChangeAttachment(self, attachment, line, ordering):
207 if self._previousHandler:
208 self._previousHandler.OnChangeAttachment(attachment, line, ordering)
209
210
211
212 class Shape(ShapeEvtHandler):
213 """OGL base class
214
215 Shape(canvas = None)
216
217 The wxShape is the top-level, abstract object that all other objects
218 are derived from. All common functionality is represented by wxShape's
219 members, and overriden members that appear in derived classes and have
220 behaviour as documented for wxShape, are not documented separately.
221 """
222
223 GraphicsInSizeToContents = False
224
225 def __init__(self, canvas = None):
226 ShapeEvtHandler.__init__(self)
227
228 self._eventHandler = self
229 self.SetShape(self)
230 self._id = 0
231 self._formatted = False
232 self._canvas = canvas
233 self._xpos = 0.0
234 self._ypos = 0.0
235 self._pen = BlackForegroundPen
236 self._brush = wx.WHITE_BRUSH
237 self._font = NormalFont
238 self._textColour = wx.BLACK
239 self._textColourName = wx.BLACK
240 self._visible = False
241 self._selected = False
242 self._attachmentMode = ATTACHMENT_MODE_NONE
243 self._spaceAttachments = True
244 self._disableLabel = False
245 self._fixedWidth = False
246 self._fixedHeight = False
247 self._drawHandles = True
248 self._sensitivity = OP_ALL
249 self._draggable = True
250 self._parent = None
251 self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
252 self._shadowMode = SHADOW_NONE
253 self._shadowOffsetX = 6
254 self._shadowOffsetY = 6
255 self._shadowBrush = wx.BLACK_BRUSH
256 self._textMarginX = 5
257 self._textMarginY = 5
258 self._regionName = "0"
259 self._centreResize = True
260 self._maintainAspectRatio = False
261 self._highlighted = False
262 self._rotation = 0.0
263 self._branchNeckLength = 10
264 self._branchStemLength = 10
265 self._branchSpacing = 10
266 self._branchStyle = BRANCHING_ATTACHMENT_NORMAL
267
268 self._regions = []
269 self._lines = []
270 self._controlPoints = []
271 self._attachmentPoints = []
272 self._text = []
273 self._children = []
274
275 # Set up a default region. Much of the above will be put into
276 # the region eventually (the duplication is for compatibility)
277 region = ShapeRegion()
278 region.SetName("0")
279 region.SetFont(NormalFont)
280 region.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT)
281 region.SetColour("BLACK")
282 self._regions.append(region)
283
284 def __str__(self):
285 return "<%s.%s>" % (self.__class__.__module__, self.__class__.__name__)
286
287 def GetClassName(self):
288 return str(self.__class__).split(".")[-1][:-2]
289
290 def Delete(self):
291 """
292 Fully disconnect this shape from parents, children, the
293 canvas, etc.
294 """
295 if self._parent:
296 self._parent.GetChildren().remove(self)
297
298 for child in self.GetChildren():
299 child.Delete()
300
301 self.ClearText()
302 self.ClearRegions()
303 self.ClearAttachments()
304
305 self._handlerShape = None
306
307 if self._canvas:
308 self.RemoveFromCanvas(self._canvas)
309
310 if self.GetEventHandler():
311 self.GetEventHandler().OnDelete()
312 self._eventHandler = None
313
314 def __del__(self):
315 ShapeEvtHandler.__del__(self)
316
317 def Draggable(self):
318 """TRUE if the shape may be dragged by the user."""
319 return True
320
321 def SetShape(self, sh):
322 self._handlerShape = sh
323
324 def GetCanvas(self):
325 """Get the internal canvas."""
326 return self._canvas
327
328 def GetBranchStyle(self):
329 return self._branchStyle
330
331 def GetRotation(self):
332 """Return the angle of rotation in radians."""
333 return self._rotation
334
335 def SetRotation(self, rotation):
336 self._rotation = rotation
337
338 def SetHighlight(self, hi, recurse = False):
339 """Set the highlight for a shape. Shape highlighting is unimplemented."""
340 self._highlighted = hi
341 if recurse:
342 for shape in self._children:
343 shape.SetHighlight(hi, recurse)
344
345 def SetSensitivityFilter(self, sens = OP_ALL, recursive = False):
346 """Set the shape to be sensitive or insensitive to specific mouse
347 operations.
348
349 sens is a bitlist of the following:
350
351 * OP_CLICK_LEFT
352 * OP_CLICK_RIGHT
353 * OP_DRAG_LEFT
354 * OP_DRAG_RIGHT
355 * OP_ALL (equivalent to a combination of all the above).
356 """
357 self._draggable = sens & OP_DRAG_LEFT
358
359 self._sensitivity = sens
360 if recursive:
361 for shape in self._children:
362 shape.SetSensitivityFilter(sens, True)
363
364 def SetDraggable(self, drag, recursive = False):
365 """Set the shape to be draggable or not draggable."""
366 self._draggable = drag
367 if drag:
368 self._sensitivity |= OP_DRAG_LEFT
369 elif self._sensitivity & OP_DRAG_LEFT:
370 self._sensitivity -= OP_DRAG_LEFT
371
372 if recursive:
373 for shape in self._children:
374 shape.SetDraggable(drag, True)
375
376 def SetDrawHandles(self, drawH):
377 """Set the drawHandles flag for this shape and all descendants.
378 If drawH is TRUE (the default), any handles (control points) will
379 be drawn. Otherwise, the handles will not be drawn.
380 """
381 self._drawHandles = drawH
382 for shape in self._children:
383 shape.SetDrawHandles(drawH)
384
385 def SetShadowMode(self, mode, redraw = False):
386 """Set the shadow mode (whether a shadow is drawn or not).
387 mode can be one of the following:
388
389 SHADOW_NONE
390 No shadow (the default).
391 SHADOW_LEFT
392 Shadow on the left side.
393 SHADOW_RIGHT
394 Shadow on the right side.
395 """
396 if redraw and self.GetCanvas():
397 dc = wx.ClientDC(self.GetCanvas())
398 self.GetCanvas().PrepareDC(dc)
399 self.Erase(dc)
400 self._shadowMode = mode
401 self.Draw(dc)
402 else:
403 self._shadowMode = mode
404
405 def GetShadowMode(self):
406 """Return the current shadow mode setting"""
407 return self._shadowMode
408
409 def SetCanvas(self, theCanvas):
410 """Identical to Shape.Attach."""
411 self._canvas = theCanvas
412 for shape in self._children:
413 shape.SetCanvas(theCanvas)
414
415 def AddToCanvas(self, theCanvas, addAfter = None):
416 """Add the shape to the canvas's shape list.
417 If addAfter is non-NULL, will add the shape after this one.
418 """
419 theCanvas.AddShape(self, addAfter)
420
421 lastImage = self
422 for object in self._children:
423 object.AddToCanvas(theCanvas, lastImage)
424 lastImage = object
425
426 def InsertInCanvas(self, theCanvas):
427 """Insert the shape at the front of the shape list of canvas."""
428 theCanvas.InsertShape(self)
429
430 lastImage = self
431 for object in self._children:
432 object.AddToCanvas(theCanvas, lastImage)
433 lastImage = object
434
435 def RemoveFromCanvas(self, theCanvas):
436 """Remove the shape from the canvas."""
437 if self.Selected():
438 self.Select(False)
439
440 self._canvas = None
441 theCanvas.RemoveShape(self)
442 for object in self._children:
443 object.RemoveFromCanvas(theCanvas)
444
445 def ClearAttachments(self):
446 """Clear internal custom attachment point shapes (of class
447 wxAttachmentPoint).
448 """
449 self._attachmentPoints = []
450
451 def ClearText(self, regionId = 0):
452 """Clear the text from the specified text region."""
453 if regionId == 0:
454 self._text = ""
455 if regionId < len(self._regions):
456 self._regions[regionId].ClearText()
457
458 def ClearRegions(self):
459 """Clear the ShapeRegions from the shape."""
460 self._regions = []
461
462 def AddRegion(self, region):
463 """Add a region to the shape."""
464 self._regions.append(region)
465
466 def SetDefaultRegionSize(self):
467 """Set the default region to be consistent with the shape size."""
468 if not self._regions:
469 return
470 w, h = self.GetBoundingBoxMax()
471 self._regions[0].SetSize(w, h)
472
473 def HitTest(self, x, y):
474 """Given a point on a canvas, returns TRUE if the point was on the
475 shape, and returns the nearest attachment point and distance from
476 the given point and target.
477 """
478 width, height = self.GetBoundingBoxMax()
479 if abs(width) < 4:
480 width = 4.0
481 if abs(height) < 4:
482 height = 4.0
483
484 width += 4 # Allowance for inaccurate mousing
485 height += 4
486
487 left = self._xpos - width / 2.0
488 top = self._ypos - height / 2.0
489 right = self._xpos + width / 2.0
490 bottom = self._ypos + height / 2.0
491
492 nearest_attachment = 0
493
494 # If within the bounding box, check the attachment points
495 # within the object.
496 if x >= left and x <= right and y >= top and y <= bottom:
497 n = self.GetNumberOfAttachments()
498 nearest = 999999
499
500 # GetAttachmentPosition[Edge] takes a logical attachment position,
501 # i.e. if it's rotated through 90%, position 0 is East-facing.
502
503 for i in range(n):
504 e = self.GetAttachmentPositionEdge(i)
505 if e:
506 xp, yp = e
507 l = math.sqrt(((xp - x) * (xp - x)) + (yp - y) * (yp - y))
508 if l < nearest:
509 nearest = l
510 nearest_attachment = i
511
512 return nearest_attachment, nearest
513 return False
514
515 # Format a text string according to the region size, adding
516 # strings with positions to region text list
517
518 def FormatText(self, dc, s, i = 0):
519 """Reformat the given text region; defaults to formatting the
520 default region.
521 """
522 self.ClearText(i)
523
524 if not self._regions:
525 return
526
527 if i > len(self._regions):
528 return
529
530 region = self._regions[i]
531 region._regionText = s
532 dc.SetFont(region.GetFont())
533
534 w, h = region.GetSize()
535
536 stringList = FormatText(dc, s, (w - 2 * self._textMarginX), (h - 2 * self._textMarginY), region.GetFormatMode())
537 for s in stringList:
538 line = ShapeTextLine(0.0, 0.0, s)
539 region.GetFormattedText().append(line)
540
541 actualW = w
542 actualH = h
543 # Don't try to resize an object with more than one image (this
544 # case should be dealt with by overriden handlers)
545 if (region.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS) and \
546 len(region.GetFormattedText()) and \
547 len(self._regions) == 1 and \
548 not Shape.GraphicsInSizeToContents:
549
550 actualW, actualH = GetCentredTextExtent(dc, region.GetFormattedText())
551 if actualW + 2 * self._textMarginX != w or actualH + 2 * self._textMarginY != h:
552 # If we are a descendant of a composite, must make sure
553 # the composite gets resized properly
554
555 topAncestor = self.GetTopAncestor()
556 if topAncestor != self:
557 Shape.GraphicsInSizeToContents = True
558
559 composite = topAncestor
560 composite.Erase(dc)
561 self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY)
562 self.Move(dc, self._xpos, self._ypos)
563 composite.CalculateSize()
564 if composite.Selected():
565 composite.DeleteControlPoints(dc)
566 composite.MakeControlPoints()
567 composite.MakeMandatoryControlPoints()
568 # Where infinite recursion might happen if we didn't stop it
569 composite.Draw(dc)
570 Shape.GraphicsInSizeToContents = False
571 else:
572 self.Erase(dc)
573
574 self.SetSize(actualW + 2 * self._textMarginX, actualH + 2 * self._textMarginY)
575 self.Move(dc, self._xpos, self._ypos)
576 self.EraseContents(dc)
577 CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, actualW - 2 * self._textMarginX, actualH - 2 * self._textMarginY, region.GetFormatMode())
578 self._formatted = True
579
580 def Recentre(self, dc):
581 """Do recentring (or other formatting) for all the text regions
582 for this shape.
583 """
584 w, h = self.GetBoundingBoxMin()
585 for region in self._regions:
586 CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, w - 2 * self._textMarginX, h - 2 * self._textMarginY, region.GetFormatMode())
587
588 def GetPerimeterPoint(self, x1, y1, x2, y2):
589 """Get the point at which the line from (x1, y1) to (x2, y2) hits
590 the shape. Returns False if the line doesn't hit the perimeter.
591 """
592 return False
593
594 def SetPen(self, the_pen):
595 """Set the pen for drawing the shape's outline."""
596 self._pen = the_pen
597
598 def SetBrush(self, the_brush):
599 """Set the brush for filling the shape's shape."""
600 self._brush = the_brush
601
602 # Get the top - most (non-division) ancestor, or self
603 def GetTopAncestor(self):
604 """Return the top-most ancestor of this shape (the root of
605 the composite).
606 """
607 if not self.GetParent():
608 return self
609
610 if isinstance(self.GetParent(), DivisionShape):
611 return self
612 return self.GetParent().GetTopAncestor()
613
614 # Region functions
615 def SetFont(self, the_font, regionId = 0):
616 """Set the font for the specified text region."""
617 self._font = the_font
618 if regionId < len(self._regions):
619 self._regions[regionId].SetFont(the_font)
620
621 def GetFont(self, regionId = 0):
622 """Get the font for the specified text region."""
623 if regionId >= len(self._regions):
624 return None
625 return self._regions[regionId].GetFont()
626
627 def SetFormatMode(self, mode, regionId = 0):
628 """Set the format mode of the default text region. The argument
629 can be a bit list of the following:
630
631 FORMAT_NONE
632 No formatting.
633 FORMAT_CENTRE_HORIZ
634 Horizontal centring.
635 FORMAT_CENTRE_VERT
636 Vertical centring.
637 """
638 if regionId < len(self._regions):
639 self._regions[regionId].SetFormatMode(mode)
640
641 def GetFormatMode(self, regionId = 0):
642 if regionId >= len(self._regions):
643 return 0
644 return self._regions[regionId].GetFormatMode()
645
646 def SetTextColour(self, the_colour, regionId = 0):
647 """Set the colour for the specified text region."""
648 self._textColour = wx.TheColourDatabase.Find(the_colour)
649 self._textColourName = the_colour
650
651 if regionId < len(self._regions):
652 self._regions[regionId].SetColour(the_colour)
653
654 def GetTextColour(self, regionId = 0):
655 """Get the colour for the specified text region."""
656 if regionId >= len(self._regions):
657 return ""
658 return self._regions[regionId].GetColour()
659
660 def SetRegionName(self, name, regionId = 0):
661 """Set the name for this region.
662 The name for a region is unique within the scope of the whole
663 composite, whereas a region id is unique only for a single image.
664 """
665 if regionId < len(self._regions):
666 self._regions[regionId].SetName(name)
667
668 def GetRegionName(self, regionId = 0):
669 """Get the region's name.
670 A region's name can be used to uniquely determine a region within
671 an entire composite image hierarchy. See also Shape.SetRegionName.
672 """
673 if regionId >= len(self._regions):
674 return ""
675 return self._regions[regionId].GetName()
676
677 def GetRegionId(self, name):
678 """Get the region's identifier by name.
679 This is not unique for within an entire composite, but is unique
680 for the image.
681 """
682 for i, r in enumerate(self._regions):
683 if r.GetName() == name:
684 return i
685 return -1
686
687 # Name all _regions in all subimages recursively
688 def NameRegions(self, parentName=""):
689 """Make unique names for all the regions in a shape or composite shape."""
690 n = self.GetNumberOfTextRegions()
691 for i in range(n):
692 if parentName:
693 buff = parentName+"."+str(i)
694 else:
695 buff = str(i)
696 self.SetRegionName(buff, i)
697
698 for j, child in enumerate(self._children):
699 if parentName:
700 buff = parentName+"."+str(j)
701 else:
702 buff = str(j)
703 child.NameRegions(buff)
704
705 # Get a region by name, possibly looking recursively into composites
706 def FindRegion(self, name):
707 """Find the actual image ('this' if non-composite) and region id
708 for the given region name.
709 """
710 id = self.GetRegionId(name)
711 if id > -1:
712 return self, id
713
714 for child in self._children:
715 actualImage, regionId = child.FindRegion(name)
716 if actualImage:
717 return actualImage, regionId
718
719 return None, -1
720
721 # Finds all region names for this image (composite or simple).
722 def FindRegionNames(self):
723 """Get a list of all region names for this image (composite or simple)."""
724 list = []
725 n = self.GetNumberOfTextRegions()
726 for i in range(n):
727 list.append(self.GetRegionName(i))
728
729 for child in self._children:
730 list += child.FindRegionNames()
731
732 return list
733
734 def AssignNewIds(self):
735 """Assign new ids to this image and its children."""
736 self._id = wx.NewId()
737 for child in self._children:
738 child.AssignNewIds()
739
740 def OnDraw(self, dc):
741 pass
742
743 def OnMoveLinks(self, dc):
744 # Want to set the ends of all attached links
745 # to point to / from this object
746
747 for line in self._lines:
748 line.GetEventHandler().OnMoveLink(dc)
749
750 def OnDrawContents(self, dc):
751 if not self._regions:
752 return
753
754 bound_x, bound_y = self.GetBoundingBoxMin()
755
756 if self._pen:
757 dc.SetPen(self._pen)
758
759 for region in self._regions:
760 if region.GetFont():
761 dc.SetFont(region.GetFont())
762
763 dc.SetTextForeground(region.GetActualColourObject())
764 dc.SetBackgroundMode(wx.TRANSPARENT)
765 if not self._formatted:
766 CentreText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode())
767 self._formatted = True
768
769 if not self.GetDisableLabel():
770 DrawFormattedText(dc, region.GetFormattedText(), self._xpos, self._ypos, bound_x - 2 * self._textMarginX, bound_y - 2 * self._textMarginY, region.GetFormatMode())
771
772
773 def DrawContents(self, dc):
774 """Draw the internal graphic of the shape (such as text).
775
776 Do not override this function: override OnDrawContents, which
777 is called by this function.
778 """
779 self.GetEventHandler().OnDrawContents(dc)
780
781 def OnSize(self, x, y):
782 pass
783
784 def OnMovePre(self, dc, x, y, old_x, old_y, display = True):
785 return True
786
787 def OnErase(self, dc):
788 if not self._visible:
789 return
790
791 # Erase links
792 for line in self._lines:
793 line.GetEventHandler().OnErase(dc)
794
795 self.GetEventHandler().OnEraseContents(dc)
796
797 def OnEraseContents(self, dc):
798 if not self._visible:
799 return
800
801 xp, yp = self.GetX(), self.GetY()
802 minX, minY = self.GetBoundingBoxMin()
803 maxX, maxY = self.GetBoundingBoxMax()
804
805 topLeftX = xp - maxX / 2.0 - 2
806 topLeftY = yp - maxY / 2.0 - 2
807
808 penWidth = 0
809 if self._pen:
810 penWidth = self._pen.GetWidth()
811
812 dc.SetPen(self.GetBackgroundPen())
813 dc.SetBrush(self.GetBackgroundBrush())
814
815 dc.DrawRectangle(topLeftX - penWidth, topLeftY - penWidth, maxX + penWidth * 2 + 4, maxY + penWidth * 2 + 4)
816
817 def EraseLinks(self, dc, attachment = -1, recurse = False):
818 """Erase links attached to this shape, but do not repair damage
819 caused to other shapes.
820 """
821 if not self._visible:
822 return
823
824 for line in self._lines:
825 if attachment == -1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment):
826 line.GetEventHandler().OnErase(dc)
827
828 if recurse:
829 for child in self._children:
830 child.EraseLinks(dc, attachment, recurse)
831
832 def DrawLinks(self, dc, attachment = -1, recurse = False):
833 """Draws any lines linked to this shape."""
834 if not self._visible:
835 return
836
837 for line in self._lines:
838 if attachment == -1 or (line.GetTo() == self and line.GetAttachmentTo() == attachment or line.GetFrom() == self and line.GetAttachmentFrom() == attachment):
839 line.Draw(dc)
840
841 if recurse:
842 for child in self._children:
843 child.DrawLinks(dc, attachment, recurse)
844
845 # Returns TRUE if pt1 <= pt2 in the sense that one point comes before
846 # another on an edge of the shape.
847 # attachmentPoint is the attachment point (= side) in question.
848
849 # This is the default, rectangular implementation.
850 def AttachmentSortTest(self, attachmentPoint, pt1, pt2):
851 """Return TRUE if pt1 is less than or equal to pt2, in the sense
852 that one point comes before another on an edge of the shape.
853
854 attachment is the attachment point (side) in question.
855
856 This function is used in Shape.MoveLineToNewAttachment to determine
857 the new line ordering.
858 """
859 physicalAttachment = self.LogicalToPhysicalAttachment(attachmentPoint)
860 if physicalAttachment in [0, 2]:
861 return pt1[0] <= pt2[0]
862 elif physicalAttachment in [1, 3]:
863 return pt1[1] <= pt2[1]
864
865 return False
866
867 def MoveLineToNewAttachment(self, dc, to_move, x, y):
868 """Move the given line (which must already be attached to the shape)
869 to a different attachment point on the shape, or a different order
870 on the same attachment.
871
872 Calls Shape.AttachmentSortTest and then
873 ShapeEvtHandler.OnChangeAttachment.
874 """
875 if self.GetAttachmentMode() == ATTACHMENT_MODE_NONE:
876 return False
877
878 # Is (x, y) on this object? If so, find the new attachment point
879 # the user has moved the point to
880 hit = self.HitTest(x, y)
881 if not hit:
882 return False
883
884 newAttachment, distance = hit
885
886 self.EraseLinks(dc)
887
888 if to_move.GetTo() == self:
889 oldAttachment = to_move.GetAttachmentTo()
890 else:
891 oldAttachment = to_move.GetAttachmentFrom()
892
893 # The links in a new ordering
894 # First, add all links to the new list
895 newOrdering = self._lines[:]
896
897 # Delete the line object from the list of links; we're going to move
898 # it to another position in the list
899 del newOrdering[newOrdering.index(to_move)]
900
901 old_x = -99999.9
902 old_y = -99999.9
903
904 found = False
905
906 for line in newOrdering:
907 if line.GetTo() == self and oldAttachment == line.GetAttachmentTo() or \
908 line.GetFrom() == self and oldAttachment == line.GetAttachmentFrom():
909 startX, startY, endX, endY = line.GetEnds()
910 if line.GetTo() == self:
911 xp = endX
912 yp = endY
913 else:
914 xp = startX
915 yp = startY
916
917 thisPoint = wx.RealPoint(xp, yp)
918 lastPoint = wx.RealPoint(old_x, old_y)
919 newPoint = wx.RealPoint(x, y)
920
921 if self.AttachmentSortTest(newAttachment, newPoint, thisPoint) and self.AttachmentSortTest(newAttachment, lastPoint, newPoint):
922 found = True
923 newOrdering.insert(newOrdering.index(line), to_move)
924
925 old_x = xp
926 old_y = yp
927 if found:
928 break
929
930 if not found:
931 newOrdering.append(to_move)
932
933 self.GetEventHandler().OnChangeAttachment(newAttachment, to_move, newOrdering)
934 return True
935
936 def OnChangeAttachment(self, attachment, line, ordering):
937 if line.GetTo() == self:
938 line.SetAttachmentTo(attachment)
939 else:
940 line.SetAttachmentFrom(attachment)
941
942 self.ApplyAttachmentOrdering(ordering)
943
944 dc = wx.ClientDC(self.GetCanvas())
945 self.GetCanvas().PrepareDC(dc)
946 self.MoveLinks(dc)
947
948 if not self.GetCanvas().GetQuickEditMode():
949 self.GetCanvas().Redraw(dc)
950
951 # Reorders the lines according to the given list
952 def ApplyAttachmentOrdering(self, linesToSort):
953 """Apply the line ordering in linesToSort to the shape, to reorder
954 the way lines are attached.
955 """
956 linesStore = self._lines[:]
957
958 self._lines = []
959
960 for line in linesToSort:
961 if line in linesStore:
962 del linesStore[linesStore.index(line)]
963 self._lines.append(line)
964
965 # Now add any lines that haven't been listed in linesToSort
966 self._lines += linesStore
967
968 def SortLines(self, attachment, linesToSort):
969 """ Reorder the lines coming into the node image at this attachment
970 position, in the order in which they appear in linesToSort.
971
972 Any remaining lines not in the list will be added to the end.
973 """
974 # This is a temporary store of all the lines at this attachment
975 # point. We'll tick them off as we've processed them.
976 linesAtThisAttachment = []
977
978 for line in self._lines[:]:
979 if line.GetTo() == self and line.GetAttachmentTo() == attachment or \
980 line.GetFrom() == self and line.GetAttachmentFrom() == attachment:
981 linesAtThisAttachment.append(line)
982 del self._lines[self._lines.index(line)]
983
984 for line in linesToSort:
985 if line in linesAtThisAttachment:
986 # Done this one
987 del linesAtThisAttachment[linesAtThisAttachment.index(line)]
988 self._lines.append(line)
989
990 # Now add any lines that haven't been listed in linesToSort
991 self._lines += linesAtThisAttachment
992
993 def OnHighlight(self, dc):
994 pass
995
996 def OnLeftClick(self, x, y, keys = 0, attachment = 0):
997 if self._sensitivity & OP_CLICK_LEFT != OP_CLICK_LEFT:
998 if self._parent:
999 attachment, dist = self._parent.HitTest(x, y)
1000 self._parent.GetEventHandler().OnLeftClick(x, y, keys, attachment)
1001
1002 def OnRightClick(self, x, y, keys = 0, attachment = 0):
1003 if self._sensitivity & OP_CLICK_RIGHT != OP_CLICK_RIGHT:
1004 attachment, dist = self._parent.HitTest(x, y)
1005 self._parent.GetEventHandler().OnRightClick(x, y, keys, attachment)
1006
1007 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
1008 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1009 if self._parent:
1010 hit = self._parent.HitTest(x, y)
1011 if hit:
1012 attachment, dist = hit
1013 self._parent.GetEventHandler().OnDragLeft(draw, x, y, keys, attachment)
1014 return
1015
1016 dc = wx.ClientDC(self.GetCanvas())
1017 self.GetCanvas().PrepareDC(dc)
1018 dc.SetLogicalFunction(OGLRBLF)
1019
1020 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1021 dc.SetPen(dottedPen)
1022 dc.SetBrush(wx.TRANSPARENT_BRUSH)
1023
1024 xx = x + DragOffsetX
1025 yy = y + DragOffsetY
1026
1027 xx, yy = self._canvas.Snap(xx, yy)
1028 w, h = self.GetBoundingBoxMax()
1029 self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
1030
1031 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
1032 global DragOffsetX, DragOffsetY
1033
1034 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1035 if self._parent:
1036 hit = self._parent.HitTest(x, y)
1037 if hit:
1038 attachment, dist = hit
1039 self._parent.GetEventHandler().OnBeginDragLeft(x, y, keys, attachment)
1040 return
1041
1042 DragOffsetX = self._xpos - x
1043 DragOffsetY = self._ypos - y
1044
1045 dc = wx.ClientDC(self.GetCanvas())
1046 self.GetCanvas().PrepareDC(dc)
1047
1048 # New policy: don't erase shape until end of drag.
1049 # self.Erase(dc)
1050 xx = x + DragOffsetX
1051 yy = y + DragOffsetY
1052 xx, yy = self._canvas.Snap(xx, yy)
1053 dc.SetLogicalFunction(OGLRBLF)
1054
1055 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
1056 dc.SetPen(dottedPen)
1057 dc.SetBrush(wx.TRANSPARENT_BRUSH)
1058
1059 w, h = self.GetBoundingBoxMax()
1060 self.GetEventHandler().OnDrawOutline(dc, xx, yy, w, h)
1061 self._canvas.CaptureMouse()
1062
1063 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
1064 if self._canvas.HasCapture():
1065 self._canvas.ReleaseMouse()
1066 if self._sensitivity & OP_DRAG_LEFT != OP_DRAG_LEFT:
1067 if self._parent:
1068 hit = self._parent.HitTest(x, y)
1069 if hit:
1070 attachment, dist = hit
1071 self._parent.GetEventHandler().OnEndDragLeft(x, y, keys, attachment)
1072 return
1073
1074 dc = wx.ClientDC(self.GetCanvas())
1075 self.GetCanvas().PrepareDC(dc)
1076
1077 dc.SetLogicalFunction(wx.COPY)
1078 xx = x + DragOffsetX
1079 yy = y + DragOffsetY
1080 xx, yy = self._canvas.Snap(xx, yy)
1081
1082 # New policy: erase shape at end of drag.
1083 self.Erase(dc)
1084
1085 self.Move(dc, xx, yy)
1086 if self._canvas and not self._canvas.GetQuickEditMode():
1087 self._canvas.Redraw(dc)
1088
1089 def OnDragRight(self, draw, x, y, keys = 0, attachment = 0):
1090 if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
1091 if self._parent:
1092 attachment, dist = self._parent.HitTest(x, y)
1093 self._parent.GetEventHandler().OnDragRight(draw, x, y, keys, attachment)
1094 return
1095
1096 def OnBeginDragRight(self, x, y, keys = 0, attachment = 0):
1097 if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
1098 if self._parent:
1099 attachment, dist = self._parent.HitTest(x, y)
1100 self._parent.GetEventHandler().OnBeginDragRight(x, y, keys, attachment)
1101 return
1102
1103 def OnEndDragRight(self, x, y, keys = 0, attachment = 0):
1104 if self._sensitivity & OP_DRAG_RIGHT != OP_DRAG_RIGHT:
1105 if self._parent:
1106 attachment, dist = self._parent.HitTest(x, y)
1107 self._parent.GetEventHandler().OnEndDragRight(x, y, keys, attachment)
1108 return
1109
1110 def OnDrawOutline(self, dc, x, y, w, h):
1111 points = [[x - w / 2.0, y - h / 2.0],
1112 [x + w / 2.0, y - h / 2.0],
1113 [x + w / 2.0, y + h / 2.0],
1114 [x - w / 2.0, y + h / 2.0],
1115 [x - w / 2.0, y - h / 2.0],
1116 ]
1117
1118 dc.DrawLines(points)
1119
1120 def Attach(self, can):
1121 """Set the shape's internal canvas pointer to point to the given canvas."""
1122 self._canvas = can
1123
1124 def Detach(self):
1125 """Disassociates the shape from its canvas."""
1126 self._canvas = None
1127
1128 def Move(self, dc, x, y, display = True):
1129 """Move the shape to the given position.
1130 Redraw if display is TRUE.
1131 """
1132 old_x = self._xpos
1133 old_y = self._ypos
1134
1135 if not self.GetEventHandler().OnMovePre(dc, x, y, old_x, old_y, display):
1136 return
1137
1138 self._xpos, self._ypos = x, y
1139
1140 self.ResetControlPoints()
1141
1142 if display:
1143 self.Draw(dc)
1144
1145 self.MoveLinks(dc)
1146
1147 self.GetEventHandler().OnMovePost(dc, x, y, old_x, old_y, display)
1148
1149 def MoveLinks(self, dc):
1150 """Redraw all the lines attached to the shape."""
1151 self.GetEventHandler().OnMoveLinks(dc)
1152
1153 def Draw(self, dc):
1154 """Draw the whole shape and any lines attached to it.
1155
1156 Do not override this function: override OnDraw, which is called
1157 by this function.
1158 """
1159 if self._visible:
1160 self.GetEventHandler().OnDraw(dc)
1161 self.GetEventHandler().OnDrawContents(dc)
1162 self.GetEventHandler().OnDrawControlPoints(dc)
1163 self.GetEventHandler().OnDrawBranches(dc)
1164
1165 def Flash(self):
1166 """Flash the shape."""
1167 if self.GetCanvas():
1168 dc = wx.ClientDC(self.GetCanvas())
1169 self.GetCanvas().PrepareDC(dc)
1170
1171 dc.SetLogicalFunction(OGLRBLF)
1172 self.Draw(dc)
1173 dc.SetLogicalFunction(wx.COPY)
1174 self.Draw(dc)
1175
1176 def Show(self, show):
1177 """Set a flag indicating whether the shape should be drawn."""
1178 self._visible = show
1179 for child in self._children:
1180 child.Show(show)
1181
1182 def Erase(self, dc):
1183 """Erase the shape.
1184 Does not repair damage caused to other shapes.
1185 """
1186 self.GetEventHandler().OnErase(dc)
1187 self.GetEventHandler().OnEraseControlPoints(dc)
1188 self.GetEventHandler().OnDrawBranches(dc, erase = True)
1189
1190 def EraseContents(self, dc):
1191 """Erase the shape contents, that is, the area within the shape's
1192 minimum bounding box.
1193 """
1194 self.GetEventHandler().OnEraseContents(dc)
1195
1196 def AddText(self, string):
1197 """Add a line of text to the shape's default text region."""
1198 if not self._regions:
1199 return
1200
1201 region = self._regions[0]
1202 #region.ClearText()
1203 new_line = ShapeTextLine(0, 0, string)
1204 text = region.GetFormattedText()
1205 text.append(new_line)
1206
1207 self._formatted = False
1208
1209 def SetSize(self, x, y, recursive = True):
1210 """Set the shape's size."""
1211 self.SetAttachmentSize(x, y)
1212 self.SetDefaultRegionSize()
1213
1214 def SetAttachmentSize(self, w, h):
1215 width, height = self.GetBoundingBoxMin()
1216 if width == 0:
1217 scaleX = 1.0
1218 else:
1219 scaleX = float(w) / width
1220 if height == 0:
1221 scaleY = 1.0
1222 else:
1223 scaleY = float(h) / height
1224
1225 for point in self._attachmentPoints:
1226 point._x = point._x * scaleX
1227 point._y = point._y * scaleY
1228
1229 # Add line FROM this object
1230 def AddLine(self, line, other, attachFrom = 0, attachTo = 0, positionFrom = -1, positionTo = -1):
1231 """Add a line between this shape and the given other shape, at the
1232 specified attachment points.
1233
1234 The position in the list of lines at each end can also be specified,
1235 so that the line will be drawn at a particular point on its attachment
1236 point.
1237 """
1238 if positionFrom == -1:
1239 if not line in self._lines:
1240 self._lines.append(line)
1241 else:
1242 # Don't preserve old ordering if we have new ordering instructions
1243 try:
1244 self._lines.remove(line)
1245 except ValueError:
1246 pass
1247 if positionFrom < len(self._lines):
1248 self._lines.insert(positionFrom, line)
1249 else:
1250 self._lines.append(line)
1251
1252 if positionTo == -1:
1253 if not other in other._lines:
1254 other._lines.append(line)
1255 else:
1256 # Don't preserve old ordering if we have new ordering instructions
1257 try:
1258 other._lines.remove(line)
1259 except ValueError:
1260 pass
1261 if positionTo < len(other._lines):
1262 other._lines.insert(positionTo, line)
1263 else:
1264 other._lines.append(line)
1265
1266 line.SetFrom(self)
1267 line.SetTo(other)
1268 line.SetAttachments(attachFrom, attachTo)
1269
1270 dc = wx.ClientDC(self._canvas)
1271 self._canvas.PrepareDC(dc)
1272 self.MoveLinks(dc)
1273
1274 def RemoveLine(self, line):
1275 """Remove the given line from the shape's list of attached lines."""
1276 if line.GetFrom() == self:
1277 line.GetTo()._lines.remove(line)
1278 else:
1279 line.GetFrom()._lines.remove(line)
1280
1281 self._lines.remove(line)
1282
1283 # Default - make 6 control points
1284 def MakeControlPoints(self):
1285 """Make a list of control points (draggable handles) appropriate to
1286 the shape.
1287 """
1288 maxX, maxY = self.GetBoundingBoxMax()
1289 minX, minY = self.GetBoundingBoxMin()
1290
1291 widthMin = minX + CONTROL_POINT_SIZE + 2
1292 heightMin = minY + CONTROL_POINT_SIZE + 2
1293
1294 # Offsets from main object
1295 top = -heightMin / 2.0
1296 bottom = heightMin / 2.0 + (maxY - minY)
1297 left = -widthMin / 2.0
1298 right = widthMin / 2.0 + (maxX - minX)
1299
1300 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, top, CONTROL_POINT_DIAGONAL)
1301 self._canvas.AddShape(control)
1302 self._controlPoints.append(control)
1303
1304 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, top, CONTROL_POINT_VERTICAL)
1305 self._canvas.AddShape(control)
1306 self._controlPoints.append(control)
1307
1308 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, top, CONTROL_POINT_DIAGONAL)
1309 self._canvas.AddShape(control)
1310 self._controlPoints.append(control)
1311
1312 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, 0, CONTROL_POINT_HORIZONTAL)
1313 self._canvas.AddShape(control)
1314 self._controlPoints.append(control)
1315
1316 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, right, bottom, CONTROL_POINT_DIAGONAL)
1317 self._canvas.AddShape(control)
1318 self._controlPoints.append(control)
1319
1320 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, 0, bottom, CONTROL_POINT_VERTICAL)
1321 self._canvas.AddShape(control)
1322 self._controlPoints.append(control)
1323
1324 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, bottom, CONTROL_POINT_DIAGONAL)
1325 self._canvas.AddShape(control)
1326 self._controlPoints.append(control)
1327
1328 control = ControlPoint(self._canvas, self, CONTROL_POINT_SIZE, left, 0, CONTROL_POINT_HORIZONTAL)
1329 self._canvas.AddShape(control)
1330 self._controlPoints.append(control)
1331
1332 def MakeMandatoryControlPoints(self):
1333 """Make the mandatory control points.
1334
1335 For example, the control point on a dividing line should appear even
1336 if the divided rectangle shape's handles should not appear (because
1337 it is the child of a composite, and children are not resizable).
1338 """
1339 for child in self._children:
1340 child.MakeMandatoryControlPoints()
1341
1342 def ResetMandatoryControlPoints(self):
1343 """Reset the mandatory control points."""
1344 for child in self._children:
1345 child.ResetMandatoryControlPoints()
1346
1347 def ResetControlPoints(self):
1348 """Reset the positions of the control points (for instance when the
1349 shape's shape has changed).
1350 """
1351 self.ResetMandatoryControlPoints()
1352
1353 if len(self._controlPoints) == 0:
1354 return
1355
1356 maxX, maxY = self.GetBoundingBoxMax()
1357 minX, minY = self.GetBoundingBoxMin()
1358
1359 widthMin = minX + CONTROL_POINT_SIZE + 2
1360 heightMin = minY + CONTROL_POINT_SIZE + 2
1361
1362 # Offsets from main object
1363 top = -heightMin / 2.0
1364 bottom = heightMin / 2.0 + (maxY - minY)
1365 left = -widthMin / 2.0
1366 right = widthMin / 2.0 + (maxX - minX)
1367
1368 self._controlPoints[0]._xoffset = left
1369 self._controlPoints[0]._yoffset = top
1370
1371 self._controlPoints[1]._xoffset = 0
1372 self._controlPoints[1]._yoffset = top
1373
1374 self._controlPoints[2]._xoffset = right
1375 self._controlPoints[2]._yoffset = top
1376
1377 self._controlPoints[3]._xoffset = right
1378 self._controlPoints[3]._yoffset = 0
1379
1380 self._controlPoints[4]._xoffset = right
1381 self._controlPoints[4]._yoffset = bottom
1382
1383 self._controlPoints[5]._xoffset = 0
1384 self._controlPoints[5]._yoffset = bottom
1385
1386 self._controlPoints[6]._xoffset = left
1387 self._controlPoints[6]._yoffset = bottom
1388
1389 self._controlPoints[7]._xoffset = left
1390 self._controlPoints[7]._yoffset = 0
1391
1392 def DeleteControlPoints(self, dc = None):
1393 """Delete the control points (or handles) for the shape.
1394
1395 Does not redraw the shape.
1396 """
1397 for control in self._controlPoints[:]:
1398 if dc:
1399 control.GetEventHandler().OnErase(dc)
1400 control.Delete()
1401 self._controlPoints.remove(control)
1402 self._controlPoints = []
1403
1404 # Children of divisions are contained objects,
1405 # so stop here
1406 if not isinstance(self, DivisionShape):
1407 for child in self._children:
1408 child.DeleteControlPoints(dc)
1409
1410 def OnDrawControlPoints(self, dc):
1411 if not self._drawHandles:
1412 return
1413
1414 dc.SetBrush(wx.BLACK_BRUSH)
1415 dc.SetPen(wx.BLACK_PEN)
1416
1417 for control in self._controlPoints:
1418 control.Draw(dc)
1419
1420 # Children of divisions are contained objects,
1421 # so stop here.
1422 # This test bypasses the type facility for speed
1423 # (critical when drawing)
1424
1425 if not isinstance(self, DivisionShape):
1426 for child in self._children:
1427 child.GetEventHandler().OnDrawControlPoints(dc)
1428
1429 def OnEraseControlPoints(self, dc):
1430 for control in self._controlPoints:
1431 control.Erase(dc)
1432
1433 if not isinstance(self, DivisionShape):
1434 for child in self._children:
1435 child.GetEventHandler().OnEraseControlPoints(dc)
1436
1437 def Select(self, select, dc = None):
1438 """Select or deselect the given shape, drawing or erasing control points
1439 (handles) as necessary.
1440 """
1441 self._selected = select
1442 if select:
1443 self.MakeControlPoints()
1444 # Children of divisions are contained objects,
1445 # so stop here
1446 if not isinstance(self, DivisionShape):
1447 for child in self._children:
1448 child.MakeMandatoryControlPoints()
1449 if dc:
1450 self.GetEventHandler().OnDrawControlPoints(dc)
1451 else:
1452 self.DeleteControlPoints(dc)
1453 if not isinstance(self, DivisionShape):
1454 for child in self._children:
1455 child.DeleteControlPoints(dc)
1456
1457 def Selected(self):
1458 """TRUE if the shape is currently selected."""
1459 return self._selected
1460
1461 def AncestorSelected(self):
1462 """TRUE if the shape's ancestor is currently selected."""
1463 if self._selected:
1464 return True
1465 if not self.GetParent():
1466 return False
1467 return self.GetParent().AncestorSelected()
1468
1469 def GetNumberOfAttachments(self):
1470 """Get the number of attachment points for this shape."""
1471 # Should return the MAXIMUM attachment point id here,
1472 # so higher-level functions can iterate through all attachments,
1473 # even if they're not contiguous.
1474
1475 if len(self._attachmentPoints) == 0:
1476 return 4
1477 else:
1478 maxN = 3
1479 for point in self._attachmentPoints:
1480 if point._id > maxN:
1481 maxN = point._id
1482 return maxN + 1
1483
1484 def AttachmentIsValid(self, attachment):
1485 """TRUE if attachment is a valid attachment point."""
1486 if len(self._attachmentPoints) == 0:
1487 return attachment in range(4)
1488
1489 for point in self._attachmentPoints:
1490 if point._id == attachment:
1491 return True
1492 return False
1493
1494 def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
1495 """Get the position at which the given attachment point should be drawn.
1496
1497 If attachment isn't found among the attachment points of the shape,
1498 returns None.
1499 """
1500 if self._attachmentMode == ATTACHMENT_MODE_NONE:
1501 return self._xpos, self._ypos
1502 elif self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
1503 pt, stemPt = self.GetBranchingAttachmentPoint(attachment, nth)
1504 return pt[0], pt[1]
1505 elif self._attachmentMode == ATTACHMENT_MODE_EDGE:
1506 if len(self._attachmentPoints):
1507 for point in self._attachmentPoints:
1508 if point._id == attachment:
1509 return self._xpos + point._x, self._ypos + point._y
1510 return None
1511 else:
1512 # Assume is rectangular
1513 w, h = self.GetBoundingBoxMax()
1514 top = self._ypos + h / 2.0
1515 bottom = self._ypos - h / 2.0
1516 left = self._xpos - w / 2.0
1517 right = self._xpos + w / 2.0
1518
1519 # wtf?
1520 line and line.IsEnd(self)
1521
1522 physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
1523
1524 # Simplified code
1525 if physicalAttachment == 0:
1526 pt = self.CalcSimpleAttachment((left, bottom), (right, bottom), nth, no_arcs, line)
1527 elif physicalAttachment == 1:
1528 pt = self.CalcSimpleAttachment((right, bottom), (right, top), nth, no_arcs, line)
1529 elif physicalAttachment == 2:
1530 pt = self.CalcSimpleAttachment((left, top), (right, top), nth, no_arcs, line)
1531 elif physicalAttachment == 3:
1532 pt = self.CalcSimpleAttachment((left, bottom), (left, top), nth, no_arcs, line)
1533 else:
1534 return None
1535 return pt[0], pt[1]
1536 return None
1537
1538 def GetBoundingBoxMax(self):
1539 """Get the maximum bounding box for the shape, taking into account
1540 external features such as shadows.
1541 """
1542 ww, hh = self.GetBoundingBoxMin()
1543 if self._shadowMode != SHADOW_NONE:
1544 ww += self._shadowOffsetX
1545 hh += self._shadowOffsetY
1546 return ww, hh
1547
1548 def GetBoundingBoxMin(self):
1549 """Get the minimum bounding box for the shape, that defines the area
1550 available for drawing the contents (such as text).
1551
1552 Must be overridden.
1553 """
1554 return 0, 0
1555
1556 def HasDescendant(self, image):
1557 """TRUE if image is a descendant of this composite."""
1558 if image == self:
1559 return True
1560 for child in self._children:
1561 if child.HasDescendant(image):
1562 return True
1563 return False
1564
1565 # Assuming the attachment lies along a vertical or horizontal line,
1566 # calculate the position on that point.
1567 def CalcSimpleAttachment(self, pt1, pt2, nth, noArcs, line):
1568 """Assuming the attachment lies along a vertical or horizontal line,
1569 calculate the position on that point.
1570
1571 Parameters:
1572
1573 pt1
1574 The first point of the line repesenting the edge of the shape.
1575
1576 pt2
1577 The second point of the line representing the edge of the shape.
1578
1579 nth
1580 The position on the edge (for example there may be 6 lines at
1581 this attachment point, and this may be the 2nd line.
1582
1583 noArcs
1584 The number of lines at this edge.
1585
1586 line
1587 The line shape.
1588
1589 Remarks
1590
1591 This function expects the line to be either vertical or horizontal,
1592 and determines which.
1593 """
1594 isEnd = line and line.IsEnd(self)
1595
1596 # Are we horizontal or vertical?
1597 isHorizontal = RoughlyEqual(pt1[1], pt2[1])
1598
1599 if isHorizontal:
1600 if pt1[0] > pt2[0]:
1601 firstPoint = pt2
1602 secondPoint = pt1
1603 else:
1604 firstPoint = pt1
1605 secondPoint = pt2
1606
1607 if self._spaceAttachments:
1608 if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1609 # Align line according to the next handle along
1610 point = line.GetNextControlPoint(self)
1611 if point[0] < firstPoint[0]:
1612 x = firstPoint[0]
1613 elif point[0] > secondPoint[0]:
1614 x = secondPoint[0]
1615 else:
1616 x = point[0]
1617 else:
1618 x = firstPoint[0] + (nth + 1) * (secondPoint[0] - firstPoint[0]) / (noArcs + 1.0)
1619 else:
1620 x = (secondPoint[0] - firstPoint[0]) / 2.0 # Midpoint
1621 y = pt1[1]
1622 else:
1623 assert RoughlyEqual(pt1[0], pt2[0])
1624
1625 if pt1[1] > pt2[1]:
1626 firstPoint = pt2
1627 secondPoint = pt1
1628 else:
1629 firstPoint = pt1
1630 secondPoint = pt2
1631
1632 if self._spaceAttachments:
1633 if line and line.GetAlignmentType(isEnd) == LINE_ALIGNMENT_TO_NEXT_HANDLE:
1634 # Align line according to the next handle along
1635 point = line.GetNextControlPoint(self)
1636 if point[1] < firstPoint[1]:
1637 y = firstPoint[1]
1638 elif point[1] > secondPoint[1]:
1639 y = secondPoint[1]
1640 else:
1641 y = point[1]
1642 else:
1643 y = firstPoint[1] + (nth + 1) * (secondPoint[1] - firstPoint[1]) / (noArcs + 1.0)
1644 else:
1645 y = (secondPoint[1] - firstPoint[1]) / 2.0 # Midpoint
1646 x = pt1[0]
1647
1648 return x, y
1649
1650 # Return the zero-based position in m_lines of line
1651 def GetLinePosition(self, line):
1652 """Get the zero-based position of line in the list of lines
1653 for this shape.
1654 """
1655 try:
1656 return self._lines.index(line)
1657 except:
1658 return 0
1659
1660
1661 # |________|
1662 # | <- root
1663 # | <- neck
1664 # shoulder1 ->---------<- shoulder2
1665 # | | | | |
1666 # <- branching attachment point N-1
1667
1668 def GetBranchingAttachmentInfo(self, attachment):
1669 """Get information about where branching connections go.
1670
1671 Returns FALSE if there are no lines at this attachment.
1672 """
1673 physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
1674
1675 # Number of lines at this attachment
1676 lineCount = self.GetAttachmentLineCount(attachment)
1677
1678 if not lineCount:
1679 return False
1680
1681 totalBranchLength = self._branchSpacing * (lineCount - 1)
1682 root = self.GetBranchingAttachmentRoot(attachment)
1683
1684 neck = wx.RealPoint()
1685 shoulder1 = wx.RealPoint()
1686 shoulder2 = wx.RealPoint()
1687
1688 # Assume that we have attachment points 0 to 3: top, right, bottom, left
1689 if physicalAttachment == 0:
1690 neck[0] = self.GetX()
1691 neck[1] = root[1] - self._branchNeckLength
1692
1693 shoulder1[0] = root[0] - totalBranchLength / 2.0
1694 shoulder2[0] = root[0] + totalBranchLength / 2.0
1695
1696 shoulder1[1] = neck[1]
1697 shoulder2[1] = neck[1]
1698 elif physicalAttachment == 1:
1699 neck[0] = root[0] + self._branchNeckLength
1700 neck[1] = root[1]
1701
1702 shoulder1[0] = neck[0]
1703 shoulder2[0] = neck[0]
1704
1705 shoulder1[1] = neck[1] - totalBranchLength / 2.0
1706 shoulder1[1] = neck[1] + totalBranchLength / 2.0
1707 elif physicalAttachment == 2:
1708 neck[0] = self.GetX()
1709 neck[1] = root[1] + self._branchNeckLength
1710
1711 shoulder1[0] = root[0] - totalBranchLength / 2.0
1712 shoulder2[0] = root[0] + totalBranchLength / 2.0
1713
1714 shoulder1[1] = neck[1]
1715 shoulder2[1] = neck[1]
1716 elif physicalAttachment == 3:
1717 neck[0] = root[0] - self._branchNeckLength
1718 neck[1] = root[1]
1719
1720 shoulder1[0] = neck[0]
1721 shoulder2[0] = neck[0]
1722
1723 shoulder1[1] = neck[1] - totalBranchLength / 2.0
1724 shoulder2[1] = neck[1] + totalBranchLength / 2.0
1725 else:
1726 raise "Unrecognised attachment point in GetBranchingAttachmentInfo"
1727 return root, neck, shoulder1, shoulder2
1728
1729 def GetBranchingAttachmentPoint(self, attachment, n):
1730 physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
1731
1732 root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
1733 pt = wx.RealPoint()
1734 stemPt = wx.RealPoint()
1735
1736 if physicalAttachment == 0:
1737 pt[1] = neck[1] - self._branchStemLength
1738 pt[0] = shoulder1[0] + n * self._branchSpacing
1739
1740 stemPt[0] = pt[0]
1741 stemPt[1] = neck[1]
1742 elif physicalAttachment == 2:
1743 pt[1] = neck[1] + self._branchStemLength
1744 pt[0] = shoulder1[0] + n * self._branchStemLength
1745
1746 stemPt[0] = pt[0]
1747 stemPt[1] = neck[1]
1748 elif physicalAttachment == 1:
1749 pt[0] = neck[0] + self._branchStemLength
1750 pt[1] = shoulder1[1] + n * self._branchSpacing
1751
1752 stemPt[0] = neck[0]
1753 stemPt[1] = pt[1]
1754 elif physicalAttachment == 3:
1755 pt[0] = neck[0] - self._branchStemLength
1756 pt[1] = shoulder1[1] + n * self._branchSpacing
1757
1758 stemPt[0] = neck[0]
1759 stemPt[1] = pt[1]
1760 else:
1761 raise "Unrecognised attachment point in GetBranchingAttachmentPoint"
1762
1763 return pt, stemPt
1764
1765 def GetAttachmentLineCount(self, attachment):
1766 """Get the number of lines at this attachment position."""
1767 count = 0
1768 for lineShape in self._lines:
1769 if lineShape.GetFrom() == self and lineShape.GetAttachmentFrom() == attachment:
1770 count += 1
1771 elif lineShape.GetTo() == self and lineShape.GetAttachmentTo() == attachment:
1772 count += 1
1773 return count
1774
1775 def GetBranchingAttachmentRoot(self, attachment):
1776 """Get the root point at the given attachment."""
1777 physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
1778
1779 root = wx.RealPoint()
1780
1781 width, height = self.GetBoundingBoxMax()
1782
1783 # Assume that we have attachment points 0 to 3: top, right, bottom, left
1784 if physicalAttachment == 0:
1785 root[0] = self.GetX()
1786 root[1] = self.GetY() - height / 2.0
1787 elif physicalAttachment == 1:
1788 root[0] = self.GetX() + width / 2.0
1789 root[1] = self.GetY()
1790 elif physicalAttachment == 2:
1791 root[0] = self.GetX()
1792 root[1] = self.GetY() + height / 2.0
1793 elif physicalAttachment == 3:
1794 root[0] = self.GetX() - width / 2.0
1795 root[1] = self.GetY()
1796 else:
1797 raise "Unrecognised attachment point in GetBranchingAttachmentRoot"
1798
1799 return root
1800
1801 # Draw or erase the branches (not the actual arcs though)
1802 def OnDrawBranchesAttachment(self, dc, attachment, erase = False):
1803 count = self.GetAttachmentLineCount(attachment)
1804 if count == 0:
1805 return
1806
1807 root, neck, shoulder1, shoulder2 = self.GetBranchingAttachmentInfo(attachment)
1808
1809 if erase:
1810 dc.SetPen(wx.WHITE_PEN)
1811 dc.SetBrush(wx.WHITE_BRUSH)
1812 else:
1813 dc.SetPen(wx.BLACK_PEN)
1814 dc.SetBrush(wx.BLACK_BRUSH)
1815
1816 # Draw neck
1817 dc.DrawLine(root[0], root[1], neck[0], neck[1])
1818
1819 if count > 1:
1820 # Draw shoulder-to-shoulder line
1821 dc.DrawLine(shoulder1[0], shoulder1[1], shoulder2[0], shoulder2[1])
1822 # Draw all the little branches
1823 for i in range(count):
1824 pt, stemPt = self.GetBranchingAttachmentPoint(attachment, i)
1825 dc.DrawLine(stemPt[0], stemPt[1], pt[0], pt[1])
1826
1827 if self.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB and count > 1:
1828 blobSize = 6.0
1829 dc.DrawEllipse(stemPt[0] - blobSize / 2.0, stemPt[1] - blobSize / 2.0, blobSize, blobSize)
1830
1831 def OnDrawBranches(self, dc, erase = False):
1832 if self._attachmentMode != ATTACHMENT_MODE_BRANCHING:
1833 return
1834 for i in range(self.GetNumberOfAttachments()):
1835 self.OnDrawBranchesAttachment(dc, i, erase)
1836
1837 def GetAttachmentPositionEdge(self, attachment, nth = 0, no_arcs = 1, line = None):
1838 """ Only get the attachment position at the _edge_ of the shape,
1839 ignoring branching mode. This is used e.g. to indicate the edge of
1840 interest, not the point on the attachment branch.
1841 """
1842 oldMode = self._attachmentMode
1843
1844 # Calculate as if to edge, not branch
1845 if self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
1846 self._attachmentMode = ATTACHMENT_MODE_EDGE
1847 res = self.GetAttachmentPosition(attachment, nth, no_arcs, line)
1848 self._attachmentMode = oldMode
1849
1850 return res
1851
1852 def PhysicalToLogicalAttachment(self, physicalAttachment):
1853 """ Rotate the standard attachment point from physical
1854 (0 is always North) to logical (0 -> 1 if rotated by 90 degrees)
1855 """
1856 if RoughlyEqual(self.GetRotation(), 0):
1857 i = physicalAttachment
1858 elif RoughlyEqual(self.GetRotation(), math.pi / 2.0):
1859 i = physicalAttachment - 1
1860 elif RoughlyEqual(self.GetRotation(), math.pi):
1861 i = physicalAttachment - 2
1862 elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0):
1863 i = physicalAttachment - 3
1864 else:
1865 # Can't handle -- assume the same
1866 return physicalAttachment
1867
1868 if i < 0:
1869 i += 4
1870
1871 return i
1872
1873 def LogicalToPhysicalAttachment(self, logicalAttachment):
1874 """Rotate the standard attachment point from logical
1875 to physical (0 is always North).
1876 """
1877 if RoughlyEqual(self.GetRotation(), 0):
1878 i = logicalAttachment
1879 elif RoughlyEqual(self.GetRotation(), math.pi / 2.0):
1880 i = logicalAttachment + 1
1881 elif RoughlyEqual(self.GetRotation(), math.pi):
1882 i = logicalAttachment + 2
1883 elif RoughlyEqual(self.GetRotation(), 3 * math.pi / 2.0):
1884 i = logicalAttachment + 3
1885 else:
1886 return logicalAttachment
1887
1888 if i > 3:
1889 i -= 4
1890
1891 return i
1892
1893 def Rotate(self, x, y, theta):
1894 """Rotate about the given axis by the given amount in radians."""
1895 self._rotation = theta
1896 if self._rotation < 0:
1897 self._rotation += 2 * math.pi
1898 elif self._rotation > 2 * math.pi:
1899 self._rotation -= 2 * math.pi
1900
1901 def GetBackgroundPen(self):
1902 """Return pen of the right colour for the background."""
1903 if self.GetCanvas():
1904 return wx.Pen(self.GetCanvas().GetBackgroundColour(), 1, wx.SOLID)
1905 return WhiteBackgroundPen
1906
1907 def GetBackgroundBrush(self):
1908 """Return brush of the right colour for the background."""
1909 if self.GetCanvas():
1910 return wx.Brush(self.GetCanvas().GetBackgroundColour(), wx.SOLID)
1911 return WhiteBackgroundBrush
1912
1913 def GetX(self):
1914 """Get the x position of the centre of the shape."""
1915 return self._xpos
1916
1917 def GetY(self):
1918 """Get the y position of the centre of the shape."""
1919 return self._ypos
1920
1921 def SetX(self, x):
1922 """Set the x position of the shape."""
1923 self._xpos = x
1924
1925 def SetY(self, y):
1926 """Set the y position of the shape."""
1927 self._ypos = y
1928
1929 def GetParent(self):
1930 """Return the parent of this shape, if it is part of a composite."""
1931 return self._parent
1932
1933 def SetParent(self, p):
1934 self._parent = p
1935
1936 def GetChildren(self):
1937 """Return the list of children for this shape."""
1938 return self._children
1939
1940 def GetDrawHandles(self):
1941 """Return the list of drawhandles."""
1942 return self._drawHandles
1943
1944 def GetEventHandler(self):
1945 """Return the event handler for this shape."""
1946 return self._eventHandler
1947
1948 def SetEventHandler(self, handler):
1949 """Set the event handler for this shape."""
1950 self._eventHandler = handler
1951
1952 def Recompute(self):
1953 """Recomputes any constraints associated with the shape.
1954
1955 Normally applicable to CompositeShapes only, but harmless for
1956 other classes of Shape.
1957 """
1958 return True
1959
1960 def IsHighlighted(self):
1961 """TRUE if the shape is highlighted. Shape highlighting is unimplemented."""
1962 return self._highlighted
1963
1964 def GetSensitivityFilter(self):
1965 """Return the sensitivity filter, a bitlist of values.
1966
1967 See Shape.SetSensitivityFilter.
1968 """
1969 return self._sensitivity
1970
1971 def SetFixedSize(self, x, y):
1972 """Set the shape to be fixed size."""
1973 self._fixedWidth = x
1974 self._fixedHeight = y
1975
1976 def GetFixedSize(self):
1977 """Return flags indicating whether the shape is of fixed size in
1978 either direction.
1979 """
1980 return self._fixedWidth, self._fixedHeight
1981
1982 def GetFixedWidth(self):
1983 """TRUE if the shape cannot be resized in the horizontal plane."""
1984 return self._fixedWidth
1985
1986 def GetFixedHeight(self):
1987 """TRUE if the shape cannot be resized in the vertical plane."""
1988 return self._fixedHeight
1989
1990 def SetSpaceAttachments(self, sp):
1991 """Indicate whether lines should be spaced out evenly at the point
1992 they touch the node (sp = True), or whether they should join at a single
1993 point (sp = False).
1994 """
1995 self._spaceAttachments = sp
1996
1997 def GetSpaceAttachments(self):
1998 """Return whether lines should be spaced out evenly at the point they
1999 touch the node (True), or whether they should join at a single point
2000 (False).
2001 """
2002 return self._spaceAttachments
2003
2004 def SetCentreResize(self, cr):
2005 """Specify whether the shape is to be resized from the centre (the
2006 centre stands still) or from the corner or side being dragged (the
2007 other corner or side stands still).
2008 """
2009 self._centreResize = cr
2010
2011 def GetCentreResize(self):
2012 """TRUE if the shape is to be resized from the centre (the centre stands
2013 still), or FALSE if from the corner or side being dragged (the other
2014 corner or side stands still)
2015 """
2016 return self._centreResize
2017
2018 def SetMaintainAspectRatio(self, ar):
2019 """Set whether a shape that resizes should not change the aspect ratio
2020 (width and height should be in the original proportion).
2021 """
2022 self._maintainAspectRatio = ar
2023
2024 def GetMaintainAspectRatio(self):
2025 """TRUE if shape keeps aspect ratio during resize."""
2026 return self._maintainAspectRatio
2027
2028 def GetLines(self):
2029 """Return the list of lines connected to this shape."""
2030 return self._lines
2031
2032 def SetDisableLabel(self, flag):
2033 """Set flag to TRUE to stop the default region being shown."""
2034 self._disableLabel = flag
2035
2036 def GetDisableLabel(self):
2037 """TRUE if the default region will not be shown, FALSE otherwise."""
2038 return self._disableLabel
2039
2040 def SetAttachmentMode(self, mode):
2041 """Set the attachment mode.
2042
2043 If TRUE, attachment points will be significant when drawing lines to
2044 and from this shape.
2045 If FALSE, lines will be drawn as if to the centre of the shape.
2046 """
2047 self._attachmentMode = mode
2048
2049 def GetAttachmentMode(self):
2050 """Return the attachment mode.
2051
2052 See Shape.SetAttachmentMode.
2053 """
2054 return self._attachmentMode
2055
2056 def SetId(self, i):
2057 """Set the integer identifier for this shape."""
2058 self._id = i
2059
2060 def GetId(self):
2061 """Return the integer identifier for this shape."""
2062 return self._id
2063
2064 def IsShown(self):
2065 """TRUE if the shape is in a visible state, FALSE otherwise.
2066
2067 Note that this has nothing to do with whether the window is hidden
2068 or the shape has scrolled off the canvas; it refers to the internal
2069 visibility flag.
2070 """
2071 return self._visible
2072
2073 def GetPen(self):
2074 """Return the pen used for drawing the shape's outline."""
2075 return self._pen
2076
2077 def GetBrush(self):
2078 """Return the brush used for filling the shape."""
2079 return self._brush
2080
2081 def GetNumberOfTextRegions(self):
2082 """Return the number of text regions for this shape."""
2083 return len(self._regions)
2084
2085 def GetRegions(self):
2086 """Return the list of ShapeRegions."""
2087 return self._regions
2088
2089 # Control points ('handles') redirect control to the actual shape, to
2090 # make it easier to override sizing behaviour.
2091 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
2092 bound_x, bound_y = self.GetBoundingBoxMin()
2093
2094 dc = wx.ClientDC(self.GetCanvas())
2095 self.GetCanvas().PrepareDC(dc)
2096
2097 dc.SetLogicalFunction(OGLRBLF)
2098
2099 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
2100 dc.SetPen(dottedPen)
2101 dc.SetBrush(wx.TRANSPARENT_BRUSH)
2102
2103 if self.GetCentreResize():
2104 # Maintain the same centre point
2105 new_width = 2.0 * abs(x - self.GetX())
2106 new_height = 2.0 * abs(y - self.GetY())
2107
2108 # Constrain sizing according to what control point you're dragging
2109 if pt._type == CONTROL_POINT_HORIZONTAL:
2110 if self.GetMaintainAspectRatio():
2111 new_height = bound_y * (new_width / bound_x)
2112 else:
2113 new_height = bound_y
2114 elif pt._type == CONTROL_POINT_VERTICAL:
2115 if self.GetMaintainAspectRatio():
2116 new_width = bound_x * (new_height / bound_y)
2117 else:
2118 new_width = bound_x
2119 elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
2120 new_height = bound_y * (new_width / bound_x)
2121
2122 if self.GetFixedWidth():
2123 new_width = bound_x
2124
2125 if self.GetFixedHeight():
2126 new_height = bound_y
2127
2128 pt._controlPointDragEndWidth = new_width
2129 pt._controlPointDragEndHeight = new_height
2130
2131 self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
2132 else:
2133 # Don't maintain the same centre point
2134 newX1 = min(pt._controlPointDragStartX, x)
2135 newY1 = min(pt._controlPointDragStartY, y)
2136 newX2 = max(pt._controlPointDragStartX, x)
2137 newY2 = max(pt._controlPointDragStartY, y)
2138 if pt._type == CONTROL_POINT_HORIZONTAL:
2139 newY1 = pt._controlPointDragStartY
2140 newY2 = newY1 + pt._controlPointDragStartHeight
2141 elif pt._type == CONTROL_POINT_VERTICAL:
2142 newX1 = pt._controlPointDragStartX
2143 newX2 = newX1 + pt._controlPointDragStartWidth
2144 elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()):
2145 newH = (newX2 - newX1) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth)
2146 if self.GetY() > pt._controlPointDragStartY:
2147 newY2 = newY1 + newH
2148 else:
2149 newY1 = newY2 - newH
2150
2151 newWidth = float(newX2 - newX1)
2152 newHeight = float(newY2 - newY1)
2153
2154 if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
2155 newWidth = bound_x * (newHeight / bound_y)
2156
2157 if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
2158 newHeight = bound_y * (newWidth / bound_x)
2159
2160 pt._controlPointDragPosX = newX1 + newWidth / 2.0
2161 pt._controlPointDragPosY = newY1 + newHeight / 2.0
2162 if self.GetFixedWidth():
2163 newWidth = bound_x
2164
2165 if self.GetFixedHeight():
2166 newHeight = bound_y
2167
2168 pt._controlPointDragEndWidth = newWidth
2169 pt._controlPointDragEndHeight = newHeight
2170 self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
2171
2172 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2173 self._canvas.CaptureMouse()
2174
2175 dc = wx.ClientDC(self.GetCanvas())
2176 self.GetCanvas().PrepareDC(dc)
2177
2178 dc.SetLogicalFunction(OGLRBLF)
2179
2180 bound_x, bound_y = self.GetBoundingBoxMin()
2181 self.GetEventHandler().OnBeginSize(bound_x, bound_y)
2182
2183 # Choose the 'opposite corner' of the object as the stationary
2184 # point in case this is non-centring resizing.
2185 if pt.GetX() < self.GetX():
2186 pt._controlPointDragStartX = self.GetX() + bound_x / 2.0
2187 else:
2188 pt._controlPointDragStartX = self.GetX() - bound_x / 2.0
2189
2190 if pt.GetY() < self.GetY():
2191 pt._controlPointDragStartY = self.GetY() + bound_y / 2.0
2192 else:
2193 pt._controlPointDragStartY = self.GetY() - bound_y / 2.0
2194
2195 if pt._type == CONTROL_POINT_HORIZONTAL:
2196 pt._controlPointDragStartY = self.GetY() - bound_y / 2.0
2197 elif pt._type == CONTROL_POINT_VERTICAL:
2198 pt._controlPointDragStartX = self.GetX() - bound_x / 2.0
2199
2200 # We may require the old width and height
2201 pt._controlPointDragStartWidth = bound_x
2202 pt._controlPointDragStartHeight = bound_y
2203
2204 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
2205 dc.SetPen(dottedPen)
2206 dc.SetBrush(wx.TRANSPARENT_BRUSH)
2207
2208 if self.GetCentreResize():
2209 new_width = 2.0 * abs(x - self.GetX())
2210 new_height = 2.0 * abs(y - self.GetY())
2211
2212 # Constrain sizing according to what control point you're dragging
2213 if pt._type == CONTROL_POINT_HORIZONTAL:
2214 if self.GetMaintainAspectRatio():
2215 new_height = bound_y * (new_width / bound_x)
2216 else:
2217 new_height = bound_y
2218 elif pt._type == CONTROL_POINT_VERTICAL:
2219 if self.GetMaintainAspectRatio():
2220 new_width = bound_x * (new_height / bound_y)
2221 else:
2222 new_width = bound_x
2223 elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT):
2224 new_height = bound_y * (new_width / bound_x)
2225
2226 if self.GetFixedWidth():
2227 new_width = bound_x
2228
2229 if self.GetFixedHeight():
2230 new_height = bound_y
2231
2232 pt._controlPointDragEndWidth = new_width
2233 pt._controlPointDragEndHeight = new_height
2234 self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), new_width, new_height)
2235 else:
2236 # Don't maintain the same centre point
2237 newX1 = min(pt._controlPointDragStartX, x)
2238 newY1 = min(pt._controlPointDragStartY, y)
2239 newX2 = max(pt._controlPointDragStartX, x)
2240 newY2 = max(pt._controlPointDragStartY, y)
2241 if pt._type == CONTROL_POINT_HORIZONTAL:
2242 newY1 = pt._controlPointDragStartY
2243 newY2 = newY1 + pt._controlPointDragStartHeight
2244 elif pt._type == CONTROL_POINT_VERTICAL:
2245 newX1 = pt._controlPointDragStartX
2246 newX2 = newX1 + pt._controlPointDragStartWidth
2247 elif pt._type == CONTROL_POINT_DIAGONAL and (keys & KEY_SHIFT or self.GetMaintainAspectRatio()):
2248 newH = (newX2 - newX1) * (float(pt._controlPointDragStartHeight) / pt._controlPointDragStartWidth)
2249 if pt.GetY() > pt._controlPointDragStartY:
2250 newY2 = newY1 + newH
2251 else:
2252 newY1 = newY2 - newH
2253
2254 newWidth = float(newX2 - newX1)
2255 newHeight = float(newY2 - newY1)
2256
2257 if pt._type == CONTROL_POINT_VERTICAL and self.GetMaintainAspectRatio():
2258 newWidth = bound_x * (newHeight / bound_y)
2259
2260 if pt._type == CONTROL_POINT_HORIZONTAL and self.GetMaintainAspectRatio():
2261 newHeight = bound_y * (newWidth / bound_x)
2262
2263 pt._controlPointDragPosX = newX1 + newWidth / 2.0
2264 pt._controlPointDragPosY = newY1 + newHeight / 2.0
2265 if self.GetFixedWidth():
2266 newWidth = bound_x
2267
2268 if self.GetFixedHeight():
2269 newHeight = bound_y
2270
2271 pt._controlPointDragEndWidth = newWidth
2272 pt._controlPointDragEndHeight = newHeight
2273 self.GetEventHandler().OnDrawOutline(dc, pt._controlPointDragPosX, pt._controlPointDragPosY, newWidth, newHeight)
2274
2275 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2276 dc = wx.ClientDC(self.GetCanvas())
2277 self.GetCanvas().PrepareDC(dc)
2278
2279 if self._canvas.HasCapture():
2280 self._canvas.ReleaseMouse()
2281 dc.SetLogicalFunction(wx.COPY)
2282 self.Recompute()
2283 self.ResetControlPoints()
2284
2285 self.Erase(dc)
2286
2287 self.SetSize(pt._controlPointDragEndWidth, pt._controlPointDragEndHeight)
2288
2289 # The next operation could destroy this control point (it does for
2290 # label objects, via formatting the text), so save all values we're
2291 # going to use, or we'll be accessing garbage.
2292
2293 #return
2294
2295 if self.GetCentreResize():
2296 self.Move(dc, self.GetX(), self.GetY())
2297 else:
2298 self.Move(dc, pt._controlPointDragPosX, pt._controlPointDragPosY)
2299
2300 # Recursively redraw links if we have a composite
2301 if len(self.GetChildren()):
2302 self.DrawLinks(dc, -1, True)
2303
2304 width, height = self.GetBoundingBoxMax()
2305 self.GetEventHandler().OnEndSize(width, height)
2306
2307 if not self._canvas.GetQuickEditMode() and pt._eraseObject:
2308 self._canvas.Redraw(dc)
2309
2310
2311
2312 class RectangleShape(Shape):
2313 """
2314 The wxRectangleShape has rounded or square corners.
2315
2316 Derived from:
2317 Shape
2318 """
2319 def __init__(self, w = 0.0, h = 0.0):
2320 Shape.__init__(self)
2321 self._width = w
2322 self._height = h
2323 self._cornerRadius = 0.0
2324 self.SetDefaultRegionSize()
2325
2326 def OnDraw(self, dc):
2327 x1 = self._xpos - self._width / 2.0
2328 y1 = self._ypos - self._height / 2.0
2329
2330 if self._shadowMode != SHADOW_NONE:
2331 if self._shadowBrush:
2332 dc.SetBrush(self._shadowBrush)
2333 dc.SetPen(TransparentPen)
2334
2335 if self._cornerRadius:
2336 dc.DrawRoundedRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height, self._cornerRadius)
2337 else:
2338 dc.DrawRectangle(x1 + self._shadowOffsetX, y1 + self._shadowOffsetY, self._width, self._height)
2339
2340 if self._pen:
2341 if self._pen.GetWidth() == 0:
2342 dc.SetPen(TransparentPen)
2343 else:
2344 dc.SetPen(self._pen)
2345 if self._brush:
2346 dc.SetBrush(self._brush)
2347
2348 if self._cornerRadius:
2349 dc.DrawRoundedRectangle(x1, y1, self._width, self._height, self._cornerRadius)
2350 else:
2351 dc.DrawRectangle(x1, y1, self._width, self._height)
2352
2353 def GetBoundingBoxMin(self):
2354 return self._width, self._height
2355
2356 def SetSize(self, x, y, recursive = False):
2357 self.SetAttachmentSize(x, y)
2358 self._width = max(x, 1)
2359 self._height = max(y, 1)
2360 self.SetDefaultRegionSize()
2361
2362 def GetCornerRadius(self):
2363 """Get the radius of the rectangle's rounded corners."""
2364 return self._cornerRadius
2365
2366 def SetCornerRadius(self, rad):
2367 """Set the radius of the rectangle's rounded corners.
2368
2369 If the radius is zero, a non-rounded rectangle will be drawn.
2370 If the radius is negative, the value is the proportion of the smaller
2371 dimension of the rectangle.
2372 """
2373 self._cornerRadius = rad
2374
2375 # Assume (x1, y1) is centre of box (most generally, line end at box)
2376 def GetPerimeterPoint(self, x1, y1, x2, y2):
2377 bound_x, bound_y = self.GetBoundingBoxMax()
2378 return FindEndForBox(bound_x, bound_y, self._xpos, self._ypos, x2, y2)
2379
2380 def GetWidth(self):
2381 return self._width
2382
2383 def GetHeight(self):
2384 return self._height
2385
2386 def SetWidth(self, w):
2387 self._width = w
2388
2389 def SetHeight(self, h):
2390 self._height = h
2391
2392
2393
2394 class PolygonShape(Shape):
2395 """A PolygonShape's shape is defined by a number of points passed to
2396 the object's constructor. It can be used to create new shapes such as
2397 diamonds and triangles.
2398 """
2399 def __init__(self):
2400 Shape.__init__(self)
2401
2402 self._points = None
2403 self._originalPoints = None
2404
2405 def Create(self, the_points = None):
2406 """Takes a list of wx.RealPoints or tuples; each point is an offset
2407 from the centre.
2408 """
2409 self.ClearPoints()
2410
2411 if not the_points:
2412 self._originalPoints = []
2413 self._points = []
2414 else:
2415 self._originalPoints = the_points
2416
2417 # Duplicate the list of points
2418 self._points = []
2419 for point in the_points:
2420 new_point = wx.Point(point[0], point[1])
2421 self._points.append(new_point)
2422 self.CalculateBoundingBox()
2423 self._originalWidth = self._boundWidth
2424 self._originalHeight = self._boundHeight
2425 self.SetDefaultRegionSize()
2426
2427 def ClearPoints(self):
2428 self._points = []
2429 self._originalPoints = []
2430
2431 # Width and height. Centre of object is centre of box
2432 def GetBoundingBoxMin(self):
2433 return self._boundWidth, self._boundHeight
2434
2435 def GetPoints(self):
2436 """Return the internal list of polygon vertices."""
2437 return self._points
2438
2439 def GetOriginalPoints(self):
2440 return self._originalPoints
2441
2442 def GetOriginalWidth(self):
2443 return self._originalWidth
2444
2445 def GetOriginalHeight(self):
2446 return self._originalHeight
2447
2448 def SetOriginalWidth(self, w):
2449 self._originalWidth = w
2450
2451 def SetOriginalHeight(self, h):
2452 self._originalHeight = h
2453
2454 def CalculateBoundingBox(self):
2455 # Calculate bounding box at construction (and presumably resize) time
2456 left = 10000
2457 right = -10000
2458 top = 10000
2459 bottom = -10000
2460
2461 for point in self._points:
2462 if point[0] < left:
2463 left = point[0]
2464 if point[0] > right:
2465 right = point[0]
2466
2467 if point[1] < top:
2468 top = point[1]
2469 if point[1] > bottom:
2470 bottom = point[1]
2471
2472 self._boundWidth = right - left
2473 self._boundHeight = bottom - top
2474
2475 def CalculatePolygonCentre(self):
2476 """Recalculates the centre of the polygon, and
2477 readjusts the point offsets accordingly.
2478 Necessary since the centre of the polygon
2479 is expected to be the real centre of the bounding
2480 box.
2481 """
2482 left = 10000
2483 right = -10000
2484 top = 10000
2485 bottom = -10000
2486
2487 for point in self._points:
2488 if point[0] < left:
2489 left = point[0]
2490 if point[0] > right:
2491 right = point[0]
2492
2493 if point[1] < top:
2494 top = point[1]
2495 if point[1] > bottom:
2496 bottom = point[1]
2497
2498 bwidth = right - left
2499 bheight = bottom - top
2500
2501 newCentreX = left + bwidth / 2.0
2502 newCentreY = top + bheight / 2.0
2503
2504 for i in range(len(self._points)):
2505 self._points[i] = self._points[i][0] - newCentreX, self._points[i][1] - newCentreY
2506 self._xpos += newCentreX
2507 self._ypos += newCentreY
2508
2509 def HitTest(self, x, y):
2510 # Imagine four lines radiating from this point. If all of these lines
2511 # hit the polygon, we're inside it, otherwise we're not. Obviously
2512 # we'd need more radiating lines to be sure of correct results for
2513 # very strange (concave) shapes.
2514 endPointsX = [x, x + 1000, x, x - 1000]
2515 endPointsY = [y - 1000, y, y + 1000, y]
2516
2517 xpoints = []
2518 ypoints = []
2519
2520 for point in self._points:
2521 xpoints.append(point[0] + self._xpos)
2522 ypoints.append(point[1] + self._ypos)
2523
2524 # We assume it's inside the polygon UNLESS one or more
2525 # lines don't hit the outline.
2526 isContained = True
2527
2528 for i in range(4):
2529 if not PolylineHitTest(xpoints, ypoints, x, y, endPointsX[i], endPointsY[i]):
2530 isContained = False
2531
2532 if not isContained:
2533 return False
2534
2535 nearest_attachment = 0
2536
2537 # If a hit, check the attachment points within the object
2538 nearest = 999999
2539
2540 for i in range(self.GetNumberOfAttachments()):
2541 e = self.GetAttachmentPositionEdge(i)
2542 if e:
2543 xp, yp = e
2544 l = math.sqrt((xp - x) * (xp - x) + (yp - y) * (yp - y))
2545 if l < nearest:
2546 nearest = l
2547 nearest_attachment = i
2548
2549 return nearest_attachment, nearest
2550
2551 # Really need to be able to reset the shape! Otherwise, if the
2552 # points ever go to zero, we've lost it, and can't resize.
2553 def SetSize(self, new_width, new_height, recursive = True):
2554 self.SetAttachmentSize(new_width, new_height)
2555
2556 # Multiply all points by proportion of new size to old size
2557 x_proportion = abs(float(new_width) / self._originalWidth)
2558 y_proportion = abs(float(new_height) / self._originalHeight)
2559
2560 for i in range(max(len(self._points), len(self._originalPoints))):
2561 self._points[i] = wx.Point(self._originalPoints[i][0] * x_proportion, self._originalPoints[i][1] * y_proportion)
2562
2563 self._boundWidth = abs(new_width)
2564 self._boundHeight = abs(new_height)
2565 self.SetDefaultRegionSize()
2566
2567 # Make the original points the same as the working points
2568 def UpdateOriginalPoints(self):
2569 """If we've changed the shape, must make the original points match the
2570 working points with this function.
2571 """
2572 self._originalPoints = []
2573
2574 for point in self._points:
2575 original_point = wx.RealPoint(point[0], point[1])
2576 self._originalPoints.append(original_point)
2577
2578 self.CalculateBoundingBox()
2579 self._originalWidth = self._boundWidth
2580 self._originalHeight = self._boundHeight
2581
2582 def AddPolygonPoint(self, pos):
2583 """Add a control point after the given point."""
2584 try:
2585 firstPoint = self._points[pos]
2586 except ValueError:
2587 firstPoint = self._points[0]
2588
2589 try:
2590 secondPoint = self._points[pos + 1]
2591 except ValueError:
2592 secondPoint = self._points[0]
2593
2594 x = (secondPoint[0] - firstPoint[0]) / 2.0 + firstPoint[0]
2595 y = (secondPoint[1] - firstPoint[1]) / 2.0 + firstPoint[1]
2596 point = wx.RealPoint(x, y)
2597
2598 if pos >= len(self._points) - 1:
2599 self._points.append(point)
2600 else:
2601 self._points.insert(pos + 1, point)
2602
2603 self.UpdateOriginalPoints()
2604
2605 if self._selected:
2606 self.DeleteControlPoints()
2607 self.MakeControlPoints()
2608
2609 def DeletePolygonPoint(self, pos):
2610 """Delete the given control point."""
2611 if pos < len(self._points):
2612 del self._points[pos]
2613 self.UpdateOriginalPoints()
2614 if self._selected:
2615 self.DeleteControlPoints()
2616 self.MakeControlPoints()
2617
2618 # Assume (x1, y1) is centre of box (most generally, line end at box)
2619 def GetPerimeterPoint(self, x1, y1, x2, y2):
2620 # First check for situation where the line is vertical,
2621 # and we would want to connect to a point on that vertical --
2622 # oglFindEndForPolyline can't cope with this (the arrow
2623 # gets drawn to the wrong place).
2624 if self._attachmentMode == ATTACHMENT_MODE_NONE and x1 == x2:
2625 # Look for the point we'd be connecting to. This is
2626 # a heuristic...
2627 for point in self._points:
2628 if point[0] == 0:
2629 if y2 > y1 and point[1] > 0:
2630 return point[0] + self._xpos, point[1] + self._ypos
2631 elif y2 < y1 and point[1] < 0:
2632 return point[0] + self._xpos, point[1] + self._ypos
2633
2634 xpoints = []
2635 ypoints = []
2636 for point in self._points:
2637 xpoints.append(point[0] + self._xpos)
2638 ypoints.append(point[1] + self._ypos)
2639
2640 return FindEndForPolyline(xpoints, ypoints, x1, y1, x2, y2)
2641
2642 def OnDraw(self, dc):
2643 if self._shadowMode != SHADOW_NONE:
2644 if self._shadowBrush:
2645 dc.SetBrush(self._shadowBrush)
2646 dc.SetPen(TransparentPen)
2647
2648 dc.DrawPolygon(self._points, self._xpos + self._shadowOffsetX, self._ypos, self._shadowOffsetY)
2649
2650 if self._pen:
2651 if self._pen.GetWidth() == 0:
2652 dc.SetPen(TransparentPen)
2653 else:
2654 dc.SetPen(self._pen)
2655 if self._brush:
2656 dc.SetBrush(self._brush)
2657 dc.DrawPolygon(self._points, self._xpos, self._ypos)
2658
2659 def OnDrawOutline(self, dc, x, y, w, h):
2660 dc.SetBrush(wx.TRANSPARENT_BRUSH)
2661 # Multiply all points by proportion of new size to old size
2662 x_proportion = abs(float(w) / self._originalWidth)
2663 y_proportion = abs(float(h) / self._originalHeight)
2664
2665 intPoints = []
2666 for point in self._originalPoints:
2667 intPoints.append(wx.Point(x_proportion * point[0], y_proportion * point[1]))
2668 dc.DrawPolygon(intPoints, x, y)
2669
2670 # Make as many control points as there are vertices
2671 def MakeControlPoints(self):
2672 for point in self._points:
2673 control = PolygonControlPoint(self._canvas, self, CONTROL_POINT_SIZE, point, point[0], point[1])
2674 self._canvas.AddShape(control)
2675 self._controlPoints.append(control)
2676
2677 def ResetControlPoints(self):
2678 for i in range(min(len(self._points), len(self._controlPoints))):
2679 point = self._points[i]
2680 self._controlPoints[i]._xoffset = point[0]
2681 self._controlPoints[i]._yoffset = point[1]
2682 self._controlPoints[i].polygonVertex = point
2683
2684 def GetNumberOfAttachments(self):
2685 maxN = max(len(self._points) - 1, 0)
2686 for point in self._attachmentPoints:
2687 if point._id > maxN:
2688 maxN = point._id
2689 return maxN + 1
2690
2691 def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
2692 if self._attachmentMode == ATTACHMENT_MODE_EDGE and self._points and attachment < len(self._points):
2693 point = self._points[0]
2694 return point[0] + self._xpos, point[1] + self._ypos
2695 return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line)
2696
2697 def AttachmentIsValid(self, attachment):
2698 if not self._points:
2699 return False
2700
2701 if attachment >= 0 and attachment < len(self._points):
2702 return True
2703
2704 for point in self._attachmentPoints:
2705 if point._id == attachment:
2706 return True
2707
2708 return False
2709
2710 # Rotate about the given axis by the given amount in radians
2711 def Rotate(self, x, y, theta):
2712 actualTheta = theta - self._rotation
2713
2714 # Rotate attachment points
2715 sinTheta = math.sin(actualTheta)
2716 cosTheta = math.cos(actualTheta)
2717
2718 for point in self._attachmentPoints:
2719 x1 = point._x
2720 y1 = point._y
2721
2722 point._x = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta
2723 point._y = x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
2724
2725 for i in range(len(self._points)):
2726 x1, y1 = self._points[i]
2727
2728 self._points[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
2729
2730 for i in range(len(self._originalPoints)):
2731 x1, y1 = self._originalPoints[i]
2732
2733 self._originalPoints[i] = x1 * cosTheta - y1 * sinTheta + x * (1 - cosTheta) + y * sinTheta, x1 * sinTheta + y1 * cosTheta + y * (1 - cosTheta) + x * sinTheta
2734
2735 # Added by Pierre Hjälm. If we don't do this the outline will be
2736 # the wrong size. Hopefully it won't have any ill effects.
2737 self.UpdateOriginalPoints()
2738
2739 self._rotation = theta
2740
2741 self.CalculatePolygonCentre()
2742 self.CalculateBoundingBox()
2743 self.ResetControlPoints()
2744
2745 # Control points ('handles') redirect control to the actual shape, to
2746 # make it easier to override sizing behaviour.
2747 def OnSizingDragLeft(self, pt, draw, x, y, keys = 0, attachment = 0):
2748 dc = wx.ClientDC(self.GetCanvas())
2749 self.GetCanvas().PrepareDC(dc)
2750
2751 dc.SetLogicalFunction(OGLRBLF)
2752
2753 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
2754 dc.SetPen(dottedPen)
2755 dc.SetBrush(wx.TRANSPARENT_BRUSH)
2756
2757 # Code for CTRL-drag in C++ version commented out
2758
2759 pt.CalculateNewSize(x, y)
2760
2761 self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1])
2762
2763 def OnSizingBeginDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2764 dc = wx.ClientDC(self.GetCanvas())
2765 self.GetCanvas().PrepareDC(dc)
2766
2767 self.Erase(dc)
2768
2769 dc.SetLogicalFunction(OGLRBLF)
2770
2771 bound_x, bound_y = self.GetBoundingBoxMin()
2772
2773 dist = math.sqrt((x - self.GetX()) * (x - self.GetX()) + (y - self.GetY()) * (y - self.GetY()))
2774
2775 pt._originalDistance = dist
2776 pt._originalSize[0] = bound_x
2777 pt._originalSize[1] = bound_y
2778
2779 if pt._originalDistance == 0:
2780 pt._originalDistance = 0.0001
2781
2782 dottedPen = wx.Pen(wx.Colour(0, 0, 0), 1, wx.DOT)
2783 dc.SetPen(dottedPen)
2784 dc.SetBrush(wx.TRANSPARENT_BRUSH)
2785
2786 # Code for CTRL-drag in C++ version commented out
2787
2788 pt.CalculateNewSize(x, y)
2789
2790 self.GetEventHandler().OnDrawOutline(dc, self.GetX(), self.GetY(), pt.GetNewSize()[0], pt.GetNewSize()[1])
2791
2792 self._canvas.CaptureMouse()
2793
2794 def OnSizingEndDragLeft(self, pt, x, y, keys = 0, attachment = 0):
2795 dc = wx.ClientDC(self.GetCanvas())
2796 self.GetCanvas().PrepareDC(dc)
2797
2798 if self._canvas.HasCapture():
2799 self._canvas.ReleaseMouse()
2800 dc.SetLogicalFunction(wx.COPY)
2801
2802 # If we're changing shape, must reset the original points
2803 if keys & KEY_CTRL:
2804 self.CalculateBoundingBox()
2805 self.CalculatePolygonCentre()
2806 else:
2807 self.SetSize(pt.GetNewSize()[0], pt.GetNewSize()[1])
2808
2809 self.Recompute()
2810 self.ResetControlPoints()
2811 self.Move(dc, self.GetX(), self.GetY())
2812 if not self._canvas.GetQuickEditMode():
2813 self._canvas.Redraw(dc)
2814
2815
2816
2817 class EllipseShape(Shape):
2818 """The EllipseShape behaves similarly to the RectangleShape but is
2819 elliptical.
2820
2821 Derived from:
2822 wxShape
2823 """
2824 def __init__(self, w, h):
2825 Shape.__init__(self)
2826 self._width = w
2827 self._height = h
2828 self.SetDefaultRegionSize()
2829
2830 def GetBoundingBoxMin(self):
2831 return self._width, self._height
2832
2833 def GetPerimeterPoint(self, x1, y1, x2, y2):
2834 bound_x, bound_y = self.GetBoundingBoxMax()
2835
2836 return DrawArcToEllipse(self._xpos, self._ypos, bound_x, bound_y, x2, y2, x1, y1)
2837
2838 def GetWidth(self):
2839 return self._width
2840
2841 def GetHeight(self):
2842 return self._height
2843
2844 def SetWidth(self, w):
2845 self._width = w
2846
2847 def SetHeight(self, h):
2848 self._height = h
2849
2850 def OnDraw(self, dc):
2851 if self._shadowMode != SHADOW_NONE:
2852 if self._shadowBrush:
2853 dc.SetBrush(self._shadowBrush)
2854 dc.SetPen(TransparentPen)
2855 dc.DrawEllipse(self._xpos - self.GetWidth() / 2.0 + self._shadowOffsetX,
2856 self._ypos - self.GetHeight() / 2.0 + self._shadowOffsetY,
2857 self.GetWidth(), self.GetHeight())
2858
2859 if self._pen:
2860 if self._pen.GetWidth() == 0:
2861 dc.SetPen(TransparentPen)
2862 else:
2863 dc.SetPen(self._pen)
2864 if self._brush:
2865 dc.SetBrush(self._brush)
2866 dc.DrawEllipse(self._xpos - self.GetWidth() / 2.0, self._ypos - self.GetHeight() / 2.0, self.GetWidth(), self.GetHeight())
2867
2868 def SetSize(self, x, y, recursive = True):
2869 self.SetAttachmentSize(x, y)
2870 self._width = x
2871 self._height = y
2872 self.SetDefaultRegionSize()
2873
2874 def GetNumberOfAttachments(self):
2875 return Shape.GetNumberOfAttachments(self)
2876
2877 # There are 4 attachment points on an ellipse - 0 = top, 1 = right,
2878 # 2 = bottom, 3 = left.
2879 def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
2880 if self._attachmentMode == ATTACHMENT_MODE_BRANCHING:
2881 return Shape.GetAttachmentPosition(self, attachment, nth, no_arcs, line)
2882
2883 if self._attachmentMode != ATTACHMENT_MODE_NONE:
2884 top = self._ypos + self._height / 2.0
2885 bottom = self._ypos - self._height / 2.0
2886 left = self._xpos - self._width / 2.0
2887 right = self._xpos + self._width / 2.0
2888
2889 physicalAttachment = self.LogicalToPhysicalAttachment(attachment)
2890
2891 if physicalAttachment == 0:
2892 if self._spaceAttachments:
2893 x = left + (nth + 1) * self._width / (no_arcs + 1.0)
2894 else:
2895 x = self._xpos
2896 y = top
2897 # We now have the point on the bounding box: but get the point
2898 # on the ellipse by imagining a vertical line from
2899 # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting
2900 # the ellipse.
2901
2902 return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos - self._height - 500, x, self._ypos)
2903 elif physicalAttachment == 1:
2904 x = right
2905 if self._spaceAttachments:
2906 y = bottom + (nth + 1) * self._height / (no_arcs + 1.0)
2907 else:
2908 y = self._ypos
2909 return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos + self._width + 500, y, self._xpos, y)
2910 elif physicalAttachment == 2:
2911 if self._spaceAttachments:
2912 x = left + (nth + 1) * self._width / (no_arcs + 1.0)
2913 else:
2914 x = self._xpos
2915 y = bottom
2916 return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, x, self._ypos + self._height + 500, x, self._ypos)
2917 elif physicalAttachment == 3:
2918 x = left
2919 if self._spaceAttachments:
2920 y = bottom + (nth + 1) * self._height / (no_arcs + 1.0)
2921 else:
2922 y = self._ypos
2923 return DrawArcToEllipse(self._xpos, self._ypos, self._width, self._height, self._xpos - self._width - 500, y, self._xpos, y)
2924 else:
2925 return Shape.GetAttachmentPosition(self, attachment, x, y, nth, no_arcs, line)
2926 else:
2927 return self._xpos, self._ypos
2928
2929
2930
2931 class CircleShape(EllipseShape):
2932 """An EllipseShape whose width and height are the same."""
2933 def __init__(self, diameter):
2934 EllipseShape.__init__(self, diameter, diameter)
2935 self.SetMaintainAspectRatio(True)
2936
2937 def GetPerimeterPoint(self, x1, y1, x2, y2):
2938 return FindEndForCircle(self._width / 2.0, self._xpos, self._ypos, x2, y2)
2939
2940
2941
2942 class TextShape(RectangleShape):
2943 """As wxRectangleShape, but only the text is displayed."""
2944 def __init__(self, width, height):
2945 RectangleShape.__init__(self, width, height)
2946
2947 def OnDraw(self, dc):
2948 pass
2949
2950
2951
2952 class ShapeRegion(object):
2953 """Object region."""
2954 def __init__(self, region = None):
2955 if region:
2956 self._regionText = region._regionText
2957 self._regionName = region._regionName
2958 self._textColour = region._textColour
2959
2960 self._font = region._font
2961 self._minHeight = region._minHeight
2962 self._minWidth = region._minWidth
2963 self._width = region._width
2964 self._height = region._height
2965 self._x = region._x
2966 self._y = region._y
2967
2968 self._regionProportionX = region._regionProportionX
2969 self._regionProportionY = region._regionProportionY
2970 self._formatMode = region._formatMode
2971 self._actualColourObject = region._actualColourObject
2972 self._actualPenObject = None
2973 self._penStyle = region._penStyle
2974 self._penColour = region._penColour
2975
2976 self.ClearText()
2977 for line in region._formattedText:
2978 new_line = ShapeTextLine(line.GetX(), line.GetY(), line.GetText())
2979 self._formattedText.append(new_line)
2980 else:
2981 self._regionText = ""
2982 self._font = NormalFont
2983 self._minHeight = 5.0
2984 self._minWidth = 5.0
2985 self._width = 0.0
2986 self._height = 0.0
2987 self._x = 0.0
2988 self._y = 0.0
2989
2990 self._regionProportionX = -1.0
2991 self._regionProportionY = -1.0
2992 self._formatMode = FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
2993 self._regionName = ""
2994 self._textColour = "BLACK"
2995 self._penColour = "BLACK"
2996 self._penStyle = wx.SOLID
2997 self._actualColourObject = wx.TheColourDatabase.Find("BLACK")
2998 self._actualPenObject = None
2999
3000 self._formattedText = []
3001
3002 def ClearText(self):
3003 self._formattedText = []
3004
3005 def SetFont(self, f):
3006 self._font = f
3007
3008 def SetMinSize(self, w, h):
3009 self._minWidth = w
3010 self._minHeight = h
3011
3012 def SetSize(self, w, h):
3013 self._width = w
3014 self._height = h
3015
3016 def SetPosition(self, xp, yp):
3017 self._x = xp
3018 self._y = yp
3019
3020 def SetProportions(self, xp, yp):
3021 self._regionProportionX = xp
3022 self._regionProportionY = yp
3023
3024 def SetFormatMode(self, mode):
3025 self._formatMode = mode
3026
3027 def SetColour(self, col):
3028 self._textColour = col
3029 self._actualColourObject = col
3030
3031 def GetActualColourObject(self):
3032 self._actualColourObject = wx.TheColourDatabase.Find(self.GetColour())
3033 return self._actualColourObject
3034
3035 def SetPenColour(self, col):
3036 self._penColour = col
3037 self._actualPenObject = None
3038
3039 # Returns NULL if the pen is invisible
3040 # (different to pen being transparent; indicates that
3041 # region boundary should not be drawn.)
3042 def GetActualPen(self):
3043 if self._actualPenObject:
3044 return self._actualPenObject
3045
3046 if not self._penColour:
3047 return None
3048 if self._penColour=="Invisible":
3049 return None
3050 self._actualPenObject = wx.Pen(self._penColour, 1, self._penStyle)
3051 return self._actualPenObject
3052
3053 def SetText(self, s):
3054 self._regionText = s
3055
3056 def SetName(self, s):
3057 self._regionName = s
3058
3059 def GetText(self):
3060 return self._regionText
3061
3062 def GetFont(self):
3063 return self._font
3064
3065 def GetMinSize(self):
3066 return self._minWidth, self._minHeight
3067
3068 def GetProportion(self):
3069 return self._regionProportionX, self._regionProportionY
3070
3071 def GetSize(self):
3072 return self._width, self._height
3073
3074 def GetPosition(self):
3075 return self._x, self._y
3076
3077 def GetFormatMode(self):
3078 return self._formatMode
3079
3080 def GetName(self):
3081 return self._regionName
3082
3083 def GetColour(self):
3084 return self._textColour
3085
3086 def GetFormattedText(self):
3087 return self._formattedText
3088
3089 def GetPenColour(self):
3090 return self._penColour
3091
3092 def GetPenStyle(self):
3093 return self._penStyle
3094
3095 def SetPenStyle(self, style):
3096 self._penStyle = style
3097 self._actualPenObject = None
3098
3099 def GetWidth(self):
3100 return self._width
3101
3102 def GetHeight(self):
3103 return self._height
3104
3105
3106
3107 class ControlPoint(RectangleShape):
3108 def __init__(self, theCanvas, object, size, the_xoffset, the_yoffset, the_type):
3109 RectangleShape.__init__(self, size, size)
3110
3111 self._canvas = theCanvas
3112 self._shape = object
3113 self._xoffset = the_xoffset
3114 self._yoffset = the_yoffset
3115 self._type = the_type
3116 self.SetPen(BlackForegroundPen)
3117 self.SetBrush(wx.BLACK_BRUSH)
3118 self._oldCursor = None
3119 self._visible = True
3120 self._eraseObject = True
3121
3122 # Don't even attempt to draw any text - waste of time
3123 def OnDrawContents(self, dc):
3124 pass
3125
3126 def OnDraw(self, dc):
3127 self._xpos = self._shape.GetX() + self._xoffset
3128 self._ypos = self._shape.GetY() + self._yoffset
3129 RectangleShape.OnDraw(self, dc)
3130
3131 def OnErase(self, dc):
3132 RectangleShape.OnErase(self, dc)
3133
3134 # Implement resizing of canvas object
3135 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
3136 self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
3137
3138 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
3139 self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
3140
3141 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
3142 self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
3143
3144 def GetNumberOfAttachments(self):
3145 return 1
3146
3147 def GetAttachmentPosition(self, attachment, nth = 0, no_arcs = 1, line = None):
3148 return self._xpos, self._ypos
3149
3150 def SetEraseObject(self, er):
3151 self._eraseObject = er
3152
3153
3154 class PolygonControlPoint(ControlPoint):
3155 def __init__(self, theCanvas, object, size, vertex, the_xoffset, the_yoffset):
3156 ControlPoint.__init__(self, theCanvas, object, size, the_xoffset, the_yoffset, 0)
3157 self._polygonVertex = vertex
3158 self._originalDistance = 0.0
3159 self._newSize = wx.RealPoint()
3160 self._originalSize = wx.RealPoint()
3161
3162 def GetNewSize(self):
3163 return self._newSize
3164
3165 # Calculate what new size would be, at end of resize
3166 def CalculateNewSize(self, x, y):
3167 bound_x, bound_y = self.GetShape().GetBoundingBoxMax()
3168 dist = math.sqrt((x - self._shape.GetX()) * (x - self._shape.GetX()) + (y - self._shape.GetY()) * (y - self._shape.GetY()))
3169
3170 self._newSize[0] = dist / self._originalDistance * self._originalSize[0]
3171 self._newSize[1] = dist / self._originalDistance * self._originalSize[1]
3172
3173 # Implement resizing polygon or moving the vertex
3174 def OnDragLeft(self, draw, x, y, keys = 0, attachment = 0):
3175 self._shape.GetEventHandler().OnSizingDragLeft(self, draw, x, y, keys, attachment)
3176
3177 def OnBeginDragLeft(self, x, y, keys = 0, attachment = 0):
3178 self._shape.GetEventHandler().OnSizingBeginDragLeft(self, x, y, keys, attachment)
3179
3180 def OnEndDragLeft(self, x, y, keys = 0, attachment = 0):
3181 self._shape.GetEventHandler().OnSizingEndDragLeft(self, x, y, keys, attachment)
3182
3183 from _canvas import *
3184 from _lines import *
3185 from _composit import *