1 # -*- coding: iso-8859-1 -*- 
   2 #---------------------------------------------------------------------------- 
   4 # Purpose:      The basic OGL shapes 
   6 # Author:       Pierre Hjälm (from C++ original by Julian Smart) 
  10 # Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart 
  11 # Licence:      wxWindows license 
  12 #---------------------------------------------------------------------------- 
  17 from _oglmisc 
import * 
  23     global WhiteBackgroundPen
, WhiteBackgroundBrush
, TransparentPen
 
  24     global BlackForegroundPen
, NormalFont
 
  26     WhiteBackgroundPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
) 
  27     WhiteBackgroundBrush 
= wx
.Brush(wx
.WHITE
, wx
.SOLID
) 
  29     TransparentPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
) 
  30     BlackForegroundPen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
  32     NormalFont 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
  39 class ShapeTextLine(object): 
  40     def __init__(self
, the_x
, the_y
, the_line
): 
  57     def SetText(self
, text
): 
  65 class ShapeEvtHandler(object): 
  66     def __init__(self
, prev 
= None, shape 
= None): 
  67         self
._previousHandler 
= prev
 
  68         self
._handlerShape 
= shape
 
  73     def SetShape(self
, sh
): 
  74         self
._handlerShape 
= sh
 
  77         return self
._handlerShape
 
  79     def SetPreviousHandler(self
, handler
): 
  80         self
._previousHandler 
= handler
 
  82     def GetPreviousHandler(self
): 
  83         return self
._previousHandler
 
  86         if self
!=self
.GetShape(): 
  90         if self
._previousHandler
: 
  91             self
._previousHandler
.OnDraw(dc
) 
  93     def OnMoveLinks(self
, dc
): 
  94         if self
._previousHandler
: 
  95             self
._previousHandler
.OnMoveLinks(dc
) 
  97     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
  98         if self
._previousHandler
: 
  99             self
._previousHandler
.OnMoveLink(dc
, moveControlPoints
) 
 101     def OnDrawContents(self
, dc
): 
 102         if self
._previousHandler
: 
 103             self
._previousHandler
.OnDrawContents(dc
) 
 105     def OnDrawBranches(self
, dc
, erase 
= False): 
 106         if self
._previousHandler
: 
 107             self
._previousHandler
.OnDrawBranches(dc
, erase 
= erase
) 
 109     def OnSize(self
, x
, y
): 
 110         if self
._previousHandler
: 
 111             self
._previousHandler
.OnSize(x
, y
) 
 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
) 
 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
) 
 125     def OnErase(self
, dc
): 
 126         if self
._previousHandler
: 
 127             self
._previousHandler
.OnErase(dc
) 
 129     def OnEraseContents(self
, dc
): 
 130         if self
._previousHandler
: 
 131             self
._previousHandler
.OnEraseContents(dc
) 
 133     def OnHighlight(self
, dc
): 
 134         if self
._previousHandler
: 
 135             self
._previousHandler
.OnHighlight(dc
) 
 137     def OnLeftClick(self
, x
, y
, keys
, attachment
): 
 138         if self
._previousHandler
: 
 139             self
._previousHandler
.OnLeftClick(x
, y
, keys
, attachment
) 
 141     def OnLeftDoubleClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 142         if self
._previousHandler
: 
 143             self
._previousHandler
.OnLeftDoubleClick(x
, y
, keys
, attachment
) 
 145     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 146         if self
._previousHandler
: 
 147             self
._previousHandler
.OnRightClick(x
, y
, keys
, attachment
) 
 149     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 150         if self
._previousHandler
: 
 151             self
._previousHandler
.OnDragLeft(draw
, x
, y
, keys
, attachment
) 
 153     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 154         if self
._previousHandler
: 
 155             self
._previousHandler
.OnBeginDragLeft(x
, y
, keys
, attachment
) 
 157     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 158         if self
._previousHandler
: 
 159             self
._previousHandler
.OnEndDragLeft(x
, y
, keys
, attachment
) 
 161     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 162         if self
._previousHandler
: 
 163             self
._previousHandler
.OnDragRight(draw
, x
, y
, keys
, attachment
) 
 165     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 166         if self
._previousHandler
: 
 167             self
._previousHandler
.OnBeginDragRight(x
, y
, keys
, attachment
) 
 169     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 170         if self
._previousHandler
: 
 171             self
._previousHandler
.OnEndDragRight(x
, y
, keys
, attachment
) 
 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
) 
 179     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 180         if self
._previousHandler
: 
 181             self
._previousHandler
.OnSizingBeginDragLeft(pt
, x
, y
, keys
, attachment
) 
 183     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 184         if self
._previousHandler
: 
 185             self
._previousHandler
.OnSizingEndDragLeft(pt
, x
, y
, keys
, attachment
) 
 187     def OnBeginSize(self
, w
, h
): 
 190     def OnEndSize(self
, w
, h
): 
 193     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 194         if self
._previousHandler
: 
 195             self
._previousHandler
.OnDrawOutline(dc
, x
, y
, w
, h
) 
 197     def OnDrawControlPoints(self
, dc
): 
 198         if self
._previousHandler
: 
 199             self
._previousHandler
.OnDrawControlPoints(dc
) 
 201     def OnEraseControlPoints(self
, dc
): 
 202         if self
._previousHandler
: 
 203             self
._previousHandler
.OnEraseControlPoints(dc
) 
 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
) 
 212 class Shape(ShapeEvtHandler
): 
 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. 
 223     GraphicsInSizeToContents 
= False 
 225     def __init__(self
, canvas 
= None): 
 226         ShapeEvtHandler
.__init
__(self
) 
 228         self
._eventHandler 
= self
 
 231         self
._formatted 
= False 
 232         self
._canvas 
= canvas
 
 235         self
._pen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
 236         self
._brush 
= wx
.WHITE_BRUSH
 
 237         self
._font 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
 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 
 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 
 263         self
._branchNeckLength 
= 10 
 264         self
._branchStemLength 
= 10 
 265         self
._branchSpacing 
= 10 
 266         self
._branchStyle 
= BRANCHING_ATTACHMENT_NORMAL
 
 270         self
._controlPoints 
= [] 
 271         self
._attachmentPoints 
= [] 
 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() 
 279         region
.SetFont(wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
)) 
 280         region
.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
) 
 281         region
.SetColour("BLACK") 
 282         self
._regions
.append(region
) 
 285         return "<%s.%s>" % (self
.__class
__.__module
__, self
.__class
__.__name
__) 
 287     def GetClassName(self
): 
 288         return str(self
.__class
__).split(".")[-1][:-2] 
 292             i 
= self
._parent
.GetChildren().index(self
) 
 293             self
._parent
.GetChildren(i
).remove(self
) 
 297         self
.ClearAttachments() 
 299         self
._handlerShape 
= None 
 302             self
.RemoveFromCanvas(self
._canvas
) 
 304         self
.GetEventHandler().OnDelete() 
 305         self
._eventHandler 
= None 
 308         ShapeEvtHandler
.__del
__(self
) 
 311         """TRUE if the shape may be dragged by the user.""" 
 314     def SetShape(self
, sh
): 
 315         self
._handlerShape 
= sh
 
 318         """Get the internal canvas.""" 
 321     def GetBranchStyle(self
): 
 322         return self
._branchStyle
 
 324     def GetRotation(self
): 
 325         """Return the angle of rotation in radians.""" 
 326         return self
._rotation
 
 328     def SetRotation(self
, rotation
): 
 329         self
._rotation 
= rotation
 
 331     def SetHighlight(self
, hi
, recurse 
= False): 
 332         """Set the highlight for a shape. Shape highlighting is unimplemented.""" 
 333         self
._highlighted 
= hi
 
 335             for shape 
in self
._children
: 
 336                 shape
.SetHighlight(hi
, recurse
) 
 338     def SetSensitivityFilter(self
, sens 
= OP_ALL
, recursive 
= False): 
 339         """Set the shape to be sensitive or insensitive to specific mouse 
 342         sens is a bitlist of the following: 
 348         * OP_ALL (equivalent to a combination of all the above). 
 350         self
._draggable 
= sens 
& OP_DRAG_LEFT
 
 352         self
._sensitivity 
= sens
 
 354             for shape 
in self
._children
: 
 355                 shape
.SetSensitivityFilter(sens
, True) 
 357     def SetDraggable(self
, drag
, recursive 
= False): 
 358         """Set the shape to be draggable or not draggable.""" 
 359         self
._draggable 
= drag
 
 361             self
._sensitivity |
= OP_DRAG_LEFT
 
 362         elif self
._sensitivity 
& OP_DRAG_LEFT
: 
 363             self
._sensitivity 
-= OP_DRAG_LEFT
 
 366             for shape 
in self
._children
: 
 367                 shape
.SetDraggable(drag
, True) 
 369     def SetDrawHandles(self
, drawH
): 
 370         """Set the drawHandles flag for this shape and all descendants. 
 371         If drawH is TRUE (the default), any handles (control points) will 
 372         be drawn. Otherwise, the handles will not be drawn. 
 374         self
._drawHandles 
= drawH
 
 375         for shape 
in self
._children
: 
 376             shape
.SetDrawHandles(drawH
) 
 378     def SetShadowMode(self
, mode
, redraw 
= False): 
 379         """Set the shadow mode (whether a shadow is drawn or not). 
 380         mode can be one of the following: 
 383           No shadow (the default).  
 385           Shadow on the left side.  
 387           Shadow on the right side. 
 389         if redraw 
and self
.GetCanvas(): 
 390             dc 
= wx
.ClientDC(self
.GetCanvas()) 
 391             self
.GetCanvas().PrepareDC(dc
) 
 393             self
._shadowMode 
= mode
 
 396             self
._shadowMode 
= mode
 
 398     def SetCanvas(self
, theCanvas
): 
 399         """Identical to Shape.Attach.""" 
 400         self
._canvas 
= theCanvas
 
 401         for shape 
in self
._children
: 
 402             shape
.SetCanvas(theCanvas
) 
 404     def AddToCanvas(self
, theCanvas
, addAfter 
= None): 
 405         """Add the shape to the canvas's shape list. 
 406         If addAfter is non-NULL, will add the shape after this one. 
 408         theCanvas
.AddShape(self
, addAfter
) 
 411         for object in self
._children
: 
 412             object.AddToCanvas(theCanvas
, lastImage
) 
 415     def InsertInCanvas(self
, theCanvas
): 
 416         """Insert the shape at the front of the shape list of canvas.""" 
 417         theCanvas
.InsertShape(self
) 
 420         for object in self
._children
: 
 421             object.AddToCanvas(theCanvas
, lastImage
) 
 424     def RemoveFromCanvas(self
, theCanvas
): 
 425         """Remove the shape from the canvas.""" 
 430         theCanvas
.RemoveShape(self
) 
 431         for object in self
._children
: 
 432             object.RemoveFromCanvas(theCanvas
) 
 434     def ClearAttachments(self
): 
 435         """Clear internal custom attachment point shapes (of class 
 438         self
._attachmentPoints 
= [] 
 440     def ClearText(self
, regionId 
= 0): 
 441         """Clear the text from the specified text region.""" 
 444         if regionId 
< len(self
._regions
): 
 445             self
._regions
[regionId
].ClearText() 
 447     def ClearRegions(self
): 
 448         """Clear the ShapeRegions from the shape.""" 
 451     def AddRegion(self
, region
): 
 452         """Add a region to the shape.""" 
 453         self
._regions
.append(region
) 
 455     def SetDefaultRegionSize(self
): 
 456         """Set the default region to be consistent with the shape size.""" 
 457         if not self
._regions
: 
 459         w
, h 
= self
.GetBoundingBoxMax() 
 460         self
._regions
[0].SetSize(w
, h
) 
 462     def HitTest(self
, x
, y
): 
 463         """Given a point on a canvas, returns TRUE if the point was on the 
 464         shape, and returns the nearest attachment point and distance from 
 465         the given point and target. 
 467         width
, height 
= self
.GetBoundingBoxMax() 
 473         width 
+= 4 # Allowance for inaccurate mousing 
 476         left 
= self
._xpos 
- width 
/ 2.0 
 477         top 
= self
._ypos 
- height 
/ 2.0 
 478         right 
= self
._xpos 
+ width 
/ 2.0 
 479         bottom 
= self
._ypos 
+ height 
/ 2.0 
 481         nearest_attachment 
= 0 
 483         # If within the bounding box, check the attachment points 
 485         if x 
>= left 
and x 
<= right 
and y 
>= top 
and y 
<= bottom
: 
 486             n 
= self
.GetNumberOfAttachments() 
 489             # GetAttachmentPosition[Edge] takes a logical attachment position, 
 490             # i.e. if it's rotated through 90%, position 0 is East-facing. 
 493                 e 
= self
.GetAttachmentPositionEdge(i
) 
 496                     l 
= math
.sqrt(((xp 
- x
) * (xp 
- x
)) + (yp 
- y
) * (yp 
- y
)) 
 499                         nearest_attachment 
= i
 
 501             return nearest_attachment
, nearest
 
 504     # Format a text string according to the region size, adding 
 505     # strings with positions to region text list 
 507     def FormatText(self
, dc
, s
, i 
= 0): 
 508         """Reformat the given text region; defaults to formatting the 
 513         if not self
._regions
: 
 516         if i 
> len(self
._regions
): 
 519         region 
= self
._regions
[i
] 
 520         region
._regionText 
= s
 
 521         dc
.SetFont(region
.GetFont()) 
 523         w
, h 
= region
.GetSize() 
 525         stringList 
= FormatText(dc
, s
, (w 
- 2 * self
._textMarginX
), (h 
- 2 * self
._textMarginY
), region
.GetFormatMode()) 
 527             line 
= ShapeTextLine(0.0, 0.0, s
) 
 528             region
.GetFormattedText().append(line
) 
 532         # Don't try to resize an object with more than one image (this 
 533         # case should be dealt with by overriden handlers) 
 534         if (region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
) and \
 
 535            len(region
.GetFormattedText()) and \
 
 536            len(self
._regions
) == 1 and \
 
 537            not Shape
.GraphicsInSizeToContents
: 
 539             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText()) 
 540             if actualW 
+ 2 * self
._textMarginX 
!= w 
or actualH 
+ 2 * self
._textMarginY 
!= h
: 
 541                 # If we are a descendant of a composite, must make sure 
 542                 # the composite gets resized properly 
 544                 topAncestor 
= self
.GetTopAncestor() 
 545                 if topAncestor 
!= self
: 
 546                     Shape
.GraphicsInSizeToContents 
= True 
 548                     composite 
= topAncestor
 
 550                     self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 551                     self
.Move(dc
, self
._xpos
, self
._ypos
) 
 552                     composite
.CalculateSize() 
 553                     if composite
.Selected(): 
 554                         composite
.DeleteControlPoints(dc
) 
 555                         composite
.MakeControlPoints() 
 556                         composite
.MakeMandatoryControlPoints() 
 557                     # Where infinite recursion might happen if we didn't stop it 
 559                     Shape
.GraphicsInSizeToContents 
= False 
 563                 self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 564                 self
.Move(dc
, self
._xpos
, self
._ypos
) 
 565                 self
.EraseContents(dc
) 
 566         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW 
- 2 * self
._textMarginX
, actualH 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 567         self
._formatted 
= True 
 569     def Recentre(self
, dc
): 
 570         """Do recentring (or other formatting) for all the text regions 
 573         w
, h 
= self
.GetBoundingBoxMin() 
 574         for region 
in self
._regions
: 
 575             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w 
- 2 * self
._textMarginX
, h 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 577     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
 578         """Get the point at which the line from (x1, y1) to (x2, y2) hits 
 579         the shape. Returns False if the line doesn't hit the perimeter. 
 583     def SetPen(self
, the_pen
): 
 584         """Set the pen for drawing the shape's outline.""" 
 587     def SetBrush(self
, the_brush
): 
 588         """Set the brush for filling the shape's shape.""" 
 589         self
._brush 
= the_brush
 
 591     # Get the top - most (non-division) ancestor, or self 
 592     def GetTopAncestor(self
): 
 593         """Return the top-most ancestor of this shape (the root of 
 596         if not self
.GetParent(): 
 599         if isinstance(self
.GetParent(), DivisionShape
): 
 601         return self
.GetParent().GetTopAncestor() 
 604     def SetFont(self
, the_font
, regionId 
= 0): 
 605         """Set the font for the specified text region.""" 
 606         self
._font 
= the_font
 
 607         if regionId 
< len(self
._regions
): 
 608             self
._regions
[regionId
].SetFont(the_font
) 
 610     def GetFont(self
, regionId 
= 0): 
 611         """Get the font for the specified text region.""" 
 612         if regionId 
>= len(self
._regions
): 
 614         return self
._regions
[regionId
].GetFont() 
 616     def SetFormatMode(self
, mode
, regionId 
= 0): 
 617         """Set the format mode of the default text region. The argument 
 618         can be a bit list of the following: 
 627         if regionId 
< len(self
._regions
): 
 628             self
._regions
[regionId
].SetFormatMode(mode
) 
 630     def GetFormatMode(self
, regionId 
= 0): 
 631         if regionId 
>= len(self
._regions
): 
 633         return self
._regions
[regionId
].GetFormatMode() 
 635     def SetTextColour(self
, the_colour
, regionId 
= 0): 
 636         """Set the colour for the specified text region.""" 
 637         self
._textColour 
= wx
.TheColourDatabase
.Find(the_colour
) 
 638         self
._textColourName 
= the_colour
 
 640         if regionId 
< len(self
._regions
): 
 641             self
._regions
[regionId
].SetColour(the_colour
) 
 643     def GetTextColour(self
, regionId 
= 0): 
 644         """Get the colour for the specified text region.""" 
 645         if regionId 
>= len(self
._regions
): 
 647         return self
._regions
[regionId
].GetTextColour() 
 649     def SetRegionName(self
, name
, regionId 
= 0): 
 650         """Set the name for this region. 
 651         The name for a region is unique within the scope of the whole 
 652         composite, whereas a region id is unique only for a single image. 
 654         if regionId 
< len(self
._regions
): 
 655             self
._regions
[regionId
].SetName(name
) 
 657     def GetRegionName(self
, regionId 
= 0): 
 658         """Get the region's name. 
 659         A region's name can be used to uniquely determine a region within 
 660         an entire composite image hierarchy. See also Shape.SetRegionName. 
 662         if regionId 
>= len(self
._regions
): 
 664         return self
._regions
[regionId
].GetName() 
 666     def GetRegionId(self
, name
): 
 667         """Get the region's identifier by name. 
 668         This is not unique for within an entire composite, but is unique 
 671         for i
, r 
in enumerate(self
._regions
): 
 672             if r
.GetName() == name
: 
 676     # Name all _regions in all subimages recursively 
 677     def NameRegions(self
, parentName
=""): 
 678         """Make unique names for all the regions in a shape or composite shape.""" 
 679         n 
= self
.GetNumberOfTextRegions() 
 682                 buff 
= parentName
+"."+str(i
) 
 685             self
.SetRegionName(buff
, i
) 
 687         for j
, child 
in enumerate(self
._children
): 
 689                 buff 
= parentName
+"."+str(j
) 
 692             child
.NameRegions(buff
) 
 694     # Get a region by name, possibly looking recursively into composites 
 695     def FindRegion(self
, name
): 
 696         """Find the actual image ('this' if non-composite) and region id 
 697         for the given region name. 
 699         id = self
.GetRegionId(name
) 
 703         for child 
in self
._children
: 
 704             actualImage
, regionId 
= child
.FindRegion(name
) 
 706                 return actualImage
, regionId
 
 710     # Finds all region names for this image (composite or simple). 
 711     def FindRegionNames(self
): 
 712         """Get a list of all region names for this image (composite or simple).""" 
 714         n 
= self
.GetNumberOfTextRegions() 
 716             list.append(self
.GetRegionName(i
)) 
 718         for child 
in self
._children
: 
 719             list += child
.FindRegionNames() 
 723     def AssignNewIds(self
): 
 724         """Assign new ids to this image and its children.""" 
 725         self
._id 
= wx
.NewId() 
 726         for child 
in self
._children
: 
 729     def OnDraw(self
, dc
): 
 732     def OnMoveLinks(self
, dc
): 
 733         # Want to set the ends of all attached links 
 734         # to point to / from this object 
 736         for line 
in self
._lines
: 
 737             line
.GetEventHandler().OnMoveLink(dc
) 
 739     def OnDrawContents(self
, dc
): 
 740         if not self
._regions
: 
 743         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
 748         region 
= self
._regions
[0] 
 750             dc
.SetFont(region
.GetFont()) 
 752         dc
.SetTextForeground(region
.GetActualColourObject()) 
 753         dc
.SetBackgroundMode(wx
.TRANSPARENT
) 
 754         if not self
._formatted
: 
 755             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 756             self
._formatted 
= True 
 758         if not self
.GetDisableLabel(): 
 759             DrawFormattedText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 762     def DrawContents(self
, dc
): 
 763         """Draw the internal graphic of the shape (such as text). 
 765         Do not override this function: override OnDrawContents, which 
 766         is called by this function. 
 768         self
.GetEventHandler().OnDrawContents(dc
) 
 770     def OnSize(self
, x
, y
): 
 773     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 776     def OnErase(self
, dc
): 
 777         if not self
._visible
: 
 781         for line 
in self
._lines
: 
 782             line
.GetEventHandler().OnErase(dc
) 
 784         self
.GetEventHandler().OnEraseContents(dc
) 
 786     def OnEraseContents(self
, dc
): 
 787         if not self
._visible
: 
 790         xp
, yp 
= self
.GetX(), self
.GetY() 
 791         minX
, minY 
= self
.GetBoundingBoxMin() 
 792         maxX
, maxY 
= self
.GetBoundingBoxMax() 
 794         topLeftX 
= xp 
- maxX 
/ 2.0 - 2 
 795         topLeftY 
= yp 
- maxY 
/ 2.0 - 2 
 799             penWidth 
= self
._pen
.GetWidth() 
 801         dc
.SetPen(self
.GetBackgroundPen()) 
 802         dc
.SetBrush(self
.GetBackgroundBrush()) 
 804         dc
.DrawRectangle(topLeftX 
- penWidth
, topLeftY 
- penWidth
, maxX 
+ penWidth 
* 2 + 4, maxY 
+ penWidth 
* 2 + 4) 
 806     def EraseLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 807         """Erase links attached to this shape, but do not repair damage 
 808         caused to other shapes. 
 810         if not self
._visible
: 
 813         for line 
in self
._lines
: 
 814             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 815                 line
.GetEventHandler().OnErase(dc
) 
 818             for child 
in self
._children
: 
 819                 child
.EraseLinks(dc
, attachment
, recurse
) 
 821     def DrawLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 822         """Draws any lines linked to this shape.""" 
 823         if not self
._visible
: 
 826         for line 
in self
._lines
: 
 827             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 831             for child 
in self
._children
: 
 832                 child
.DrawLinks(dc
, attachment
, recurse
) 
 834     #  Returns TRUE if pt1 <= pt2 in the sense that one point comes before 
 835     #  another on an edge of the shape. 
 836     # attachmentPoint is the attachment point (= side) in question. 
 838     # This is the default, rectangular implementation. 
 839     def AttachmentSortTest(self
, attachmentPoint
, pt1
, pt2
): 
 840         """Return TRUE if pt1 is less than or equal to pt2, in the sense 
 841         that one point comes before another on an edge of the shape. 
 843         attachment is the attachment point (side) in question. 
 845         This function is used in Shape.MoveLineToNewAttachment to determine 
 846         the new line ordering. 
 848         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachmentPoint
) 
 849         if physicalAttachment 
in [0, 2]: 
 850             return pt1
[0] <= pt2
[0] 
 851         elif physicalAttachment 
in [1, 3]: 
 852             return pt1
[1] <= pt2
[1] 
 856     def MoveLineToNewAttachment(self
, dc
, to_move
, x
, y
): 
 857         """Move the given line (which must already be attached to the shape) 
 858         to a different attachment point on the shape, or a different order 
 859         on the same attachment. 
 861         Calls Shape.AttachmentSortTest and then 
 862         ShapeEvtHandler.OnChangeAttachment. 
 864         if self
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 867         # Is (x, y) on this object? If so, find the new attachment point 
 868         # the user has moved the point to 
 869         hit 
= self
.HitTest(x
, y
) 
 873         newAttachment
, distance 
= hit
 
 877         if to_move
.GetTo() == self
: 
 878             oldAttachment 
= to_move
.GetAttachmentTo() 
 880             oldAttachment 
= to_move
.GetAttachmentFrom() 
 882         # The links in a new ordering 
 883         # First, add all links to the new list 
 884         newOrdering 
= self
._lines
[:] 
 886         # Delete the line object from the list of links; we're going to move 
 887         # it to another position in the list 
 888         del newOrdering
[newOrdering
.index(to_move
)] 
 895         for line 
in newOrdering
: 
 896             if line
.GetTo() == self 
and oldAttachment 
== line
.GetAttachmentTo() or \
 
 897                line
.GetFrom() == self 
and oldAttachment 
== line
.GetAttachmentFrom(): 
 898                 startX
, startY
, endX
, endY 
= line
.GetEnds() 
 899                 if line
.GetTo() == self
: 
 906                 thisPoint 
= wx
.RealPoint(xp
, yp
) 
 907                 lastPoint 
= wx
.RealPoint(old_x
, old_y
) 
 908                 newPoint 
= wx
.RealPoint(x
, y
) 
 910                 if self
.AttachmentSortTest(newAttachment
, newPoint
, thisPoint
) and self
.AttachmentSortTest(newAttachment
, lastPoint
, newPoint
): 
 912                     newOrdering
.insert(newOrdering
.index(line
), to_move
) 
 920             newOrdering
.append(to_move
) 
 922         self
.GetEventHandler().OnChangeAttachment(newAttachment
, to_move
, newOrdering
) 
 925     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 926         if line
.GetTo() == self
: 
 927             line
.SetAttachmentTo(attachment
) 
 929             line
.SetAttachmentFrom(attachment
) 
 931         self
.ApplyAttachmentOrdering(ordering
) 
 933         dc 
= wx
.ClientDC(self
.GetCanvas()) 
 934         self
.GetCanvas().PrepareDC(dc
) 
 937         if not self
.GetCanvas().GetQuickEditMode(): 
 938             self
.GetCanvas().Redraw(dc
) 
 940     # Reorders the lines according to the given list 
 941     def ApplyAttachmentOrdering(self
, linesToSort
): 
 942         """Apply the line ordering in linesToSort to the shape, to reorder 
 943         the way lines are attached. 
 945         linesStore 
= self
._lines
[:] 
 949         for line 
in linesToSort
: 
 950             if line 
in linesStore
: 
 951                 del linesStore
[linesStore
.index(line
)] 
 952                 self
._lines
.append(line
) 
 954         # Now add any lines that haven't been listed in linesToSort 
 955         self
._lines 
+= linesStore
 
 957     def SortLines(self
, attachment
, linesToSort
): 
 958         """ Reorder the lines coming into the node image at this attachment 
 959         position, in the order in which they appear in linesToSort. 
 961         Any remaining lines not in the list will be added to the end. 
 963         # This is a temporary store of all the lines at this attachment 
 964         # point. We'll tick them off as we've processed them. 
 965         linesAtThisAttachment 
= [] 
 967         for line 
in self
._lines
[:]: 
 968             if line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or \
 
 969                line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
: 
 970                 linesAtThisAttachment
.append(line
) 
 971                 del self
._lines
[self
._lines
.index(line
)] 
 973         for line 
in linesToSort
: 
 974             if line 
in linesAtThisAttachment
: 
 976                 del linesAtThisAttachment
[linesAtThisAttachment
.index(line
)] 
 977                 self
._lines
.append(line
) 
 979         # Now add any lines that haven't been listed in linesToSort 
 980         self
._lines 
+= linesAtThisAttachment
 
 982     def OnHighlight(self
, dc
): 
 985     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 986         if self
._sensitivity 
& OP_CLICK_LEFT 
!= OP_CLICK_LEFT
: 
 988                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 989                 self
._parent
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
 991     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 992         if self
._sensitivity 
& OP_CLICK_RIGHT 
!= OP_CLICK_RIGHT
: 
 993             attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 994             self
._parent
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
 996     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 997         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
 999                 hit 
= self
._parent
.HitTest(x
, y
) 
1001                     attachment
, dist 
= hit
 
1002                 self
._parent
.GetEventHandler().OnDragLeft(draw
, x
, y
, keys
, attachment
) 
1005         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1006         self
.GetCanvas().PrepareDC(dc
) 
1007         dc
.SetLogicalFunction(OGLRBLF
) 
1009         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1010         dc
.SetPen(dottedPen
) 
1011         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1013         xx 
= x 
+ DragOffsetX
 
1014         yy 
= y 
+ DragOffsetY
 
1016         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1017         w
, h 
= self
.GetBoundingBoxMax() 
1018         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1020     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1021         global DragOffsetX
, DragOffsetY
 
1023         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1025                 hit 
= self
._parent
.HitTest(x
, y
) 
1027                     attachment
, dist 
= hit
 
1028                 self
._parent
.GetEventHandler().OnBeginDragLeft(x
, y
, keys
, attachment
) 
1031         DragOffsetX 
= self
._xpos 
- x
 
1032         DragOffsetY 
= self
._ypos 
- y
 
1034         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1035         self
.GetCanvas().PrepareDC(dc
) 
1037         # New policy: don't erase shape until end of drag. 
1039         xx 
= x 
+ DragOffsetX
 
1040         yy 
= y 
+ DragOffsetY
 
1041         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1042         dc
.SetLogicalFunction(OGLRBLF
) 
1044         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1045         dc
.SetPen(dottedPen
) 
1046         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1048         w
, h 
= self
.GetBoundingBoxMax() 
1049         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1050         self
._canvas
.CaptureMouse() 
1052     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1053         if self
._canvas
.HasCapture(): 
1054             self
._canvas
.ReleaseMouse() 
1055         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1057                 hit 
= self
._parent
.HitTest(x
, y
) 
1059                     attachment
, dist 
= hit
 
1060                 self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, attachment
) 
1063         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1064         self
.GetCanvas().PrepareDC(dc
) 
1066         dc
.SetLogicalFunction(wx
.COPY
) 
1067         xx 
= x 
+ DragOffsetX
 
1068         yy 
= y 
+ DragOffsetY
 
1069         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1071         # New policy: erase shape at end of drag. 
1074         self
.Move(dc
, xx
, yy
) 
1075         if self
._canvas 
and not self
._canvas
.GetQuickEditMode(): 
1076             self
._canvas
.Redraw(dc
) 
1078     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1079         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1081                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1082                 self
._parent
.GetEventHandler().OnDragRight(draw
, x
, y
, keys
, attachment
) 
1085     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1086         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1088                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1089                 self
._parent
.GetEventHandler().OnBeginDragRight(x
, y
, keys
, attachment
) 
1092     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1093         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1095                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1096                 self
._parent
.GetEventHandler().OnEndDragRight(x
, y
, keys
, attachment
) 
1099     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
1100         points 
= [[x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1101                 [x 
+ w 
/ 2.0, y 
- h 
/ 2.0], 
1102                 [x 
+ w 
/ 2.0, y 
+ h 
/ 2.0], 
1103                 [x 
- w 
/ 2.0, y 
+ h 
/ 2.0], 
1104                 [x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1107         dc
.DrawLines(points
) 
1109     def Attach(self
, can
): 
1110         """Set the shape's internal canvas pointer to point to the given canvas.""" 
1114         """Disassociates the shape from its canvas.""" 
1117     def Move(self
, dc
, x
, y
, display 
= True): 
1118         """Move the shape to the given position. 
1119         Redraw if display is TRUE. 
1124         if not self
.GetEventHandler().OnMovePre(dc
, x
, y
, old_x
, old_y
, display
): 
1127         self
._xpos
, self
._ypos 
= x
, y
 
1129         self
.ResetControlPoints() 
1136         self
.GetEventHandler().OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
1138     def MoveLinks(self
, dc
): 
1139         """Redraw all the lines attached to the shape.""" 
1140         self
.GetEventHandler().OnMoveLinks(dc
) 
1143         """Draw the whole shape and any lines attached to it. 
1145         Do not override this function: override OnDraw, which is called 
1149             self
.GetEventHandler().OnDraw(dc
) 
1150             self
.GetEventHandler().OnDrawContents(dc
) 
1151             self
.GetEventHandler().OnDrawControlPoints(dc
) 
1152             self
.GetEventHandler().OnDrawBranches(dc
) 
1155         """Flash the shape.""" 
1156         if self
.GetCanvas(): 
1157             dc 
= wx
.ClientDC(self
.GetCanvas()) 
1158             self
.GetCanvas
.PrepareDC(dc
) 
1160             dc
.SetLogicalFunction(OGLRBLF
) 
1162             dc
.SetLogicalFunction(wx
.COPY
) 
1165     def Show(self
, show
): 
1166         """Set a flag indicating whether the shape should be drawn.""" 
1167         self
._visible 
= show
 
1168         for child 
in self
._children
: 
1171     def Erase(self
, dc
): 
1173         Does not repair damage caused to other shapes. 
1175         self
.GetEventHandler().OnErase(dc
) 
1176         self
.GetEventHandler().OnEraseControlPoints(dc
) 
1177         self
.GetEventHandler().OnDrawBranches(dc
, erase 
= True) 
1179     def EraseContents(self
, dc
): 
1180         """Erase the shape contents, that is, the area within the shape's 
1181         minimum bounding box. 
1183         self
.GetEventHandler().OnEraseContents(dc
) 
1185     def AddText(self
, string
): 
1186         """Add a line of text to the shape's default text region.""" 
1187         if not self
._regions
: 
1190         region 
= self
._regions
[0] 
1192         new_line 
= ShapeTextLine(0, 0, string
) 
1193         text 
= region
.GetFormattedText() 
1194         text
.append(new_line
) 
1196         self
._formatted 
= False 
1198     def SetSize(self
, x
, y
, recursive 
= True): 
1199         """Set the shape's size.""" 
1200         self
.SetAttachmentSize(x
, y
) 
1201         self
.SetDefaultRegionSize() 
1203     def SetAttachmentSize(self
, w
, h
): 
1204         width
, height 
= self
.GetBoundingBoxMin() 
1208             scaleX 
= float(w
) / width
 
1212             scaleY 
= float(h
) / height
 
1214         for point 
in self
._attachmentPoints
: 
1215             point
._x 
= point
._x 
* scaleX
 
1216             point
._y 
= point
._y 
* scaleY
 
1218     # Add line FROM this object 
1219     def AddLine(self
, line
, other
, attachFrom 
= 0, attachTo 
= 0, positionFrom 
= -1, positionTo 
= -1): 
1220         """Add a line between this shape and the given other shape, at the 
1221         specified attachment points. 
1223         The position in the list of lines at each end can also be specified, 
1224         so that the line will be drawn at a particular point on its attachment 
1227         if positionFrom 
== -1: 
1228             if not line 
in self
._lines
: 
1229                 self
._lines
.append(line
) 
1231             # Don't preserve old ordering if we have new ordering instructions 
1233                 self
._lines
.remove(line
) 
1236             if positionFrom 
< len(self
._lines
): 
1237                 self
._lines
.insert(positionFrom
, line
) 
1239                 self
._lines
.append(line
) 
1241         if positionTo 
== -1: 
1242             if not other 
in other
._lines
: 
1243                 other
._lines
.append(line
) 
1245             # Don't preserve old ordering if we have new ordering instructions 
1247                 other
._lines
.remove(line
) 
1250             if positionTo 
< len(other
._lines
): 
1251                 other
._lines
.insert(positionTo
, line
) 
1253                 other
._lines
.append(line
) 
1257         line
.SetAttachments(attachFrom
, attachTo
) 
1259         dc 
= wx
.ClientDC(self
._canvas
) 
1260         self
._canvas
.PrepareDC(dc
) 
1263     def RemoveLine(self
, line
): 
1264         """Remove the given line from the shape's list of attached lines.""" 
1265         if line
.GetFrom() == self
: 
1266             line
.GetTo()._lines
.remove(line
) 
1268             line
.GetFrom()._lines
.remove(line
) 
1270         self
._lines
.remove(line
) 
1272     # Default - make 6 control points 
1273     def MakeControlPoints(self
): 
1274         """Make a list of control points (draggable handles) appropriate to 
1277         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1278         minX
, minY 
= self
.GetBoundingBoxMin() 
1280         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1281         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1283         # Offsets from main object 
1284         top 
= -heightMin 
/ 2.0 
1285         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1286         left 
= -widthMin 
/ 2.0 
1287         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1289         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, top
, CONTROL_POINT_DIAGONAL
) 
1290         self
._canvas
.AddShape(control
) 
1291         self
._controlPoints
.append(control
) 
1293         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, top
, CONTROL_POINT_VERTICAL
) 
1294         self
._canvas
.AddShape(control
) 
1295         self
._controlPoints
.append(control
) 
1297         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, top
, CONTROL_POINT_DIAGONAL
) 
1298         self
._canvas
.AddShape(control
) 
1299         self
._controlPoints
.append(control
) 
1301         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, 0, CONTROL_POINT_HORIZONTAL
) 
1302         self
._canvas
.AddShape(control
) 
1303         self
._controlPoints
.append(control
) 
1305         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, bottom
, CONTROL_POINT_DIAGONAL
) 
1306         self
._canvas
.AddShape(control
) 
1307         self
._controlPoints
.append(control
) 
1309         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, bottom
, CONTROL_POINT_VERTICAL
) 
1310         self
._canvas
.AddShape(control
) 
1311         self
._controlPoints
.append(control
) 
1313         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, bottom
, CONTROL_POINT_DIAGONAL
) 
1314         self
._canvas
.AddShape(control
) 
1315         self
._controlPoints
.append(control
) 
1317         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, 0, CONTROL_POINT_HORIZONTAL
) 
1318         self
._canvas
.AddShape(control
) 
1319         self
._controlPoints
.append(control
) 
1321     def MakeMandatoryControlPoints(self
): 
1322         """Make the mandatory control points. 
1324         For example, the control point on a dividing line should appear even 
1325         if the divided rectangle shape's handles should not appear (because 
1326         it is the child of a composite, and children are not resizable). 
1328         for child 
in self
._children
: 
1329             child
.MakeMandatoryControlPoints() 
1331     def ResetMandatoryControlPoints(self
): 
1332         """Reset the mandatory control points.""" 
1333         for child 
in self
._children
: 
1334             child
.ResetMandatoryControlPoints() 
1336     def ResetControlPoints(self
): 
1337         """Reset the positions of the control points (for instance when the 
1338         shape's shape has changed). 
1340         self
.ResetMandatoryControlPoints() 
1342         if len(self
._controlPoints
) == 0: 
1345         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1346         minX
, minY 
= self
.GetBoundingBoxMin() 
1348         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1349         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1351         # Offsets from main object 
1352         top 
= -heightMin 
/ 2.0 
1353         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1354         left 
= -widthMin 
/ 2.0 
1355         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1357         self
._controlPoints
[0]._xoffset 
= left
 
1358         self
._controlPoints
[0]._yoffset 
= top
 
1360         self
._controlPoints
[1]._xoffset 
= 0 
1361         self
._controlPoints
[1]._yoffset 
= top
 
1363         self
._controlPoints
[2]._xoffset 
= right
 
1364         self
._controlPoints
[2]._yoffset 
= top
 
1366         self
._controlPoints
[3]._xoffset 
= right
 
1367         self
._controlPoints
[3]._yoffset 
= 0 
1369         self
._controlPoints
[4]._xoffset 
= right
 
1370         self
._controlPoints
[4]._yoffset 
= bottom
 
1372         self
._controlPoints
[5]._xoffset 
= 0 
1373         self
._controlPoints
[5]._yoffset 
= bottom
 
1375         self
._controlPoints
[6]._xoffset 
= left
 
1376         self
._controlPoints
[6]._yoffset 
= bottom
 
1378         self
._controlPoints
[7]._xoffset 
= left
 
1379         self
._controlPoints
[7]._yoffset 
= 0 
1381     def DeleteControlPoints(self
, dc 
= None): 
1382         """Delete the control points (or handles) for the shape. 
1384         Does not redraw the shape. 
1386         for control 
in self
._controlPoints
[:]: 
1388                 control
.GetEventHandler().OnErase(dc
) 
1390             self
._controlPoints
.remove(control
) 
1391         self
._controlPoints 
= [] 
1393         # Children of divisions are contained objects, 
1395         if not isinstance(self
, DivisionShape
): 
1396             for child 
in self
._children
: 
1397                 child
.DeleteControlPoints(dc
) 
1399     def OnDrawControlPoints(self
, dc
): 
1400         if not self
._drawHandles
: 
1403         dc
.SetBrush(wx
.BLACK_BRUSH
) 
1404         dc
.SetPen(wx
.BLACK_PEN
) 
1406         for control 
in self
._controlPoints
: 
1409         # Children of divisions are contained objects, 
1411         # This test bypasses the type facility for speed 
1412         # (critical when drawing) 
1414         if not isinstance(self
, DivisionShape
): 
1415             for child 
in self
._children
: 
1416                 child
.GetEventHandler().OnDrawControlPoints(dc
) 
1418     def OnEraseControlPoints(self
, dc
): 
1419         for control 
in self
._controlPoints
: 
1422         if not isinstance(self
, DivisionShape
): 
1423             for child 
in self
._children
: 
1424                 child
.GetEventHandler().OnEraseControlPoints(dc
) 
1426     def Select(self
, select
, dc 
= None): 
1427         """Select or deselect the given shape, drawing or erasing control points 
1428         (handles) as necessary. 
1430         self
._selected 
= select
 
1432             self
.MakeControlPoints() 
1433             # Children of divisions are contained objects, 
1435             if not isinstance(self
, DivisionShape
): 
1436                 for child 
in self
._children
: 
1437                     child
.MakeMandatoryControlPoints() 
1439                 self
.GetEventHandler().OnDrawControlPoints(dc
) 
1441             self
.DeleteControlPoints(dc
) 
1442             if not isinstance(self
, DivisionShape
): 
1443                 for child 
in self
._children
: 
1444                     child
.DeleteControlPoints(dc
) 
1447         """TRUE if the shape is currently selected.""" 
1448         return self
._selected
 
1450     def AncestorSelected(self
): 
1451         """TRUE if the shape's ancestor is currently selected.""" 
1454         if not self
.GetParent(): 
1456         return self
.GetParent().AncestorSelected() 
1458     def GetNumberOfAttachments(self
): 
1459         """Get the number of attachment points for this shape.""" 
1460         # Should return the MAXIMUM attachment point id here, 
1461         # so higher-level functions can iterate through all attachments, 
1462         # even if they're not contiguous. 
1464         if len(self
._attachmentPoints
) == 0: 
1468             for point 
in self
._attachmentPoints
: 
1469                 if point
._id 
> maxN
: 
1473     def AttachmentIsValid(self
, attachment
): 
1474         """TRUE if attachment is a valid attachment point.""" 
1475         if len(self
._attachmentPoints
) == 0: 
1476             return attachment 
in range(4) 
1478         for point 
in self
._attachmentPoints
: 
1479             if point
._id 
== attachment
: 
1483     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1484         """Get the position at which the given attachment point should be drawn. 
1486         If attachment isn't found among the attachment points of the shape, 
1489         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE
: 
1490             return self
._xpos
, self
._ypos
 
1491         elif self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1492             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, nth
) 
1494         elif self
._attachmentMode 
== ATTACHMENT_MODE_EDGE
: 
1495             if len(self
._attachmentPoints
): 
1496                 for point 
in self
._attachmentPoints
: 
1497                     if point
._id 
== attachment
: 
1498                         return self
._xpos 
+ point
._x
, self
._ypos 
+ point
._y
 
1501                 # Assume is rectangular 
1502                 w
, h 
= self
.GetBoundingBoxMax() 
1503                 top 
= self
._ypos 
+ h 
/ 2.0 
1504                 bottom 
= self
._ypos 
- h 
/ 2.0 
1505                 left 
= self
._xpos 
- w 
/ 2.0 
1506                 right 
= self
._xpos 
+ w 
/ 2.0 
1509                 line 
and line
.IsEnd(self
) 
1511                 physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1514                 if physicalAttachment 
== 0: 
1515                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (right
, bottom
), nth
, no_arcs
, line
) 
1516                 elif physicalAttachment 
== 1: 
1517                     pt 
= self
.CalcSimpleAttachment((right
, bottom
), (right
, top
), nth
, no_arcs
, line
) 
1518                 elif physicalAttachment 
== 2: 
1519                     pt 
= self
.CalcSimpleAttachment((left
, top
), (right
, top
), nth
, no_arcs
, line
) 
1520                 elif physicalAttachment 
== 3: 
1521                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (left
, top
), nth
, no_arcs
, line
) 
1527     def GetBoundingBoxMax(self
): 
1528         """Get the maximum bounding box for the shape, taking into account 
1529         external features such as shadows. 
1531         ww
, hh 
= self
.GetBoundingBoxMin() 
1532         if self
._shadowMode 
!= SHADOW_NONE
: 
1533             ww 
+= self
._shadowOffsetX
 
1534             hh 
+= self
._shadowOffsetY
 
1537     def GetBoundingBoxMin(self
): 
1538         """Get the minimum bounding box for the shape, that defines the area 
1539         available for drawing the contents (such as text). 
1545     def HasDescendant(self
, image
): 
1546         """TRUE if image is a descendant of this composite.""" 
1549         for child 
in self
._children
: 
1550             if child
.HasDescendant(image
): 
1554     # Assuming the attachment lies along a vertical or horizontal line, 
1555     # calculate the position on that point. 
1556     def CalcSimpleAttachment(self
, pt1
, pt2
, nth
, noArcs
, line
): 
1557         """Assuming the attachment lies along a vertical or horizontal line, 
1558         calculate the position on that point. 
1563             The first point of the line repesenting the edge of the shape. 
1566             The second point of the line representing the edge of the shape. 
1569             The position on the edge (for example there may be 6 lines at 
1570             this attachment point, and this may be the 2nd line. 
1573             The number of lines at this edge. 
1580         This function expects the line to be either vertical or horizontal, 
1581         and determines which. 
1583         isEnd 
= line 
and line
.IsEnd(self
) 
1585         # Are we horizontal or vertical? 
1586         isHorizontal 
= RoughlyEqual(pt1
[1], pt2
[1]) 
1596             if self
._spaceAttachments
: 
1597                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1598                     # Align line according to the next handle along 
1599                     point 
= line
.GetNextControlPoint(self
) 
1600                     if point
[0] < firstPoint
[0]: 
1602                     elif point
[0] > secondPoint
[0]: 
1607                     x 
= firstPoint
[0] + (nth 
+ 1) * (secondPoint
[0] - firstPoint
[0]) / (noArcs 
+ 1.0) 
1609                 x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 # Midpoint 
1612             assert RoughlyEqual(pt1
[0], pt2
[0]) 
1621             if self
._spaceAttachments
: 
1622                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1623                     # Align line according to the next handle along 
1624                     point 
= line
.GetNextControlPoint(self
) 
1625                     if point
[1] < firstPoint
[1]: 
1627                     elif point
[1] > secondPoint
[1]: 
1632                     y 
= firstPoint
[1] + (nth 
+ 1) * (secondPoint
[1] - firstPoint
[1]) / (noArcs 
+ 1.0) 
1634                 y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 # Midpoint 
1639     # Return the zero-based position in m_lines of line 
1640     def GetLinePosition(self
, line
): 
1641         """Get the zero-based position of line in the list of lines 
1645             return self
._lines
.index(line
) 
1653     # shoulder1 ->---------<- shoulder2 
1655     #                      <- branching attachment point N-1 
1657     def GetBranchingAttachmentInfo(self
, attachment
): 
1658         """Get information about where branching connections go. 
1660         Returns FALSE if there are no lines at this attachment. 
1662         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1664         # Number of lines at this attachment 
1665         lineCount 
= self
.GetAttachmentLineCount(attachment
) 
1670         totalBranchLength 
= self
._branchSpacing 
* (lineCount 
- 1) 
1671         root 
= self
.GetBranchingAttachmentRoot(attachment
) 
1673         neck 
= wx
.RealPoint() 
1674         shoulder1 
= wx
.RealPoint() 
1675         shoulder2 
= wx
.RealPoint() 
1677         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1678         if physicalAttachment 
== 0: 
1679             neck
[0] = self
.GetX() 
1680             neck
[1] = root
[1] - self
._branchNeckLength
 
1682             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1683             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1685             shoulder1
[1] = neck
[1] 
1686             shoulder2
[1] = neck
[1] 
1687         elif physicalAttachment 
== 1: 
1688             neck
[0] = root
[0] + self
._branchNeckLength
 
1691             shoulder1
[0] = neck
[0] 
1692             shoulder2
[0] = neck
[0] 
1694             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1695             shoulder1
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1696         elif physicalAttachment 
== 2: 
1697             neck
[0] = self
.GetX() 
1698             neck
[1] = root
[1] + self
._branchNeckLength
 
1700             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1701             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1703             shoulder1
[1] = neck
[1] 
1704             shoulder2
[1] = neck
[1] 
1705         elif physicalAttachment 
== 3: 
1706             neck
[0] = root
[0] - self
._branchNeckLength
 
1709             shoulder1
[0] = neck
[0] 
1710             shoulder2
[0] = neck
[0] 
1712             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1713             shoulder2
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1715             raise "Unrecognised attachment point in GetBranchingAttachmentInfo" 
1716         return root
, neck
, shoulder1
, shoulder2
 
1718     def GetBranchingAttachmentPoint(self
, attachment
, n
): 
1719         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1721         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1723         stemPt 
= wx
.RealPoint() 
1725         if physicalAttachment 
== 0: 
1726             pt
[1] = neck
[1] - self
._branchStemLength
 
1727             pt
[0] = shoulder1
[0] + n 
* self
._branchSpacing
 
1731         elif physicalAttachment 
== 2: 
1732             pt
[1] = neck
[1] + self
._branchStemLength
 
1733             pt
[0] = shoulder1
[0] + n 
* self
._branchStemLength
 
1737         elif physicalAttachment 
== 1: 
1738             pt
[0] = neck
[0] + self
._branchStemLength
 
1739             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1743         elif physicalAttachment 
== 3: 
1744             pt
[0] = neck
[0] - self
._branchStemLength
 
1745             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1750             raise "Unrecognised attachment point in GetBranchingAttachmentPoint" 
1754     def GetAttachmentLineCount(self
, attachment
): 
1755         """Get the number of lines at this attachment position.""" 
1757         for lineShape 
in self
._lines
: 
1758             if lineShape
.GetFrom() == self 
and lineShape
.GetAttachmentFrom() == attachment
: 
1760             elif lineShape
.GetTo() == self 
and lineShape
.GetAttachmentTo() == attachment
: 
1764     def GetBranchingAttachmentRoot(self
, attachment
): 
1765         """Get the root point at the given attachment.""" 
1766         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1768         root 
= wx
.RealPoint() 
1770         width
, height 
= self
.GetBoundingBoxMax() 
1772         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1773         if physicalAttachment 
== 0: 
1774             root
[0] = self
.GetX() 
1775             root
[1] = self
.GetY() - height 
/ 2.0 
1776         elif physicalAttachment 
== 1: 
1777             root
[0] = self
.GetX() + width 
/ 2.0 
1778             root
[1] = self
.GetY() 
1779         elif physicalAttachment 
== 2: 
1780             root
[0] = self
.GetX() 
1781             root
[1] = self
.GetY() + height 
/ 2.0 
1782         elif physicalAttachment 
== 3: 
1783             root
[0] = self
.GetX() - width 
/ 2.0 
1784             root
[1] = self
.GetY() 
1786             raise "Unrecognised attachment point in GetBranchingAttachmentRoot" 
1790     # Draw or erase the branches (not the actual arcs though) 
1791     def OnDrawBranchesAttachment(self
, dc
, attachment
, erase 
= False): 
1792         count 
= self
.GetAttachmentLineCount(attachment
) 
1796         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1799             dc
.SetPen(wx
.WHITE_PEN
) 
1800             dc
.SetBrush(wx
.WHITE_BRUSH
) 
1802             dc
.SetPen(wx
.BLACK_PEN
) 
1803             dc
.SetBrush(wx
.BLACK_BRUSH
) 
1806         dc
.DrawLine(root
[0], root
[1], neck
[0], neck
[1]) 
1809             # Draw shoulder-to-shoulder line 
1810             dc
.DrawLine(shoulder1
[0], shoulder1
[1], shoulder2
[0], shoulder2
[1]) 
1811         # Draw all the little branches 
1812         for i 
in range(count
): 
1813             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, i
) 
1814             dc
.DrawLine(stemPt
[0], stemPt
[1], pt
[0], pt
[1]) 
1816             if self
.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB 
and count 
> 1: 
1818                 dc
.DrawEllipse(stemPt
[0] - blobSize 
/ 2.0, stemPt
[1] - blobSize 
/ 2.0, blobSize
, blobSize
) 
1820     def OnDrawBranches(self
, dc
, erase 
= False): 
1821         if self
._attachmentMode 
!= ATTACHMENT_MODE_BRANCHING
: 
1823         for i 
in range(self
.GetNumberOfAttachments()): 
1824             self
.OnDrawBranchesAttachment(dc
, i
, erase
) 
1826     def GetAttachmentPositionEdge(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1827         """ Only get the attachment position at the _edge_ of the shape, 
1828         ignoring branching mode. This is used e.g. to indicate the edge of 
1829         interest, not the point on the attachment branch. 
1831         oldMode 
= self
._attachmentMode
 
1833         # Calculate as if to edge, not branch 
1834         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1835             self
._attachmentMode 
= ATTACHMENT_MODE_EDGE
 
1836         res 
= self
.GetAttachmentPosition(attachment
, nth
, no_arcs
, line
) 
1837         self
._attachmentMode 
= oldMode
 
1841     def PhysicalToLogicalAttachment(self
, physicalAttachment
): 
1842         """ Rotate the standard attachment point from physical 
1843         (0 is always North) to logical (0 -> 1 if rotated by 90 degrees) 
1845         if RoughlyEqual(self
.GetRotation(), 0): 
1846             i 
= physicalAttachment
 
1847         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1848             i 
= physicalAttachment 
- 1 
1849         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1850             i 
= physicalAttachment 
- 2 
1851         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1852             i 
= physicalAttachment 
- 3 
1854             # Can't handle -- assume the same 
1855             return physicalAttachment
 
1862     def LogicalToPhysicalAttachment(self
, logicalAttachment
): 
1863         """Rotate the standard attachment point from logical 
1864         to physical (0 is always North). 
1866         if RoughlyEqual(self
.GetRotation(), 0): 
1867             i 
= logicalAttachment
 
1868         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1869             i 
= logicalAttachment 
+ 1 
1870         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1871             i 
= logicalAttachment 
+ 2 
1872         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1873             i 
= logicalAttachment 
+ 3 
1875             return logicalAttachment
 
1882     def Rotate(self
, x
, y
, theta
): 
1883         """Rotate about the given axis by the given amount in radians.""" 
1884         self
._rotation 
= theta
 
1885         if self
._rotation 
< 0: 
1886             self
._rotation 
+= 2 * math
.pi
 
1887         elif self
._rotation 
> 2 * math
.pi
: 
1888             self
._rotation 
-= 2 * math
.pi
 
1890     def GetBackgroundPen(self
): 
1891         """Return pen of the right colour for the background.""" 
1892         if self
.GetCanvas(): 
1893             return wx
.Pen(self
.GetCanvas().GetBackgroundColour(), 1, wx
.SOLID
) 
1894         return WhiteBackgroundPen
 
1896     def GetBackgroundBrush(self
): 
1897         """Return brush of the right colour for the background.""" 
1898         if self
.GetCanvas(): 
1899             return wx
.Brush(self
.GetCanvas().GetBackgroundColour(), wx
.SOLID
) 
1900         return WhiteBackgroundBrush
 
1903         """Get the x position of the centre of the shape.""" 
1907         """Get the y position of the centre of the shape.""" 
1911         """Set the x position of the shape.""" 
1915         """Set the y position of the shape.""" 
1918     def GetParent(self
): 
1919         """Return the parent of this shape, if it is part of a composite.""" 
1922     def SetParent(self
, p
): 
1925     def GetChildren(self
): 
1926         """Return the list of children for this shape.""" 
1927         return self
._children
 
1929     def GetDrawHandles(self
): 
1930         """Return the list of drawhandles.""" 
1931         return self
._drawHandles
 
1933     def GetEventHandler(self
): 
1934         """Return the event handler for this shape.""" 
1935         return self
._eventHandler
 
1937     def SetEventHandler(self
, handler
): 
1938         """Set the event handler for this shape.""" 
1939         self
._eventHandler 
= handler
 
1941     def Recompute(self
): 
1942         """Recomputes any constraints associated with the shape. 
1944         Normally applicable to CompositeShapes only, but harmless for 
1945         other classes of Shape. 
1949     def IsHighlighted(self
): 
1950         """TRUE if the shape is highlighted. Shape highlighting is unimplemented.""" 
1951         return self
._highlighted
 
1953     def GetSensitivityFilter(self
): 
1954         """Return the sensitivity filter, a bitlist of values. 
1956         See Shape.SetSensitivityFilter. 
1958         return self
._sensitivity
 
1960     def SetFixedSize(self
, x
, y
): 
1961         """Set the shape to be fixed size.""" 
1962         self
._fixedWidth 
= x
 
1963         self
._fixedHeight 
= y
 
1965     def GetFixedSize(self
): 
1966         """Return flags indicating whether the shape is of fixed size in 
1969         return self
._fixedWidth
, self
._fixedHeight
 
1971     def GetFixedWidth(self
): 
1972         """TRUE if the shape cannot be resized in the horizontal plane.""" 
1973         return self
._fixedWidth
 
1975     def GetFixedHeight(self
): 
1976         """TRUE if the shape cannot be resized in the vertical plane.""" 
1977         return self
._fixedHeight
 
1979     def SetSpaceAttachments(self
, sp
): 
1980         """Indicate whether lines should be spaced out evenly at the point 
1981         they touch the node (sp = True), or whether they should join at a single 
1984         self
._spaceAttachments 
= sp
 
1986     def GetSpaceAttachments(self
): 
1987         """Return whether lines should be spaced out evenly at the point they 
1988         touch the node (True), or whether they should join at a single point 
1991         return self
._spaceAttachments
 
1993     def SetCentreResize(self
, cr
): 
1994         """Specify whether the shape is to be resized from the centre (the 
1995         centre stands still) or from the corner or side being dragged (the 
1996         other corner or side stands still). 
1998         self
._centreResize 
= cr
 
2000     def GetCentreResize(self
): 
2001         """TRUE if the shape is to be resized from the centre (the centre stands 
2002         still), or FALSE if from the corner or side being dragged (the other 
2003         corner or side stands still) 
2005         return self
._centreResize
 
2007     def SetMaintainAspectRatio(self
, ar
): 
2008         """Set whether a shape that resizes should not change the aspect ratio 
2009         (width and height should be in the original proportion). 
2011         self
._maintainAspectRatio 
= ar
 
2013     def GetMaintainAspectRatio(self
): 
2014         """TRUE if shape keeps aspect ratio during resize.""" 
2015         return self
._maintainAspectRatio
 
2018         """Return the list of lines connected to this shape.""" 
2021     def SetDisableLabel(self
, flag
): 
2022         """Set flag to TRUE to stop the default region being shown.""" 
2023         self
._disableLabel 
= flag
 
2025     def GetDisableLabel(self
): 
2026         """TRUE if the default region will not be shown, FALSE otherwise.""" 
2027         return self
._disableLabel
 
2029     def SetAttachmentMode(self
, mode
): 
2030         """Set the attachment mode. 
2032         If TRUE, attachment points will be significant when drawing lines to 
2033         and from this shape. 
2034         If FALSE, lines will be drawn as if to the centre of the shape. 
2036         self
._attachmentMode 
= mode
 
2038     def GetAttachmentMode(self
): 
2039         """Return the attachment mode. 
2041         See Shape.SetAttachmentMode. 
2043         return self
._attachmentMode
 
2046         """Set the integer identifier for this shape.""" 
2050         """Return the integer identifier for this shape.""" 
2054         """TRUE if the shape is in a visible state, FALSE otherwise. 
2056         Note that this has nothing to do with whether the window is hidden 
2057         or the shape has scrolled off the canvas; it refers to the internal 
2060         return self
._visible
 
2063         """Return the pen used for drawing the shape's outline.""" 
2067         """Return the brush used for filling the shape.""" 
2070     def GetNumberOfTextRegions(self
): 
2071         """Return the number of text regions for this shape.""" 
2072         return len(self
._regions
) 
2074     def GetRegions(self
): 
2075         """Return the list of ShapeRegions.""" 
2076         return self
._regions
 
2078     # Control points ('handles') redirect control to the actual shape, to 
2079     # make it easier to override sizing behaviour. 
2080     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2081         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2083         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2084         self
.GetCanvas().PrepareDC(dc
) 
2086         dc
.SetLogicalFunction(OGLRBLF
) 
2088         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2089         dc
.SetPen(dottedPen
) 
2090         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2092         if self
.GetCentreResize(): 
2093             # Maintain the same centre point 
2094             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2095             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2097             # Constrain sizing according to what control point you're dragging 
2098             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2099                 if self
.GetMaintainAspectRatio(): 
2100                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2102                     new_height 
= bound_y
 
2103             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2104                 if self
.GetMaintainAspectRatio(): 
2105                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2108             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2109                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2111             if self
.GetFixedWidth(): 
2114             if self
.GetFixedHeight(): 
2115                 new_height 
= bound_y
 
2117             pt
._controlPointDragEndWidth 
= new_width
 
2118             pt
._controlPointDragEndHeight 
= new_height
 
2120             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2122             # Don't maintain the same centre point 
2123             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2124             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2125             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2126             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2127             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2128                 newY1 
= pt
._controlPointDragStartY
 
2129                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2130             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2131                 newX1 
= pt
._controlPointDragStartX
 
2132                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2133             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2134                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2135                 if self
.GetY() > pt
._controlPointDragStartY
: 
2136                     newY2 
= newY1 
+ newH
 
2138                     newY1 
= newY2 
- newH
 
2140             newWidth 
= float(newX2 
- newX1
) 
2141             newHeight 
= float(newY2 
- newY1
) 
2143             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2144                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2146             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2147                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2149             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2150             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2151             if self
.GetFixedWidth(): 
2154             if self
.GetFixedHeight(): 
2157             pt
._controlPointDragEndWidth 
= newWidth
 
2158             pt
._controlPointDragEndHeight 
= newHeight
 
2159             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2161     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2162         self
._canvas
.CaptureMouse() 
2164         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2165         self
.GetCanvas().PrepareDC(dc
) 
2167         dc
.SetLogicalFunction(OGLRBLF
) 
2169         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2170         self
.GetEventHandler().OnEndSize(bound_x
, bound_y
) 
2172         # Choose the 'opposite corner' of the object as the stationary 
2173         # point in case this is non-centring resizing. 
2174         if pt
.GetX() < self
.GetX(): 
2175             pt
._controlPointDragStartX 
= self
.GetX() + bound_x 
/ 2.0 
2177             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2179         if pt
.GetY() < self
.GetY(): 
2180             pt
._controlPointDragStartY 
= self
.GetY() + bound_y 
/ 2.0 
2182             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2184         if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2185             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2186         elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2187             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2189         # We may require the old width and height 
2190         pt
._controlPointDragStartWidth 
= bound_x
 
2191         pt
._controlPointDragStartHeight 
= bound_y
 
2193         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2194         dc
.SetPen(dottedPen
) 
2195         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2197         if self
.GetCentreResize(): 
2198             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2199             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2201             # Constrain sizing according to what control point you're dragging 
2202             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2203                 if self
.GetMaintainAspectRatio(): 
2204                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2206                     new_height 
= bound_y
 
2207             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2208                 if self
.GetMaintainAspectRatio(): 
2209                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2212             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2213                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2215             if self
.GetFixedWidth(): 
2218             if self
.GetFixedHeight(): 
2219                 new_height 
= bound_y
 
2221             pt
._controlPointDragEndWidth 
= new_width
 
2222             pt
._controlPointDragEndHeight 
= new_height
 
2223             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2225             # Don't maintain the same centre point 
2226             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2227             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2228             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2229             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2230             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2231                 newY1 
= pt
._controlPointDragStartY
 
2232                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2233             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2234                 newX1 
= pt
._controlPointDragStartX
 
2235                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2236             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2237                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2238                 if pt
.GetY() > pt
._controlPointDragStartY
: 
2239                     newY2 
= newY1 
+ newH
 
2241                     newY1 
= newY2 
- newH
 
2243             newWidth 
= float(newX2 
- newX1
) 
2244             newHeight 
= float(newY2 
- newY1
) 
2246             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2247                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2249             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2250                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2252             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2253             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2254             if self
.GetFixedWidth(): 
2257             if self
.GetFixedHeight(): 
2260             pt
._controlPointDragEndWidth 
= newWidth
 
2261             pt
._controlPointDragEndHeight 
= newHeight
 
2262             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2264     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2265         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2266         self
.GetCanvas().PrepareDC(dc
) 
2268         if self
._canvas
.HasCapture(): 
2269             self
._canvas
.ReleaseMouse() 
2270         dc
.SetLogicalFunction(wx
.COPY
) 
2272         self
.ResetControlPoints() 
2276         self
.SetSize(pt
._controlPointDragEndWidth
, pt
._controlPointDragEndHeight
) 
2278         # The next operation could destroy this control point (it does for 
2279         # label objects, via formatting the text), so save all values we're 
2280         # going to use, or we'll be accessing garbage. 
2284         if self
.GetCentreResize(): 
2285             self
.Move(dc
, self
.GetX(), self
.GetY()) 
2287             self
.Move(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
) 
2289         # Recursively redraw links if we have a composite 
2290         if len(self
.GetChildren()): 
2291             self
.DrawLinks(dc
, -1, True) 
2293         width
, height 
= self
.GetBoundingBoxMax() 
2294         self
.GetEventHandler().OnEndSize(width
, height
) 
2296         if not self
._canvas
.GetQuickEditMode() and pt
._eraseObject
: 
2297             self
._canvas
.Redraw(dc
) 
2301 class RectangleShape(Shape
): 
2303     The wxRectangleShape has rounded or square corners. 
2308     def __init__(self
, w 
= 0.0, h 
= 0.0): 
2309         Shape
.__init
__(self
) 
2312         self
._cornerRadius 
= 0.0 
2313         self
.SetDefaultRegionSize() 
2315     def OnDraw(self
, dc
): 
2316         x1 
= self
._xpos 
- self
._width 
/ 2.0 
2317         y1 
= self
._ypos 
- self
._height 
/ 2.0 
2319         if self
._shadowMode 
!= SHADOW_NONE
: 
2320             if self
._shadowBrush
: 
2321                 dc
.SetBrush(self
._shadowBrush
) 
2322             dc
.SetPen(TransparentPen
) 
2324             if self
._cornerRadius
: 
2325                 dc
.DrawRoundedRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
, self
._cornerRadius
) 
2327                 dc
.DrawRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
) 
2330             if self
._pen
.GetWidth() == 0: 
2331                 dc
.SetPen(TransparentPen
) 
2333                 dc
.SetPen(self
._pen
) 
2335             dc
.SetBrush(self
._brush
) 
2337         if self
._cornerRadius
: 
2338             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
2340             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
2342     def GetBoundingBoxMin(self
): 
2343         return self
._width
, self
._height
 
2345     def SetSize(self
, x
, y
, recursive 
= False): 
2346         self
.SetAttachmentSize(x
, y
) 
2347         self
._width 
= max(x
, 1) 
2348         self
._height 
= max(y
, 1) 
2349         self
.SetDefaultRegionSize() 
2351     def GetCornerRadius(self
): 
2352         """Get the radius of the rectangle's rounded corners.""" 
2353         return self
._cornerRadius
 
2355     def SetCornerRadius(self
, rad
): 
2356         """Set the radius of the rectangle's rounded corners. 
2358         If the radius is zero, a non-rounded rectangle will be drawn. 
2359         If the radius is negative, the value is the proportion of the smaller 
2360         dimension of the rectangle. 
2362         self
._cornerRadius 
= rad
 
2364     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2365     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2366         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2367         return FindEndForBox(bound_x
, bound_y
, self
._xpos
, self
._ypos
, x2
, y2
) 
2372     def GetHeight(self
): 
2375     def SetWidth(self
, w
): 
2378     def SetHeight(self
, h
): 
2383 class PolygonShape(Shape
): 
2384     """A PolygonShape's shape is defined by a number of points passed to 
2385     the object's constructor. It can be used to create new shapes such as 
2386     diamonds and triangles. 
2389         Shape
.__init
__(self
) 
2392         self
._originalPoints 
= None 
2394     def Create(self
, the_points 
= None): 
2395         """Takes a list of wx.RealPoints or tuples; each point is an offset 
2401             self
._originalPoints 
= [] 
2404             self
._originalPoints 
= the_points
 
2406             # Duplicate the list of points 
2408             for point 
in the_points
: 
2409                 new_point 
= wx
.Point(point
[0], point
[1]) 
2410                 self
._points
.append(new_point
) 
2411             self
.CalculateBoundingBox() 
2412             self
._originalWidth 
= self
._boundWidth
 
2413             self
._originalHeight 
= self
._boundHeight
 
2414             self
.SetDefaultRegionSize() 
2416     def ClearPoints(self
): 
2418         self
._originalPoints 
= [] 
2420     # Width and height. Centre of object is centre of box 
2421     def GetBoundingBoxMin(self
): 
2422         return self
._boundWidth
, self
._boundHeight
 
2424     def GetPoints(self
): 
2425         """Return the internal list of polygon vertices.""" 
2428     def GetOriginalPoints(self
): 
2429         return self
._originalPoints
 
2431     def GetOriginalWidth(self
): 
2432         return self
._originalWidth
 
2434     def GetOriginalHeight(self
): 
2435         return self
._originalHeight
 
2437     def SetOriginalWidth(self
, w
): 
2438         self
._originalWidth 
= w
 
2440     def SetOriginalHeight(self
, h
): 
2441         self
._originalHeight 
= h
 
2443     def CalculateBoundingBox(self
): 
2444         # Calculate bounding box at construction (and presumably resize) time 
2450         for point 
in self
._points
: 
2453             if point
[0] > right
: 
2458             if point
[1] > bottom
: 
2461         self
._boundWidth 
= right 
- left
 
2462         self
._boundHeight 
= bottom 
- top
 
2464     def CalculatePolygonCentre(self
): 
2465         """Recalculates the centre of the polygon, and 
2466         readjusts the point offsets accordingly. 
2467         Necessary since the centre of the polygon 
2468         is expected to be the real centre of the bounding 
2476         for point 
in self
._points
: 
2479             if point
[0] > right
: 
2484             if point
[1] > bottom
: 
2487         bwidth 
= right 
- left
 
2488         bheight 
= bottom 
- top
 
2490         newCentreX 
= left 
+ bwidth 
/ 2.0 
2491         newCentreY 
= top 
+ bheight 
/ 2.0 
2493         for i 
in range(len(self
._points
)): 
2494             self
._points
[i
] = self
._points
[i
][0] - newCentreX
, self
._points
[i
][1] - newCentreY
 
2495         self
._xpos 
+= newCentreX
 
2496         self
._ypos 
+= newCentreY
 
2498     def HitTest(self
, x
, y
): 
2499         # Imagine four lines radiating from this point. If all of these lines 
2500         # hit the polygon, we're inside it, otherwise we're not. Obviously 
2501         # we'd need more radiating lines to be sure of correct results for 
2502         # very strange (concave) shapes. 
2503         endPointsX 
= [x
, x 
+ 1000, x
, x 
- 1000] 
2504         endPointsY 
= [y 
- 1000, y
, y 
+ 1000, y
] 
2509         for point 
in self
._points
: 
2510             xpoints
.append(point
[0] + self
._xpos
) 
2511             ypoints
.append(point
[1] + self
._ypos
) 
2513         # We assume it's inside the polygon UNLESS one or more 
2514         # lines don't hit the outline. 
2518             if not PolylineHitTest(xpoints
, ypoints
, x
, y
, endPointsX
[i
], endPointsY
[i
]): 
2524         nearest_attachment 
= 0 
2526         # If a hit, check the attachment points within the object 
2529         for i 
in range(self
.GetNumberOfAttachments()): 
2530             e 
= self
.GetAttachmentPositionEdge(i
) 
2533                 l 
= math
.sqrt((xp 
- x
) * (xp 
- x
) + (yp 
- y
) * (yp 
- y
)) 
2536                     nearest_attachment 
= i
 
2538         return nearest_attachment
, nearest
 
2540     # Really need to be able to reset the shape! Otherwise, if the 
2541     # points ever go to zero, we've lost it, and can't resize. 
2542     def SetSize(self
, new_width
, new_height
, recursive 
= True): 
2543         self
.SetAttachmentSize(new_width
, new_height
) 
2545         # Multiply all points by proportion of new size to old size 
2546         x_proportion 
= abs(float(new_width
) / self
._originalWidth
) 
2547         y_proportion 
= abs(float(new_height
) / self
._originalHeight
) 
2549         for i 
in range(max(len(self
._points
), len(self
._originalPoints
))): 
2550             self
._points
[i
] = wx
.Point(self
._originalPoints
[i
][0] * x_proportion
, self
._originalPoints
[i
][1] * y_proportion
) 
2552         self
._boundWidth 
= abs(new_width
) 
2553         self
._boundHeight 
= abs(new_height
) 
2554         self
.SetDefaultRegionSize() 
2556     # Make the original points the same as the working points 
2557     def UpdateOriginalPoints(self
): 
2558         """If we've changed the shape, must make the original points match the 
2559         working points with this function. 
2561         self
._originalPoints 
= [] 
2563         for point 
in self
._points
: 
2564             original_point 
= wx
.RealPoint(point
[0], point
[1]) 
2565             self
._originalPoints
.append(original_point
) 
2567         self
.CalculateBoundingBox() 
2568         self
._originalWidth 
= self
._boundWidth
 
2569         self
._originalHeight 
= self
._boundHeight
 
2571     def AddPolygonPoint(self
, pos
): 
2572         """Add a control point after the given point.""" 
2574             firstPoint 
= self
._points
[pos
] 
2576             firstPoint 
= self
._points
[0] 
2579             secondPoint 
= self
._points
[pos 
+ 1] 
2581             secondPoint 
= self
._points
[0] 
2583         x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 + firstPoint
[0] 
2584         y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 + firstPoint
[1] 
2585         point 
= wx
.RealPoint(x
, y
) 
2587         if pos 
>= len(self
._points
) - 1: 
2588             self
._points
.append(point
) 
2590             self
._points
.insert(pos 
+ 1, point
) 
2592         self
.UpdateOriginalPoints() 
2595             self
.DeleteControlPoints() 
2596             self
.MakeControlPoints() 
2598     def DeletePolygonPoint(self
, pos
): 
2599         """Delete the given control point.""" 
2600         if pos 
< len(self
._points
): 
2601             del self
._points
[pos
] 
2602             self
.UpdateOriginalPoints() 
2604                 self
.DeleteControlPoints() 
2605                 self
.MakeControlPoints() 
2607     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2608     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2609         # First check for situation where the line is vertical, 
2610         # and we would want to connect to a point on that vertical -- 
2611         # oglFindEndForPolyline can't cope with this (the arrow 
2612         # gets drawn to the wrong place). 
2613         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE 
and x1 
== x2
: 
2614             # Look for the point we'd be connecting to. This is 
2616             for point 
in self
._points
: 
2618                     if y2 
> y1 
and point
[1] > 0: 
2619                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2620                     elif y2 
< y1 
and point
[1] < 0: 
2621                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2625         for point 
in self
._points
: 
2626             xpoints
.append(point
[0] + self
._xpos
) 
2627             ypoints
.append(point
[1] + self
._ypos
) 
2629         return FindEndForPolyline(xpoints
, ypoints
, x1
, y1
, x2
, y2
) 
2631     def OnDraw(self
, dc
): 
2632         if self
._shadowMode 
!= SHADOW_NONE
: 
2633             if self
._shadowBrush
: 
2634                 dc
.SetBrush(self
._shadowBrush
) 
2635             dc
.SetPen(TransparentPen
) 
2637             dc
.DrawPolygon(self
._points
, self
._xpos 
+ self
._shadowOffsetX
, self
._ypos
, self
._shadowOffsetY
) 
2640             if self
._pen
.GetWidth() == 0: 
2641                 dc
.SetPen(TransparentPen
) 
2643                 dc
.SetPen(self
._pen
) 
2645             dc
.SetBrush(self
._brush
) 
2646         dc
.DrawPolygon(self
._points
, self
._xpos
, self
._ypos
) 
2648     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
2649         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2650         # Multiply all points by proportion of new size to old size 
2651         x_proportion 
= abs(float(w
) / self
._originalWidth
) 
2652         y_proportion 
= abs(float(h
) / self
._originalHeight
) 
2655         for point 
in self
._originalPoints
: 
2656             intPoints
.append(wx
.Point(x_proportion 
* point
[0], y_proportion 
* point
[1])) 
2657         dc
.DrawPolygon(intPoints
, x
, y
) 
2659     # Make as many control points as there are vertices 
2660     def MakeControlPoints(self
): 
2661         for point 
in self
._points
: 
2662             control 
= PolygonControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
, point
[0], point
[1]) 
2663             self
._canvas
.AddShape(control
) 
2664             self
._controlPoints
.append(control
) 
2666     def ResetControlPoints(self
): 
2667         for i 
in range(min(len(self
._points
), len(self
._controlPoints
))): 
2668             point 
= self
._points
[i
] 
2669             self
._controlPoints
[i
]._xoffset 
= point
[0] 
2670             self
._controlPoints
[i
]._yoffset 
= point
[1] 
2671             self
._controlPoints
[i
].polygonVertex 
= point
 
2673     def GetNumberOfAttachments(self
): 
2674         maxN 
= max(len(self
._points
) - 1, 0) 
2675         for point 
in self
._attachmentPoints
: 
2676             if point
._id 
> maxN
: 
2680     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2681         if self
._attachmentMode 
== ATTACHMENT_MODE_EDGE 
and self
._points 
and attachment 
< len(self
._points
): 
2682             point 
= self
._points
[0] 
2683             return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2684         return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2686     def AttachmentIsValid(self
, attachment
): 
2687         if not self
._points
: 
2690         if attachment 
>= 0 and attachment 
< len(self
._points
): 
2693         for point 
in self
._attachmentPoints
: 
2694             if point
._id 
== attachment
: 
2699     # Rotate about the given axis by the given amount in radians 
2700     def Rotate(self
, x
, y
, theta
): 
2701         actualTheta 
= theta 
- self
._rotation
 
2703         # Rotate attachment points 
2704         sinTheta 
= math
.sin(actualTheta
) 
2705         cosTheta 
= math
.cos(actualTheta
) 
2707         for point 
in self
._attachmentPoints
: 
2711             point
._x 
= x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
 
2712             point
._y 
= x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2714         for i 
in range(len(self
._points
)): 
2715             x1
, y1 
= self
._points
[i
] 
2717             self
._points
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2719         for i 
in range(len(self
._originalPoints
)): 
2720             x1
, y1 
= self
._originalPoints
[i
] 
2722             self
._originalPoints
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2724         # Added by Pierre Hjälm. If we don't do this the outline will be 
2725         # the wrong size. Hopefully it won't have any ill effects. 
2726         self
.UpdateOriginalPoints() 
2728         self
._rotation 
= theta
 
2730         self
.CalculatePolygonCentre() 
2731         self
.CalculateBoundingBox() 
2732         self
.ResetControlPoints() 
2734     # Control points ('handles') redirect control to the actual shape, to 
2735     # make it easier to override sizing behaviour. 
2736     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2737         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2738         self
.GetCanvas().PrepareDC(dc
) 
2740         dc
.SetLogicalFunction(OGLRBLF
) 
2742         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2743         dc
.SetPen(dottedPen
) 
2744         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2746         # Code for CTRL-drag in C++ version commented out 
2748         pt
.CalculateNewSize(x
, y
) 
2750         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2752     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2753         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2754         self
.GetCanvas().PrepareDC(dc
) 
2758         dc
.SetLogicalFunction(OGLRBLF
) 
2760         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2762         dist 
= math
.sqrt((x 
- self
.GetX()) * (x 
- self
.GetX()) + (y 
- self
.GetY()) * (y 
- self
.GetY())) 
2764         pt
._originalDistance 
= dist
 
2765         pt
._originalSize
[0] = bound_x
 
2766         pt
._originalSize
[1] = bound_y
 
2768         if pt
._originalDistance 
== 0: 
2769             pt
._originalDistance 
= 0.0001 
2771         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2772         dc
.SetPen(dottedPen
) 
2773         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2775         # Code for CTRL-drag in C++ version commented out 
2777         pt
.CalculateNewSize(x
, y
) 
2779         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2781         self
._canvas
.CaptureMouse() 
2783     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2784         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2785         self
.GetCanvas().PrepareDC(dc
) 
2787         if self
._canvas
.HasCapture(): 
2788             self
._canvas
.ReleaseMouse() 
2789         dc
.SetLogicalFunction(wx
.COPY
) 
2791         # If we're changing shape, must reset the original points 
2793             self
.CalculateBoundingBox() 
2794             self
.CalculatePolygonCentre() 
2796             self
.SetSize(pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2799         self
.ResetControlPoints() 
2800         self
.Move(dc
, self
.GetX(), self
.GetY()) 
2801         if not self
._canvas
.GetQuickEditMode(): 
2802             self
._canvas
.Redraw(dc
) 
2806 class EllipseShape(Shape
): 
2807     """The EllipseShape behaves similarly to the RectangleShape but is 
2813     def __init__(self
, w
, h
): 
2814         Shape
.__init
__(self
) 
2817         self
.SetDefaultRegionSize() 
2819     def GetBoundingBoxMin(self
): 
2820         return self
._width
, self
._height
 
2822     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2823         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2825         return DrawArcToEllipse(self
._xpos
, self
._ypos
, bound_x
, bound_y
, x2
, y2
, x1
, y1
) 
2830     def GetHeight(self
): 
2833     def SetWidth(self
, w
): 
2836     def SetHeight(self
, h
): 
2839     def OnDraw(self
, dc
): 
2840         if self
._shadowMode 
!= SHADOW_NONE
: 
2841             if self
._shadowBrush
: 
2842                 dc
.SetBrush(self
._shadowBrush
) 
2843             dc
.SetPen(TransparentPen
) 
2844             dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0 + self
._shadowOffsetX
, 
2845                            self
._ypos 
- self
.GetHeight() / 2.0 + self
._shadowOffsetY
, 
2846                            self
.GetWidth(), self
.GetHeight()) 
2849             if self
._pen
.GetWidth() == 0: 
2850                 dc
.SetPen(TransparentPen
) 
2852                 dc
.SetPen(self
._pen
) 
2854             dc
.SetBrush(self
._brush
) 
2855         dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0, self
._ypos 
- self
.GetHeight() / 2.0, self
.GetWidth(), self
.GetHeight()) 
2857     def SetSize(self
, x
, y
, recursive 
= True): 
2858         self
.SetAttachmentSize(x
, y
) 
2861         self
.SetDefaultRegionSize() 
2863     def GetNumberOfAttachments(self
): 
2864         return Shape
.GetNumberOfAttachments(self
) 
2866     # There are 4 attachment points on an ellipse - 0 = top, 1 = right, 
2867     # 2 = bottom, 3 = left. 
2868     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2869         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
2870             return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2872         if self
._attachmentMode 
!= ATTACHMENT_MODE_NONE
: 
2873             top 
= self
._ypos 
+ self
._height 
/ 2.0 
2874             bottom 
= self
._ypos 
- self
._height 
/ 2.0 
2875             left 
= self
._xpos 
- self
._width 
/ 2.0 
2876             right 
= self
._xpos 
+ self
._width 
/ 2.0 
2878             physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
2880             if physicalAttachment 
== 0: 
2881                 if self
._spaceAttachments
: 
2882                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2886                 # We now have the point on the bounding box: but get the point 
2887                 # on the ellipse by imagining a vertical line from 
2888                 # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting 
2891                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
- self
._height 
- 500, x
, self
._ypos
) 
2892             elif physicalAttachment 
== 1: 
2894                 if self
._spaceAttachments
: 
2895                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2898                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
+ self
._width 
+ 500, y
, self
._xpos
, y
) 
2899             elif physicalAttachment 
== 2: 
2900                 if self
._spaceAttachments
: 
2901                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2905                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
+ self
._height 
+ 500, x
, self
._ypos
) 
2906             elif physicalAttachment 
== 3: 
2908                 if self
._spaceAttachments
: 
2909                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2912                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
- self
._width 
- 500, y
, self
._xpos
, y
) 
2914                 return Shape
.GetAttachmentPosition(self
, attachment
, x
, y
, nth
, no_arcs
, line
) 
2916             return self
._xpos
, self
._ypos
 
2920 class CircleShape(EllipseShape
): 
2921     """An EllipseShape whose width and height are the same.""" 
2922     def __init__(self
, diameter
): 
2923         EllipseShape
.__init
__(self
, diameter
, diameter
) 
2924         self
.SetMaintainAspectRatio(True) 
2926     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2927         return FindEndForCircle(self
._width 
/ 2.0, self
._xpos
, self
._ypos
, x2
, y2
) 
2931 class TextShape(RectangleShape
): 
2932     """As wxRectangleShape, but only the text is displayed.""" 
2933     def __init__(self
, width
, height
): 
2934         RectangleShape
.__init
__(self
, width
, height
) 
2936     def OnDraw(self
, dc
): 
2941 class ShapeRegion(object): 
2942     """Object region.""" 
2943     def __init__(self
, region 
= None): 
2945             self
._regionText 
= region
._regionText
 
2946             self
._regionName 
= region
._regionName
 
2947             self
._textColour 
= region
._textColour
 
2949             self
._font 
= region
._font
 
2950             self
._minHeight 
= region
._minHeight
 
2951             self
._minWidth 
= region
._minWidth
 
2952             self
._width 
= region
._width
 
2953             self
._height 
= region
._height
 
2957             self
._regionProportionX 
= region
._regionProportionX
 
2958             self
._regionProportionY 
= region
._regionProportionY
 
2959             self
._formatMode 
= region
._formatMode
 
2960             self
._actualColourObject 
= region
._actualColourObject
 
2961             self
._actualPenObject 
= None 
2962             self
._penStyle 
= region
._penStyle
 
2963             self
._penColour 
= region
._penColour
 
2966             for line 
in region
._formattedText
: 
2967                 new_line 
= ShapeTextLine(line
.GetX(), line
.GetY(), line
.GetText()) 
2968                 self
._formattedText
.append(new_line
) 
2970             self
._regionText 
= "" 
2971             self
._font 
= NormalFont
 
2972             self
._minHeight 
= 5.0 
2973             self
._minWidth 
= 5.0 
2979             self
._regionProportionX 
= -1.0 
2980             self
._regionProportionY 
= -1.0 
2981             self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
2982             self
._regionName 
= "" 
2983             self
._textColour 
= "BLACK" 
2984             self
._penColour 
= "BLACK" 
2985             self
._penStyle 
= wx
.SOLID
 
2986             self
._actualColourObject 
= wx
.TheColourDatabase
.Find("BLACK") 
2987             self
._actualPenObject 
= None 
2989         self
._formattedText 
= [] 
2991     def ClearText(self
): 
2992         self
._formattedText 
= [] 
2994     def SetFont(self
, f
): 
2997     def SetMinSize(self
, w
, h
): 
3001     def SetSize(self
, w
, h
): 
3005     def SetPosition(self
, xp
, yp
): 
3009     def SetProportions(self
, xp
, yp
): 
3010         self
._regionProportionX 
= xp
 
3011         self
._regionProportionY 
= yp
 
3013     def SetFormatMode(self
, mode
): 
3014         self
._formatMode 
= mode
 
3016     def SetColour(self
, col
): 
3017         self
._textColour 
= col
 
3018         self
._actualColourObject 
= col
 
3020     def GetActualColourObject(self
): 
3021         self
._actualColourObject 
= wx
.TheColourDatabase
.Find(self
.GetColour()) 
3022         return self
._actualColourObject
 
3024     def SetPenColour(self
, col
): 
3025         self
._penColour 
= col
 
3026         self
._actualPenObject 
= None 
3028     # Returns NULL if the pen is invisible 
3029     # (different to pen being transparent; indicates that 
3030     # region boundary should not be drawn.) 
3031     def GetActualPen(self
): 
3032         if self
._actualPenObject
: 
3033             return self
._actualPenObject
 
3035         if not self
._penColour
: 
3037         if self
._penColour
=="Invisible": 
3039         self
._actualPenObject 
= wx
.ThePenList
.FindOrCreatePen(self
._penColour
, 1, self
._penStyle
) 
3040         return self
._actualPenObject
 
3042     def SetText(self
, s
): 
3043         self
._regionText 
= s
 
3045     def SetName(self
, s
): 
3046         self
._regionName 
= s
 
3049         return self
._regionText
 
3054     def GetMinSize(self
): 
3055         return self
._minWidth
, self
._minHeight
 
3057     def GetProportion(self
): 
3058         return self
._regionProportionX
, self
._regionProportionY
 
3061         return self
._width
, self
._height
 
3063     def GetPosition(self
): 
3064         return self
._x
, self
._y
 
3066     def GetFormatMode(self
): 
3067         return self
._formatMode
 
3070         return self
._regionName
 
3072     def GetColour(self
): 
3073         return self
._textColour
 
3075     def GetFormattedText(self
): 
3076         return self
._formattedText
 
3078     def GetPenColour(self
): 
3079         return self
._penColour
 
3081     def GetPenStyle(self
): 
3082         return self
._penStyle
 
3084     def SetPenStyle(self
, style
): 
3085         self
._penStyle 
= style
 
3086         self
._actualPenObject 
= None 
3091     def GetHeight(self
): 
3096 class ControlPoint(RectangleShape
): 
3097     def __init__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, the_type
): 
3098         RectangleShape
.__init
__(self
, size
, size
) 
3100         self
._canvas 
= theCanvas
 
3101         self
._shape 
= object 
3102         self
._xoffset 
= the_xoffset
 
3103         self
._yoffset 
= the_yoffset
 
3104         self
._type 
= the_type
 
3105         self
.SetPen(BlackForegroundPen
) 
3106         self
.SetBrush(wx
.BLACK_BRUSH
) 
3107         self
._oldCursor 
= None 
3108         self
._visible 
= True 
3109         self
._eraseObject 
= True 
3111     # Don't even attempt to draw any text - waste of time 
3112     def OnDrawContents(self
, dc
): 
3115     def OnDraw(self
, dc
): 
3116         self
._xpos 
= self
._shape
.GetX() + self
._xoffset
 
3117         self
._ypos 
= self
._shape
.GetY() + self
._yoffset
 
3118         RectangleShape
.OnDraw(self
, dc
) 
3120     def OnErase(self
, dc
): 
3121         RectangleShape
.OnErase(self
, dc
) 
3123     # Implement resizing of canvas object 
3124     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3125         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3127     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3128         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3130     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3131         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3133     def GetNumberOfAttachments(self
): 
3136     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
3137         return self
._xpos
, self
._ypos
 
3139     def SetEraseObject(self
, er
): 
3140         self
._eraseObject 
= er
 
3143 class PolygonControlPoint(ControlPoint
): 
3144     def __init__(self
, theCanvas
, object, size
, vertex
, the_xoffset
, the_yoffset
): 
3145         ControlPoint
.__init
__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, 0) 
3146         self
._polygonVertex 
= vertex
 
3147         self
._originalDistance 
= 0.0 
3148         self
._newSize 
= wx
.RealPoint() 
3149         self
._originalSize 
= wx
.RealPoint() 
3151     def GetNewSize(self
): 
3152         return self
._newSize
 
3154     # Calculate what new size would be, at end of resize 
3155     def CalculateNewSize(self
, x
, y
): 
3156         bound_x
, bound_y 
= self
.GetShape().GetBoundingBoxMax() 
3157         dist 
= math
.sqrt((x 
- self
._shape
.GetX()) * (x 
- self
._shape
.GetX()) + (y 
- self
._shape
.GetY()) * (y 
- self
._shape
.GetY())) 
3159         self
._newSize
[0] = dist 
/ self
._originalDistance 
* self
._originalSize
[0] 
3160         self
._newSize
[1] = dist 
/ self
._originalDistance 
* self
._originalSize
[1] 
3162     # Implement resizing polygon or moving the vertex 
3163     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3164         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3166     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3167         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3169     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3170         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3172 from _canvas 
import * 
3173 from _lines 
import * 
3174 from _composit 
import *