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 * 
  24     global WhiteBackgroundPen
, WhiteBackgroundBrush
, TransparentPen
 
  25     global BlackForegroundPen
, NormalFont
 
  27     WhiteBackgroundPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
) 
  28     WhiteBackgroundBrush 
= wx
.Brush(wx
.WHITE
, wx
.SOLID
) 
  30     TransparentPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
) 
  31     BlackForegroundPen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
  33     NormalFont 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
  40 class ShapeTextLine(object): 
  41     def __init__(self
, the_x
, the_y
, the_line
): 
  58     def SetText(self
, text
): 
  66 class ShapeEvtHandler(object): 
  67     def __init__(self
, prev 
= None, shape 
= None): 
  68         self
._previousHandler 
= prev
 
  69         self
._handlerShape 
= shape
 
  74     def SetShape(self
, sh
): 
  75         self
._handlerShape 
= sh
 
  78         return self
._handlerShape
 
  80     def SetPreviousHandler(self
, handler
): 
  81         self
._previousHandler 
= handler
 
  83     def GetPreviousHandler(self
): 
  84         return self
._previousHandler
 
  87         if self
._previousHandler
: 
  88             self
._previousHandler
.OnDraw(dc
) 
  90     def OnMoveLinks(self
, dc
): 
  91         if self
._previousHandler
: 
  92             self
._previousHandler
.OnMoveLinks(dc
) 
  94     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
  95         if self
._previousHandler
: 
  96             self
._previousHandler
.OnMoveLink(dc
, moveControlPoints
) 
  98     def OnDrawContents(self
, dc
): 
  99         if self
._previousHandler
: 
 100             self
._previousHandler
.OnDrawContents(dc
) 
 102     def OnDrawBranches(self
, dc
, erase 
= False): 
 103         if self
._previousHandler
: 
 104             self
._previousHandler
.OnDrawBranches(dc
, erase 
= erase
) 
 106     def OnSize(self
, x
, y
): 
 107         if self
._previousHandler
: 
 108             self
._previousHandler
.OnSize(x
, y
) 
 110     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 111         if self
._previousHandler
: 
 112             return self
._previousHandler
.OnMovePre(dc
, x
, y
, old_x
, old_y
, display
) 
 116     def OnMovePost(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 117         if self
._previousHandler
: 
 118             return self
._previousHandler
.OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
 122     def OnErase(self
, dc
): 
 123         if self
._previousHandler
: 
 124             self
._previousHandler
.OnErase(dc
) 
 126     def OnEraseContents(self
, dc
): 
 127         if self
._previousHandler
: 
 128             self
._previousHandler
.OnEraseContents(dc
) 
 130     def OnHighlight(self
, dc
): 
 131         if self
._previousHandler
: 
 132             self
._previousHandler
.OnHighlight(dc
) 
 134     def OnLeftClick(self
, x
, y
, keys
, attachment
): 
 135         if self
._previousHandler
: 
 136             self
._previousHandler
.OnLeftClick(x
, y
, keys
, attachment
) 
 138     def OnLeftDoubleClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 139         if self
._previousHandler
: 
 140             self
._previousHandler
.OnLeftDoubleClick(x
, y
, keys
, attachment
) 
 142     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 143         if self
._previousHandler
: 
 144             self
._previousHandler
.OnRightClick(x
, y
, keys
, attachment
) 
 146     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 147         if self
._previousHandler
: 
 148             self
._previousHandler
.OnDragLeft(draw
, x
, y
, keys
, attachment
) 
 150     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 151         if self
._previousHandler
: 
 152             self
._previousHandler
.OnBeginDragLeft(x
, y
, keys
, attachment
) 
 154     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 155         if self
._previousHandler
: 
 156             self
._previousHandler
.OnEndDragLeft(x
, y
, keys
, attachment
) 
 158     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 159         if self
._previousHandler
: 
 160             self
._previousHandler
.OnDragRight(draw
, x
, y
, keys
, attachment
) 
 162     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 163         if self
._previousHandler
: 
 164             self
._previousHandler
.OnBeginDragRight(x
, y
, keys
, attachment
) 
 166     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 167         if self
._previousHandler
: 
 168             self
._previousHandler
.OnEndDragRight(x
, y
, keys
, attachment
) 
 170     # Control points ('handles') redirect control to the actual shape, 
 171     # to make it easier to override sizing behaviour. 
 172     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 173         if self
._previousHandler
: 
 174             self
._previousHandler
.OnSizingDragLeft(pt
, draw
, x
, y
, keys
, attachment
) 
 176     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 177         if self
._previousHandler
: 
 178             self
._previousHandler
.OnSizingBeginDragLeft(pt
, x
, y
, keys
, attachment
) 
 180     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 181         if self
._previousHandler
: 
 182             self
._previousHandler
.OnSizingEndDragLeft(pt
, x
, y
, keys
, attachment
) 
 184     def OnBeginSize(self
, w
, h
): 
 187     def OnEndSize(self
, w
, h
): 
 190     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 191         if self
._previousHandler
: 
 192             self
._previousHandler
.OnDrawOutline(dc
, x
, y
, w
, h
) 
 194     def OnDrawControlPoints(self
, dc
): 
 195         if self
._previousHandler
: 
 196             self
._previousHandler
.OnDrawControlPoints(dc
) 
 198     def OnEraseControlPoints(self
, dc
): 
 199         if self
._previousHandler
: 
 200             self
._previousHandler
.OnEraseControlPoints(dc
) 
 202     # Can override this to prevent or intercept line reordering. 
 203     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 204         if self
._previousHandler
: 
 205             self
._previousHandler
.OnChangeAttachment(attachment
, line
, ordering
) 
 209 class Shape(ShapeEvtHandler
): 
 214     The wxShape is the top-level, abstract object that all other objects 
 215     are derived from. All common functionality is represented by wxShape's 
 216     members, and overriden members that appear in derived classes and have 
 217     behaviour as documented for wxShape, are not documented separately. 
 220     GraphicsInSizeToContents 
= False 
 222     def __init__(self
, canvas 
= None): 
 223         ShapeEvtHandler
.__init
__(self
) 
 225         self
._eventHandler 
= self
 
 228         self
._formatted 
= False 
 229         self
._canvas 
= canvas
 
 232         self
._pen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
 233         self
._brush 
= wx
.WHITE_BRUSH
 
 234         self
._font 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
 235         self
._textColour 
= wx
.BLACK
 
 236         self
._textColourName 
= wx
.BLACK
 
 237         self
._visible 
= False 
 238         self
._selected 
= False 
 239         self
._attachmentMode 
= ATTACHMENT_MODE_NONE
 
 240         self
._spaceAttachments 
= True 
 241         self
._disableLabel 
= False 
 242         self
._fixedWidth 
= False 
 243         self
._fixedHeight 
= False 
 244         self
._drawHandles 
= True 
 245         self
._sensitivity 
= OP_ALL
 
 246         self
._draggable 
= True 
 248         self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
 249         self
._shadowMode 
= SHADOW_NONE
 
 250         self
._shadowOffsetX 
= 6 
 251         self
._shadowOffsetY 
= 6 
 252         self
._shadowBrush 
= wx
.BLACK_BRUSH
 
 253         self
._textMarginX 
= 5 
 254         self
._textMarginY 
= 5 
 255         self
._regionName 
= "0" 
 256         self
._centreResize 
= True 
 257         self
._maintainAspectRatio 
= False 
 258         self
._highlighted 
= False 
 260         self
._branchNeckLength 
= 10 
 261         self
._branchStemLength 
= 10 
 262         self
._branchSpacing 
= 10 
 263         self
._branchStyle 
= BRANCHING_ATTACHMENT_NORMAL
 
 267         self
._controlPoints 
= [] 
 268         self
._attachmentPoints 
= [] 
 272         # Set up a default region. Much of the above will be put into 
 273         # the region eventually (the duplication is for compatibility) 
 274         region 
= ShapeRegion() 
 276         region
.SetFont(wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
)) 
 277         region
.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
) 
 278         region
.SetColour("BLACK") 
 279         self
._regions
.append(region
) 
 282         return "<%s.%s>" % (self
.__class
__.__module
__, self
.__class
__.__name
__) 
 284     def GetClassName(self
): 
 285         return str(self
.__class
__).split(".")[-1][:-2] 
 289             i 
= self
._parent
.GetChildren().index(self
) 
 290             self
._parent
.GetChildren(i
).remove(self
) 
 294         self
.ClearAttachments() 
 297             self
._canvas
.RemoveShape(self
) 
 299         self
.GetEventHandler().OnDelete() 
 302         """TRUE if the shape may be dragged by the user.""" 
 305     def SetShape(self
, sh
): 
 306         self
._handlerShape 
= sh
 
 309         """Get the internal canvas.""" 
 312     def GetBranchStyle(self
): 
 313         return self
._branchStyle
 
 315     def GetRotation(self
): 
 316         """Return the angle of rotation in radians.""" 
 317         return self
._rotation
 
 319     def SetRotation(self
, rotation
): 
 320         self
._rotation 
= rotation
 
 322     def SetHighlight(self
, hi
, recurse 
= False): 
 323         """Set the highlight for a shape. Shape highlighting is unimplemented.""" 
 324         self
._highlighted 
= hi
 
 326             for shape 
in self
._children
: 
 327                 shape
.SetHighlight(hi
, recurse
) 
 329     def SetSensitivityFilter(self
, sens 
= OP_ALL
, recursive 
= False): 
 330         """Set the shape to be sensitive or insensitive to specific mouse 
 333         sens is a bitlist of the following: 
 339         * OP_ALL (equivalent to a combination of all the above). 
 341         self
._draggable 
= sens 
& OP_DRAG_LEFT
 
 343         self
._sensitivity 
= sens
 
 345             for shape 
in self
._children
: 
 346                 shape
.SetSensitivityFilter(sens
, True) 
 348     def SetDraggable(self
, drag
, recursive 
= False): 
 349         """Set the shape to be draggable or not draggable.""" 
 350         self
._draggable 
= drag
 
 352             self
._sensitivity |
= OP_DRAG_LEFT
 
 353         elif self
._sensitivity 
& OP_DRAG_LEFT
: 
 354             self
._sensitivity 
-= OP_DRAG_LEFT
 
 357             for shape 
in self
._children
: 
 358                 shape
.SetDraggable(drag
, True) 
 360     def SetDrawHandles(self
, drawH
): 
 361         """Set the drawHandles flag for this shape and all descendants. 
 362         If drawH is TRUE (the default), any handles (control points) will 
 363         be drawn. Otherwise, the handles will not be drawn. 
 365         self
._drawHandles 
= drawH
 
 366         for shape 
in self
._children
: 
 367             shape
.SetDrawHandles(drawH
) 
 369     def SetShadowMode(self
, mode
, redraw 
= False): 
 370         """Set the shadow mode (whether a shadow is drawn or not). 
 371         mode can be one of the following: 
 374           No shadow (the default).  
 376           Shadow on the left side.  
 378           Shadow on the right side. 
 380         if redraw 
and self
.GetCanvas(): 
 381             dc 
= wx
.ClientDC(self
.GetCanvas()) 
 382             self
.GetCanvas().PrepareDC(dc
) 
 384             self
._shadowMode 
= mode
 
 387             self
._shadowMode 
= mode
 
 389     def SetCanvas(self
, theCanvas
): 
 390         """Identical to Shape.Attach.""" 
 391         self
._canvas 
= theCanvas
 
 392         for shape 
in self
._children
: 
 393             shape
.SetCanvas(theCanvas
) 
 395     def AddToCanvas(self
, theCanvas
, addAfter 
= None): 
 396         """Add the shape to the canvas's shape list. 
 397         If addAfter is non-NULL, will add the shape after this one. 
 399         theCanvas
.AddShape(self
, addAfter
) 
 402         for object in self
._children
: 
 403             object.AddToCanvas(theCanvas
, lastImage
) 
 406     def InsertInCanvas(self
, theCanvas
): 
 407         """Insert the shape at the front of the shape list of canvas.""" 
 408         theCanvas
.InsertShape(self
) 
 411         for object in self
._children
: 
 412             object.AddToCanvas(theCanvas
, lastImage
) 
 415     def RemoveFromCanvas(self
, theCanvas
): 
 416         """Remove the shape from the canvas.""" 
 419         theCanvas
.RemoveShape(self
) 
 420         for object in self
._children
: 
 421             object.RemoveFromCanvas(theCanvas
) 
 423     def ClearAttachments(self
): 
 424         """Clear internal custom attachment point shapes (of class 
 427         self
._attachmentPoints 
= [] 
 429     def ClearText(self
, regionId 
= 0): 
 430         """Clear the text from the specified text region.""" 
 433         if regionId 
< len(self
._regions
): 
 434             self
._regions
[regionId
].ClearText() 
 436     def ClearRegions(self
): 
 437         """Clear the ShapeRegions from the shape.""" 
 440     def AddRegion(self
, region
): 
 441         """Add a region to the shape.""" 
 442         self
._regions
.append(region
) 
 444     def SetDefaultRegionSize(self
): 
 445         """Set the default region to be consistent with the shape size.""" 
 446         if not self
._regions
: 
 448         w
, h 
= self
.GetBoundingBoxMax() 
 449         self
._regions
[0].SetSize(w
, h
) 
 451     def HitTest(self
, x
, y
): 
 452         """Given a point on a canvas, returns TRUE if the point was on the 
 453         shape, and returns the nearest attachment point and distance from 
 454         the given point and target. 
 456         width
, height 
= self
.GetBoundingBoxMax() 
 462         width 
+= 4 # Allowance for inaccurate mousing 
 465         left 
= self
._xpos 
- width 
/ 2.0 
 466         top 
= self
._ypos 
- height 
/ 2.0 
 467         right 
= self
._xpos 
+ width 
/ 2.0 
 468         bottom 
= self
._ypos 
+ height 
/ 2.0 
 470         nearest_attachment 
= 0 
 472         # If within the bounding box, check the attachment points 
 474         if x 
>= left 
and x 
<= right 
and y 
>= top 
and y 
<= bottom
: 
 475             n 
= self
.GetNumberOfAttachments() 
 478             # GetAttachmentPosition[Edge] takes a logical attachment position, 
 479             # i.e. if it's rotated through 90%, position 0 is East-facing. 
 482                 e 
= self
.GetAttachmentPositionEdge(i
) 
 485                     l 
= math
.sqrt(((xp 
- x
) * (xp 
- x
)) + (yp 
- y
) * (yp 
- y
)) 
 488                         nearest_attachment 
= i
 
 490             return nearest_attachment
, nearest
 
 493     # Format a text string according to the region size, adding 
 494     # strings with positions to region text list 
 496     def FormatText(self
, dc
, s
, i 
= 0): 
 497         """Reformat the given text region; defaults to formatting the 
 502         if not self
._regions
: 
 505         if i 
> len(self
._regions
): 
 508         region 
= self
._regions
[i
] 
 509         region
._regionText 
= s
 
 510         dc
.SetFont(region
.GetFont()) 
 512         w
, h 
= region
.GetSize() 
 514         stringList 
= FormatText(dc
, s
, (w 
- 2 * self
._textMarginX
), (h 
- 2 * self
._textMarginY
), region
.GetFormatMode()) 
 516             line 
= ShapeTextLine(0.0, 0.0, s
) 
 517             region
.GetFormattedText().append(line
) 
 521         # Don't try to resize an object with more than one image (this 
 522         # case should be dealt with by overriden handlers) 
 523         if (region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
) and \
 
 524            len(region
.GetFormattedText()) and \
 
 525            len(self
._regions
) == 1 and \
 
 526            not Shape
.GraphicsInSizeToContents
: 
 528             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText()) 
 529             if actualW 
+ 2 * self
._textMarginX 
!= w 
or actualH 
+ 2 * self
._textMarginY 
!= h
: 
 530                 # If we are a descendant of a composite, must make sure 
 531                 # the composite gets resized properly 
 533                 topAncestor 
= self
.GetTopAncestor() 
 534                 if topAncestor 
!= self
: 
 535                     Shape
.GraphicsInSizeToContents 
= True 
 537                     composite 
= topAncestor
 
 539                     self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 540                     self
.Move(dc
, self
._xpos
, self
._ypos
) 
 541                     composite
.CalculateSize() 
 542                     if composite
.Selected(): 
 543                         composite
.DeleteControlPoints(dc
) 
 544                         composite
.MakeControlPoints() 
 545                         composite
.MakeMandatoryControlPoints() 
 546                     # Where infinite recursion might happen if we didn't stop it 
 548                     Shape
.GraphicsInSizeToContents 
= False 
 552                 self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 553                 self
.Move(dc
, self
._xpos
, self
._ypos
) 
 554                 self
.EraseContents(dc
) 
 555         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW 
- 2 * self
._textMarginX
, actualH 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 556         self
._formatted 
= True 
 558     def Recentre(self
, dc
): 
 559         """Do recentring (or other formatting) for all the text regions 
 562         w
, h 
= self
.GetBoundingBoxMin() 
 563         for region 
in self
._regions
: 
 564             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w 
- 2 * self
._textMarginX
, h 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 566     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
 567         """Get the point at which the line from (x1, y1) to (x2, y2) hits 
 568         the shape. Returns False if the line doesn't hit the perimeter. 
 572     def SetPen(self
, the_pen
): 
 573         """Set the pen for drawing the shape's outline.""" 
 576     def SetBrush(self
, the_brush
): 
 577         """Set the brush for filling the shape's shape.""" 
 578         self
._brush 
= the_brush
 
 580     # Get the top - most (non-division) ancestor, or self 
 581     def GetTopAncestor(self
): 
 582         """Return the top-most ancestor of this shape (the root of 
 585         if not self
.GetParent(): 
 588         if isinstance(self
.GetParent(), DivisionShape
): 
 590         return self
.GetParent().GetTopAncestor() 
 593     def SetFont(self
, the_font
, regionId 
= 0): 
 594         """Set the font for the specified text region.""" 
 595         self
._font 
= the_font
 
 596         if regionId 
< len(self
._regions
): 
 597             self
._regions
[regionId
].SetFont(the_font
) 
 599     def GetFont(self
, regionId 
= 0): 
 600         """Get the font for the specified text region.""" 
 601         if regionId 
>= len(self
._regions
): 
 603         return self
._regions
[regionId
].GetFont() 
 605     def SetFormatMode(self
, mode
, regionId 
= 0): 
 606         """Set the format mode of the default text region. The argument 
 607         can be a bit list of the following: 
 616         if regionId 
< len(self
._regions
): 
 617             self
._regions
[regionId
].SetFormatMode(mode
) 
 619     def GetFormatMode(self
, regionId 
= 0): 
 620         if regionId 
>= len(self
._regions
): 
 622         return self
._regions
[regionId
].GetFormatMode() 
 624     def SetTextColour(self
, the_colour
, regionId 
= 0): 
 625         """Set the colour for the specified text region.""" 
 626         self
._textColour 
= wx
.TheColourDatabase
.Find(the_colour
) 
 627         self
._textColourName 
= the_colour
 
 629         if regionId 
< len(self
._regions
): 
 630             self
._regions
[regionId
].SetColour(the_colour
) 
 632     def GetTextColour(self
, regionId 
= 0): 
 633         """Get the colour for the specified text region.""" 
 634         if regionId 
>= len(self
._regions
): 
 636         return self
._regions
[regionId
].GetTextColour() 
 638     def SetRegionName(self
, name
, regionId 
= 0): 
 639         """Set the name for this region. 
 640         The name for a region is unique within the scope of the whole 
 641         composite, whereas a region id is unique only for a single image. 
 643         if regionId 
< len(self
._regions
): 
 644             self
._regions
[regionId
].SetName(name
) 
 646     def GetRegionName(self
, regionId 
= 0): 
 647         """Get the region's name. 
 648         A region's name can be used to uniquely determine a region within 
 649         an entire composite image hierarchy. See also Shape.SetRegionName. 
 651         if regionId 
>= len(self
._regions
): 
 653         return self
._regions
[regionId
].GetName() 
 655     def GetRegionId(self
, name
): 
 656         """Get the region's identifier by name. 
 657         This is not unique for within an entire composite, but is unique 
 660         for i
, r 
in enumerate(self
._regions
): 
 661             if r
.GetName() == name
: 
 665     # Name all _regions in all subimages recursively 
 666     def NameRegions(self
, parentName
=""): 
 667         """Make unique names for all the regions in a shape or composite shape.""" 
 668         n 
= self
.GetNumberOfTextRegions() 
 671                 buff 
= parentName
+"."+str(i
) 
 674             self
.SetRegionName(buff
, i
) 
 676         for j
, child 
in enumerate(self
._children
): 
 678                 buff 
= parentName
+"."+str(j
) 
 681             child
.NameRegions(buff
) 
 683     # Get a region by name, possibly looking recursively into composites 
 684     def FindRegion(self
, name
): 
 685         """Find the actual image ('this' if non-composite) and region id 
 686         for the given region name. 
 688         id = self
.GetRegionId(name
) 
 692         for child 
in self
._children
: 
 693             actualImage
, regionId 
= child
.FindRegion(name
) 
 695                 return actualImage
, regionId
 
 699     # Finds all region names for this image (composite or simple). 
 700     def FindRegionNames(self
): 
 701         """Get a list of all region names for this image (composite or simple).""" 
 703         n 
= self
.GetNumberOfTextRegions() 
 705             list.append(self
.GetRegionName(i
)) 
 707         for child 
in self
._children
: 
 708             list += child
.FindRegionNames() 
 712     def AssignNewIds(self
): 
 713         """Assign new ids to this image and its children.""" 
 714         self
._id 
= wx
.NewId() 
 715         for child 
in self
._children
: 
 718     def OnDraw(self
, dc
): 
 721     def OnMoveLinks(self
, dc
): 
 722         # Want to set the ends of all attached links 
 723         # to point to / from this object 
 725         for line 
in self
._lines
: 
 726             line
.GetEventHandler().OnMoveLink(dc
) 
 728     def OnDrawContents(self
, dc
): 
 729         if not self
._regions
: 
 732         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
 737         region 
= self
._regions
[0] 
 739             dc
.SetFont(region
.GetFont()) 
 741         dc
.SetTextForeground(region
.GetActualColourObject()) 
 742         dc
.SetBackgroundMode(wx
.TRANSPARENT
) 
 743         if not self
._formatted
: 
 744             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 745             self
._formatted 
= True 
 747         if not self
.GetDisableLabel(): 
 748             DrawFormattedText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 751     def DrawContents(self
, dc
): 
 752         """Draw the internal graphic of the shape (such as text). 
 754         Do not override this function: override OnDrawContents, which 
 755         is called by this function. 
 757         self
.GetEventHandler().OnDrawContents(dc
) 
 759     def OnSize(self
, x
, y
): 
 762     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 765     def OnErase(self
, dc
): 
 766         if not self
._visible
: 
 770         for line 
in self
._lines
: 
 771             line
.GetEventHandler().OnErase(dc
) 
 773         self
.GetEventHandler().OnEraseContents(dc
) 
 775     def OnEraseContents(self
, dc
): 
 776         if not self
._visible
: 
 779         xp
, yp 
= self
.GetX(), self
.GetY() 
 780         minX
, minY 
= self
.GetBoundingBoxMin() 
 781         maxX
, maxY 
= self
.GetBoundingBoxMax() 
 783         topLeftX 
= xp 
- maxX 
/ 2.0 - 2 
 784         topLeftY 
= yp 
- maxY 
/ 2.0 - 2 
 788             penWidth 
= self
._pen
.GetWidth() 
 790         dc
.SetPen(self
.GetBackgroundPen()) 
 791         dc
.SetBrush(self
.GetBackgroundBrush()) 
 793         dc
.DrawRectangle(topLeftX 
- penWidth
, topLeftY 
- penWidth
, maxX 
+ penWidth 
* 2 + 4, maxY 
+ penWidth 
* 2 + 4) 
 795     def EraseLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 796         """Erase links attached to this shape, but do not repair damage 
 797         caused to other shapes. 
 799         if not self
._visible
: 
 802         for line 
in self
._lines
: 
 803             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 804                 line
.GetEventHandler().OnErase(dc
) 
 807             for child 
in self
._children
: 
 808                 child
.EraseLinks(dc
, attachment
, recurse
) 
 810     def DrawLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 811         """Draws any lines linked to this shape.""" 
 812         if not self
._visible
: 
 815         for line 
in self
._lines
: 
 816             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 820             for child 
in self
._children
: 
 821                 child
.DrawLinks(dc
, attachment
, recurse
) 
 823     #  Returns TRUE if pt1 <= pt2 in the sense that one point comes before 
 824     #  another on an edge of the shape. 
 825     # attachmentPoint is the attachment point (= side) in question. 
 827     # This is the default, rectangular implementation. 
 828     def AttachmentSortTest(self
, attachmentPoint
, pt1
, pt2
): 
 829         """Return TRUE if pt1 is less than or equal to pt2, in the sense 
 830         that one point comes before another on an edge of the shape. 
 832         attachment is the attachment point (side) in question. 
 834         This function is used in Shape.MoveLineToNewAttachment to determine 
 835         the new line ordering. 
 837         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachmentPoint
) 
 838         if physicalAttachment 
in [0, 2]: 
 839             return pt1
[0] <= pt2
[0] 
 840         elif physicalAttachment 
in [1, 3]: 
 841             return pt1
[1] <= pt2
[1] 
 845     def MoveLineToNewAttachment(self
, dc
, to_move
, x
, y
): 
 846         """Move the given line (which must already be attached to the shape) 
 847         to a different attachment point on the shape, or a different order 
 848         on the same attachment. 
 850         Calls Shape.AttachmentSortTest and then 
 851         ShapeEvtHandler.OnChangeAttachment. 
 853         if self
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 856         # Is (x, y) on this object? If so, find the new attachment point 
 857         # the user has moved the point to 
 858         hit 
= self
.HitTest(x
, y
) 
 862         newAttachment
, distance 
= hit
 
 866         if to_move
.GetTo() == self
: 
 867             oldAttachment 
= to_move
.GetAttachmentTo() 
 869             oldAttachment 
= to_move
.GetAttachmentFrom() 
 871         # The links in a new ordering 
 872         # First, add all links to the new list 
 873         newOrdering 
= self
._lines
[:] 
 875         # Delete the line object from the list of links; we're going to move 
 876         # it to another position in the list 
 877         del newOrdering
[newOrdering
.index(to_move
)] 
 884         for line 
in newOrdering
: 
 885             if line
.GetTo() == self 
and oldAttachment 
== line
.GetAttachmentTo() or \
 
 886                line
.GetFrom() == self 
and oldAttachment 
== line
.GetAttachmentFrom(): 
 887                 startX
, startY
, endX
, endY 
= line
.GetEnds() 
 888                 if line
.GetTo() == self
: 
 895                 thisPoint 
= wx
.RealPoint(xp
, yp
) 
 896                 lastPoint 
= wx
.RealPoint(old_x
, old_y
) 
 897                 newPoint 
= wx
.RealPoint(x
, y
) 
 899                 if self
.AttachmentSortTest(newAttachment
, newPoint
, thisPoint
) and self
.AttachmentSortTest(newAttachment
, lastPoint
, newPoint
): 
 901                     newOrdering
.insert(newOrdering
.index(line
), to_move
) 
 909             newOrdering
.append(to_move
) 
 911         self
.GetEventHandler().OnChangeAttachment(newAttachment
, to_move
, newOrdering
) 
 914     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 915         if line
.GetTo() == self
: 
 916             line
.SetAttachmentTo(attachment
) 
 918             line
.SetAttachmentFrom(attachment
) 
 920         self
.ApplyAttachmentOrdering(ordering
) 
 922         dc 
= wx
.ClientDC(self
.GetCanvas()) 
 923         self
.GetCanvas().PrepareDC(dc
) 
 926         if not self
.GetCanvas().GetQuickEditMode(): 
 927             self
.GetCanvas().Redraw(dc
) 
 929     # Reorders the lines according to the given list 
 930     def ApplyAttachmentOrdering(self
, linesToSort
): 
 931         """Apply the line ordering in linesToSort to the shape, to reorder 
 932         the way lines are attached. 
 934         linesStore 
= self
._lines
[:] 
 938         for line 
in linesToSort
: 
 939             if line 
in linesStore
: 
 940                 del linesStore
[linesStore
.index(line
)] 
 941                 self
._lines
.append(line
) 
 943         # Now add any lines that haven't been listed in linesToSort 
 944         self
._lines 
+= linesStore
 
 946     def SortLines(self
, attachment
, linesToSort
): 
 947         """ Reorder the lines coming into the node image at this attachment 
 948         position, in the order in which they appear in linesToSort. 
 950         Any remaining lines not in the list will be added to the end. 
 952         # This is a temporary store of all the lines at this attachment 
 953         # point. We'll tick them off as we've processed them. 
 954         linesAtThisAttachment 
= [] 
 956         for line 
in self
._lines
[:]: 
 957             if line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or \
 
 958                line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
: 
 959                 linesAtThisAttachment
.append(line
) 
 960                 del self
._lines
[self
._lines
.index(line
)] 
 962         for line 
in linesToSort
: 
 963             if line 
in linesAtThisAttachment
: 
 965                 del linesAtThisAttachment
[linesAtThisAttachment
.index(line
)] 
 966                 self
._lines
.append(line
) 
 968         # Now add any lines that haven't been listed in linesToSort 
 969         self
._lines 
+= linesAtThisAttachment
 
 971     def OnHighlight(self
, dc
): 
 974     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 975         if self
._sensitivity 
& OP_CLICK_LEFT 
!= OP_CLICK_LEFT
: 
 977                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 978                 self
._parent
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
 980     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 981         if self
._sensitivity 
& OP_CLICK_RIGHT 
!= OP_CLICK_RIGHT
: 
 982             attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 983             self
._parent
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
 985     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 986         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
 988                 hit 
= self
._parent
.HitTest(x
, y
) 
 990                     attachment
, dist 
= hit
 
 991                 self
._parent
.GetEventHandler().OnDragLeft(draw
, x
, y
, keys
, attachment
) 
 994         dc 
= wx
.ClientDC(self
.GetCanvas()) 
 995         self
.GetCanvas().PrepareDC(dc
) 
 996         dc
.SetLogicalFunction(OGLRBLF
) 
 998         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1000         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1002         xx 
= x 
+ DragOffsetX
 
1003         yy 
= y 
+ DragOffsetY
 
1005         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1006         w
, h 
= self
.GetBoundingBoxMax() 
1007         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1009     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1010         global DragOffsetX
, DragOffsetY
 
1012         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1014                 hit 
= self
._parent
.HitTest(x
, y
) 
1016                     attachment
, dist 
= hit
 
1017                 self
._parent
.GetEventHandler().OnBeginDragLeft(x
, y
, keys
, attachment
) 
1020         DragOffsetX 
= self
._xpos 
- x
 
1021         DragOffsetY 
= self
._ypos 
- y
 
1023         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1024         self
.GetCanvas().PrepareDC(dc
) 
1026         # New policy: don't erase shape until end of drag. 
1028         xx 
= x 
+ DragOffsetX
 
1029         yy 
= y 
+ DragOffsetY
 
1030         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1031         dc
.SetLogicalFunction(OGLRBLF
) 
1033         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1034         dc
.SetPen(dottedPen
) 
1035         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1037         w
, h 
= self
.GetBoundingBoxMax() 
1038         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1039         self
._canvas
.CaptureMouse() 
1041     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1042         if self
._canvas
.HasCapture(): 
1043             self
._canvas
.ReleaseMouse() 
1044         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1046                 hit 
= self
._parent
.HitTest(x
, y
) 
1048                     attachment
, dist 
= hit
 
1049                 self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, attachment
) 
1052         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1053         self
.GetCanvas().PrepareDC(dc
) 
1055         dc
.SetLogicalFunction(wx
.COPY
) 
1056         xx 
= x 
+ DragOffsetX
 
1057         yy 
= y 
+ DragOffsetY
 
1058         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1060         # New policy: erase shape at end of drag. 
1063         self
.Move(dc
, xx
, yy
) 
1064         if self
._canvas 
and not self
._canvas
.GetQuickEditMode(): 
1065             self
._canvas
.Redraw(dc
) 
1067     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1068         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1070                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1071                 self
._parent
.GetEventHandler().OnDragRight(draw
, x
, y
, keys
, attachment
) 
1074     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1075         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1077                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1078                 self
._parent
.GetEventHandler().OnBeginDragRight(x
, y
, keys
, attachment
) 
1081     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1082         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1084                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1085                 self
._parent
.GetEventHandler().OnEndDragRight(x
, y
, keys
, attachment
) 
1088     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
1089         points 
= [[x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1090                 [x 
+ w 
/ 2.0, y 
- h 
/ 2.0], 
1091                 [x 
+ w 
/ 2.0, y 
+ h 
/ 2.0], 
1092                 [x 
- w 
/ 2.0, y 
+ h 
/ 2.0], 
1093                 [x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1096         dc
.DrawLines(points
) 
1098     def Attach(self
, can
): 
1099         """Set the shape's internal canvas pointer to point to the given canvas.""" 
1103         """Disassociates the shape from its canvas.""" 
1106     def Move(self
, dc
, x
, y
, display 
= True): 
1107         """Move the shape to the given position. 
1108         Redraw if display is TRUE. 
1113         if not self
.GetEventHandler().OnMovePre(dc
, x
, y
, old_x
, old_y
, display
): 
1116         self
._xpos
, self
._ypos 
= x
, y
 
1118         self
.ResetControlPoints() 
1125         self
.GetEventHandler().OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
1127     def MoveLinks(self
, dc
): 
1128         """Redraw all the lines attached to the shape.""" 
1129         self
.GetEventHandler().OnMoveLinks(dc
) 
1132         """Draw the whole shape and any lines attached to it. 
1134         Do not override this function: override OnDraw, which is called 
1138             self
.GetEventHandler().OnDraw(dc
) 
1139             self
.GetEventHandler().OnDrawContents(dc
) 
1140             self
.GetEventHandler().OnDrawControlPoints(dc
) 
1141             self
.GetEventHandler().OnDrawBranches(dc
) 
1144         """Flash the shape.""" 
1145         if self
.GetCanvas(): 
1146             dc 
= wx
.ClientDC(self
.GetCanvas()) 
1147             self
.GetCanvas
.PrepareDC(dc
) 
1149             dc
.SetLogicalFunction(OGLRBLF
) 
1151             dc
.SetLogicalFunction(wx
.COPY
) 
1154     def Show(self
, show
): 
1155         """Set a flag indicating whether the shape should be drawn.""" 
1156         self
._visible 
= show
 
1157         for child 
in self
._children
: 
1160     def Erase(self
, dc
): 
1162         Does not repair damage caused to other shapes. 
1164         self
.GetEventHandler().OnErase(dc
) 
1165         self
.GetEventHandler().OnEraseControlPoints(dc
) 
1166         self
.GetEventHandler().OnDrawBranches(dc
, erase 
= True) 
1168     def EraseContents(self
, dc
): 
1169         """Erase the shape contents, that is, the area within the shape's 
1170         minimum bounding box. 
1172         self
.GetEventHandler().OnEraseContents(dc
) 
1174     def AddText(self
, string
): 
1175         """Add a line of text to the shape's default text region.""" 
1176         if not self
._regions
: 
1179         region 
= self
._regions
[0] 
1181         new_line 
= ShapeTextLine(0, 0, string
) 
1182         text 
= region
.GetFormattedText() 
1183         text
.append(new_line
) 
1185         self
._formatted 
= False 
1187     def SetSize(self
, x
, y
, recursive 
= True): 
1188         """Set the shape's size.""" 
1189         self
.SetAttachmentSize(x
, y
) 
1190         self
.SetDefaultRegionSize() 
1192     def SetAttachmentSize(self
, w
, h
): 
1193         width
, height 
= self
.GetBoundingBoxMin() 
1197             scaleX 
= float(w
) / width
 
1201             scaleY 
= float(h
) / height
 
1203         for point 
in self
._attachmentPoints
: 
1204             point
._x 
= point
._x 
* scaleX
 
1205             point
._y 
= point
._y 
* scaleY
 
1207     # Add line FROM this object 
1208     def AddLine(self
, line
, other
, attachFrom 
= 0, attachTo 
= 0, positionFrom 
= -1, positionTo 
= -1): 
1209         """Add a line between this shape and the given other shape, at the 
1210         specified attachment points. 
1212         The position in the list of lines at each end can also be specified, 
1213         so that the line will be drawn at a particular point on its attachment 
1216         if positionFrom 
== -1: 
1217             if not line 
in self
._lines
: 
1218                 self
._lines
.append(line
) 
1220             # Don't preserve old ordering if we have new ordering instructions 
1222                 self
._lines
.remove(line
) 
1225             if positionFrom 
< len(self
._lines
): 
1226                 self
._lines
.insert(positionFrom
, line
) 
1228                 self
._lines
.append(line
) 
1230         if positionTo 
== -1: 
1231             if not other 
in other
._lines
: 
1232                 other
._lines
.append(line
) 
1234             # Don't preserve old ordering if we have new ordering instructions 
1236                 other
._lines
.remove(line
) 
1239             if positionTo 
< len(other
._lines
): 
1240                 other
._lines
.insert(positionTo
, line
) 
1242                 other
._lines
.append(line
) 
1246         line
.SetAttachments(attachFrom
, attachTo
) 
1248         dc 
= wx
.ClientDC(self
._canvas
) 
1249         self
._canvas
.PrepareDC(dc
) 
1252     def RemoveLine(self
, line
): 
1253         """Remove the given line from the shape's list of attached lines.""" 
1254         if line
.GetFrom() == self
: 
1255             line
.GetTo()._lines
.remove(line
) 
1257             line
.GetFrom()._lines
.remove(line
) 
1259         self
._lines
.remove(line
) 
1261     # Default - make 6 control points 
1262     def MakeControlPoints(self
): 
1263         """Make a list of control points (draggable handles) appropriate to 
1266         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1267         minX
, minY 
= self
.GetBoundingBoxMin() 
1269         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1270         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1272         # Offsets from main object 
1273         top 
= -heightMin 
/ 2.0 
1274         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1275         left 
= -widthMin 
/ 2.0 
1276         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1278         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, top
, CONTROL_POINT_DIAGONAL
) 
1279         self
._canvas
.AddShape(control
) 
1280         self
._controlPoints
.append(control
) 
1282         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, top
, CONTROL_POINT_VERTICAL
) 
1283         self
._canvas
.AddShape(control
) 
1284         self
._controlPoints
.append(control
) 
1286         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, top
, CONTROL_POINT_DIAGONAL
) 
1287         self
._canvas
.AddShape(control
) 
1288         self
._controlPoints
.append(control
) 
1290         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, 0, CONTROL_POINT_HORIZONTAL
) 
1291         self
._canvas
.AddShape(control
) 
1292         self
._controlPoints
.append(control
) 
1294         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, bottom
, CONTROL_POINT_DIAGONAL
) 
1295         self
._canvas
.AddShape(control
) 
1296         self
._controlPoints
.append(control
) 
1298         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, bottom
, CONTROL_POINT_VERTICAL
) 
1299         self
._canvas
.AddShape(control
) 
1300         self
._controlPoints
.append(control
) 
1302         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, bottom
, CONTROL_POINT_DIAGONAL
) 
1303         self
._canvas
.AddShape(control
) 
1304         self
._controlPoints
.append(control
) 
1306         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, 0, CONTROL_POINT_HORIZONTAL
) 
1307         self
._canvas
.AddShape(control
) 
1308         self
._controlPoints
.append(control
) 
1310     def MakeMandatoryControlPoints(self
): 
1311         """Make the mandatory control points. 
1313         For example, the control point on a dividing line should appear even 
1314         if the divided rectangle shape's handles should not appear (because 
1315         it is the child of a composite, and children are not resizable). 
1317         for child 
in self
._children
: 
1318             child
.MakeMandatoryControlPoints() 
1320     def ResetMandatoryControlPoints(self
): 
1321         """Reset the mandatory control points.""" 
1322         for child 
in self
._children
: 
1323             child
.ResetMandatoryControlPoints() 
1325     def ResetControlPoints(self
): 
1326         """Reset the positions of the control points (for instance when the 
1327         shape's shape has changed). 
1329         self
.ResetMandatoryControlPoints() 
1331         if len(self
._controlPoints
) == 0: 
1334         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1335         minX
, minY 
= self
.GetBoundingBoxMin() 
1337         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1338         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1340         # Offsets from main object 
1341         top 
= -heightMin 
/ 2.0 
1342         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1343         left 
= -widthMin 
/ 2.0 
1344         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1346         self
._controlPoints
[0]._xoffset 
= left
 
1347         self
._controlPoints
[0]._yoffset 
= top
 
1349         self
._controlPoints
[1]._xoffset 
= 0 
1350         self
._controlPoints
[1]._yoffset 
= top
 
1352         self
._controlPoints
[2]._xoffset 
= right
 
1353         self
._controlPoints
[2]._yoffset 
= top
 
1355         self
._controlPoints
[3]._xoffset 
= right
 
1356         self
._controlPoints
[3]._yoffset 
= 0 
1358         self
._controlPoints
[4]._xoffset 
= right
 
1359         self
._controlPoints
[4]._yoffset 
= bottom
 
1361         self
._controlPoints
[5]._xoffset 
= 0 
1362         self
._controlPoints
[5]._yoffset 
= bottom
 
1364         self
._controlPoints
[6]._xoffset 
= left
 
1365         self
._controlPoints
[6]._yoffset 
= bottom
 
1367         self
._controlPoints
[7]._xoffset 
= left
 
1368         self
._controlPoints
[7]._yoffset 
= 0 
1370     def DeleteControlPoints(self
, dc 
= None): 
1371         """Delete the control points (or handles) for the shape. 
1373         Does not redraw the shape. 
1375         for control 
in self
._controlPoints
: 
1377                 control
.GetEventHandler().OnErase(dc
) 
1378             self
._canvas
.RemoveShape(control
) 
1380         self
._controlPoints 
= [] 
1382         # Children of divisions are contained objects, 
1384         if not isinstance(self
, DivisionShape
): 
1385             for child 
in self
._children
: 
1386                 child
.DeleteControlPoints(dc
) 
1388     def OnDrawControlPoints(self
, dc
): 
1389         if not self
._drawHandles
: 
1392         dc
.SetBrush(wx
.BLACK_BRUSH
) 
1393         dc
.SetPen(wx
.BLACK_PEN
) 
1395         for control 
in self
._controlPoints
: 
1398         # Children of divisions are contained objects, 
1400         # This test bypasses the type facility for speed 
1401         # (critical when drawing) 
1403         if not isinstance(self
, DivisionShape
): 
1404             for child 
in self
._children
: 
1405                 child
.GetEventHandler().OnDrawControlPoints(dc
) 
1407     def OnEraseControlPoints(self
, dc
): 
1408         for control 
in self
._controlPoints
: 
1411         if not isinstance(self
, DivisionShape
): 
1412             for child 
in self
._children
: 
1413                 child
.GetEventHandler().OnEraseControlPoints(dc
) 
1415     def Select(self
, select
, dc 
= None): 
1416         """Select or deselect the given shape, drawing or erasing control points 
1417         (handles) as necessary. 
1419         self
._selected 
= select
 
1421             self
.MakeControlPoints() 
1422             # Children of divisions are contained objects, 
1424             if not isinstance(self
, DivisionShape
): 
1425                 for child 
in self
._children
: 
1426                     child
.MakeMandatoryControlPoints() 
1428                 self
.GetEventHandler().OnDrawControlPoints(dc
) 
1430             self
.DeleteControlPoints(dc
) 
1431             if not isinstance(self
, DivisionShape
): 
1432                 for child 
in self
._children
: 
1433                     child
.DeleteControlPoints(dc
) 
1436         """TRUE if the shape is currently selected.""" 
1437         return self
._selected
 
1439     def AncestorSelected(self
): 
1440         """TRUE if the shape's ancestor is currently selected.""" 
1443         if not self
.GetParent(): 
1445         return self
.GetParent().AncestorSelected() 
1447     def GetNumberOfAttachments(self
): 
1448         """Get the number of attachment points for this shape.""" 
1449         # Should return the MAXIMUM attachment point id here, 
1450         # so higher-level functions can iterate through all attachments, 
1451         # even if they're not contiguous. 
1453         if len(self
._attachmentPoints
) == 0: 
1457             for point 
in self
._attachmentPoints
: 
1458                 if point
._id 
> maxN
: 
1462     def AttachmentIsValid(self
, attachment
): 
1463         """TRUE if attachment is a valid attachment point.""" 
1464         if len(self
._attachmentPoints
) == 0: 
1465             return attachment 
in range(4) 
1467         for point 
in self
._attachmentPoints
: 
1468             if point
._id 
== attachment
: 
1472     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1473         """Get the position at which the given attachment point should be drawn. 
1475         If attachment isn't found among the attachment points of the shape, 
1478         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE
: 
1479             return self
._xpos
, self
._ypos
 
1480         elif self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1481             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, nth
) 
1483         elif self
._attachmentMode 
== ATTACHMENT_MODE_EDGE
: 
1484             if len(self
._attachmentPoints
): 
1485                 for point 
in self
._attachmentPoints
: 
1486                     if point
._id 
== attachment
: 
1487                         return self
._xpos 
+ point
._x
, self
._ypos 
+ point
._y
 
1490                 # Assume is rectangular 
1491                 w
, h 
= self
.GetBoundingBoxMax() 
1492                 top 
= self
._ypos 
+ h 
/ 2.0 
1493                 bottom 
= self
._ypos 
- h 
/ 2.0 
1494                 left 
= self
._xpos 
- w 
/ 2.0 
1495                 right 
= self
._xpos 
+ w 
/ 2.0 
1498                 line 
and line
.IsEnd(self
) 
1500                 physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1503                 if physicalAttachment 
== 0: 
1504                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (right
, bottom
), nth
, no_arcs
, line
) 
1505                 elif physicalAttachment 
== 1: 
1506                     pt 
= self
.CalcSimpleAttachment((right
, bottom
), (right
, top
), nth
, no_arcs
, line
) 
1507                 elif physicalAttachment 
== 2: 
1508                     pt 
= self
.CalcSimpleAttachment((left
, top
), (right
, top
), nth
, no_arcs
, line
) 
1509                 elif physicalAttachment 
== 3: 
1510                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (left
, top
), nth
, no_arcs
, line
) 
1516     def GetBoundingBoxMax(self
): 
1517         """Get the maximum bounding box for the shape, taking into account 
1518         external features such as shadows. 
1520         ww
, hh 
= self
.GetBoundingBoxMin() 
1521         if self
._shadowMode 
!= SHADOW_NONE
: 
1522             ww 
+= self
._shadowOffsetX
 
1523             hh 
+= self
._shadowOffsetY
 
1526     def GetBoundingBoxMin(self
): 
1527         """Get the minimum bounding box for the shape, that defines the area 
1528         available for drawing the contents (such as text). 
1534     def HasDescendant(self
, image
): 
1535         """TRUE if image is a descendant of this composite.""" 
1538         for child 
in self
._children
: 
1539             if child
.HasDescendant(image
): 
1543     # Clears points from a list of wxRealPoints, and clears list 
1544     # Useless in python? /pi 
1545     def ClearPointList(self
, list): 
1548     # Assuming the attachment lies along a vertical or horizontal line, 
1549     # calculate the position on that point. 
1550     def CalcSimpleAttachment(self
, pt1
, pt2
, nth
, noArcs
, line
): 
1551         """Assuming the attachment lies along a vertical or horizontal line, 
1552         calculate the position on that point. 
1557             The first point of the line repesenting the edge of the shape. 
1560             The second point of the line representing the edge of the shape. 
1563             The position on the edge (for example there may be 6 lines at 
1564             this attachment point, and this may be the 2nd line. 
1567             The number of lines at this edge. 
1574         This function expects the line to be either vertical or horizontal, 
1575         and determines which. 
1577         isEnd 
= line 
and line
.IsEnd(self
) 
1579         # Are we horizontal or vertical? 
1580         isHorizontal 
= RoughlyEqual(pt1
[1], pt2
[1]) 
1590             if self
._spaceAttachments
: 
1591                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1592                     # Align line according to the next handle along 
1593                     point 
= line
.GetNextControlPoint(self
) 
1594                     if point
[0] < firstPoint
[0]: 
1596                     elif point
[0] > secondPoint
[0]: 
1601                     x 
= firstPoint
[0] + (nth 
+ 1) * (secondPoint
[0] - firstPoint
[0]) / (noArcs 
+ 1.0) 
1603                 x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 # Midpoint 
1606             assert RoughlyEqual(pt1
[0], pt2
[0]) 
1615             if self
._spaceAttachments
: 
1616                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1617                     # Align line according to the next handle along 
1618                     point 
= line
.GetNextControlPoint(self
) 
1619                     if point
[1] < firstPoint
[1]: 
1621                     elif point
[1] > secondPoint
[1]: 
1626                     y 
= firstPoint
[1] + (nth 
+ 1) * (secondPoint
[1] - firstPoint
[1]) / (noArcs 
+ 1.0) 
1628                 y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 # Midpoint 
1633     # Return the zero-based position in m_lines of line 
1634     def GetLinePosition(self
, line
): 
1635         """Get the zero-based position of line in the list of lines 
1639             return self
._lines
.index(line
) 
1647     # shoulder1 ->---------<- shoulder2 
1649     #                      <- branching attachment point N-1 
1651     def GetBranchingAttachmentInfo(self
, attachment
): 
1652         """Get information about where branching connections go. 
1654         Returns FALSE if there are no lines at this attachment. 
1656         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1658         # Number of lines at this attachment 
1659         lineCount 
= self
.GetAttachmentLineCount(attachment
) 
1664         totalBranchLength 
= self
._branchSpacing 
* (lineCount 
- 1) 
1665         root 
= self
.GetBranchingAttachmentRoot(attachment
) 
1667         neck 
= wx
.RealPoint() 
1668         shoulder1 
= wx
.RealPoint() 
1669         shoulder2 
= wx
.RealPoint() 
1671         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1672         if physicalAttachment 
== 0: 
1673             neck
[0] = self
.GetX() 
1674             neck
[1] = root
[1] - self
._branchNeckLength
 
1676             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1677             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1679             shoulder1
[1] = neck
[1] 
1680             shoulder2
[1] = neck
[1] 
1681         elif physicalAttachment 
== 1: 
1682             neck
[0] = root
[0] + self
._branchNeckLength
 
1685             shoulder1
[0] = neck
[0] 
1686             shoulder2
[0] = neck
[0] 
1688             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1689             shoulder1
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1690         elif physicalAttachment 
== 2: 
1691             neck
[0] = self
.GetX() 
1692             neck
[1] = root
[1] + self
._branchNeckLength
 
1694             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1695             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1697             shoulder1
[1] = neck
[1] 
1698             shoulder2
[1] = neck
[1] 
1699         elif physicalAttachment 
== 3: 
1700             neck
[0] = root
[0] - self
._branchNeckLength
 
1703             shoulder1
[0] = neck
[0] 
1704             shoulder2
[0] = neck
[0] 
1706             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1707             shoulder2
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1709             raise "Unrecognised attachment point in GetBranchingAttachmentInfo" 
1710         return root
, neck
, shoulder1
, shoulder2
 
1712     def GetBranchingAttachmentPoint(self
, attachment
, n
): 
1713         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1715         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1717         stemPt 
= wx
.RealPoint() 
1719         if physicalAttachment 
== 0: 
1720             pt
[1] = neck
[1] - self
._branchStemLength
 
1721             pt
[0] = shoulder1
[0] + n 
* self
._branchSpacing
 
1725         elif physicalAttachment 
== 2: 
1726             pt
[1] = neck
[1] + self
._branchStemLength
 
1727             pt
[0] = shoulder1
[0] + n 
* self
._branchStemLength
 
1731         elif physicalAttachment 
== 1: 
1732             pt
[0] = neck
[0] + self
._branchStemLength
 
1733             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1737         elif physicalAttachment 
== 3: 
1738             pt
[0] = neck
[0] - self
._branchStemLength
 
1739             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1744             raise "Unrecognised attachment point in GetBranchingAttachmentPoint" 
1748     def GetAttachmentLineCount(self
, attachment
): 
1749         """Get the number of lines at this attachment position.""" 
1751         for lineShape 
in self
._lines
: 
1752             if lineShape
.GetFrom() == self 
and lineShape
.GetAttachmentFrom() == attachment
: 
1754             elif lineShape
.GetTo() == self 
and lineShape
.GetAttachmentTo() == attachment
: 
1758     def GetBranchingAttachmentRoot(self
, attachment
): 
1759         """Get the root point at the given attachment.""" 
1760         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1762         root 
= wx
.RealPoint() 
1764         width
, height 
= self
.GetBoundingBoxMax() 
1766         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1767         if physicalAttachment 
== 0: 
1768             root
[0] = self
.GetX() 
1769             root
[1] = self
.GetY() - height 
/ 2.0 
1770         elif physicalAttachment 
== 1: 
1771             root
[0] = self
.GetX() + width 
/ 2.0 
1772             root
[1] = self
.GetY() 
1773         elif physicalAttachment 
== 2: 
1774             root
[0] = self
.GetX() 
1775             root
[1] = self
.GetY() + height 
/ 2.0 
1776         elif physicalAttachment 
== 3: 
1777             root
[0] = self
.GetX() - width 
/ 2.0 
1778             root
[1] = self
.GetY() 
1780             raise "Unrecognised attachment point in GetBranchingAttachmentRoot" 
1784     # Draw or erase the branches (not the actual arcs though) 
1785     def OnDrawBranchesAttachment(self
, dc
, attachment
, erase 
= False): 
1786         count 
= self
.GetAttachmentLineCount(attachment
) 
1790         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1793             dc
.SetPen(wx
.WHITE_PEN
) 
1794             dc
.SetBrush(wx
.WHITE_BRUSH
) 
1796             dc
.SetPen(wx
.BLACK_PEN
) 
1797             dc
.SetBrush(wx
.BLACK_BRUSH
) 
1800         dc
.DrawLine(root
[0], root
[1], neck
[0], neck
[1]) 
1803             # Draw shoulder-to-shoulder line 
1804             dc
.DrawLine(shoulder1
[0], shoulder1
[1], shoulder2
[0], shoulder2
[1]) 
1805         # Draw all the little branches 
1806         for i 
in range(count
): 
1807             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, i
) 
1808             dc
.DrawLine(stemPt
[0], stemPt
[1], pt
[0], pt
[1]) 
1810             if self
.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB 
and count 
> 1: 
1812                 dc
.DrawEllipse(stemPt
[0] - blobSize 
/ 2.0, stemPt
[1] - blobSize 
/ 2.0, blobSize
, blobSize
) 
1814     def OnDrawBranches(self
, dc
, erase 
= False): 
1815         if self
._attachmentMode 
!= ATTACHMENT_MODE_BRANCHING
: 
1817         for i 
in range(self
.GetNumberOfAttachments()): 
1818             self
.OnDrawBranchesAttachment(dc
, i
, erase
) 
1820     def GetAttachmentPositionEdge(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1821         """ Only get the attachment position at the _edge_ of the shape, 
1822         ignoring branching mode. This is used e.g. to indicate the edge of 
1823         interest, not the point on the attachment branch. 
1825         oldMode 
= self
._attachmentMode
 
1827         # Calculate as if to edge, not branch 
1828         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1829             self
._attachmentMode 
= ATTACHMENT_MODE_EDGE
 
1830         res 
= self
.GetAttachmentPosition(attachment
, nth
, no_arcs
, line
) 
1831         self
._attachmentMode 
= oldMode
 
1835     def PhysicalToLogicalAttachment(self
, physicalAttachment
): 
1836         """ Rotate the standard attachment point from physical 
1837         (0 is always North) to logical (0 -> 1 if rotated by 90 degrees) 
1839         if RoughlyEqual(self
.GetRotation(), 0): 
1840             i 
= physicalAttachment
 
1841         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1842             i 
= physicalAttachment 
- 1 
1843         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1844             i 
= physicalAttachment 
- 2 
1845         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1846             i 
= physicalAttachment 
- 3 
1848             # Can't handle -- assume the same 
1849             return physicalAttachment
 
1856     def LogicalToPhysicalAttachment(self
, logicalAttachment
): 
1857         """Rotate the standard attachment point from logical 
1858         to physical (0 is always North). 
1860         if RoughlyEqual(self
.GetRotation(), 0): 
1861             i 
= logicalAttachment
 
1862         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1863             i 
= logicalAttachment 
+ 1 
1864         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1865             i 
= logicalAttachment 
+ 2 
1866         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1867             i 
= logicalAttachment 
+ 3 
1869             return logicalAttachment
 
1876     def Rotate(self
, x
, y
, theta
): 
1877         """Rotate about the given axis by the given amount in radians.""" 
1878         self
._rotation 
= theta
 
1879         if self
._rotation 
< 0: 
1880             self
._rotation 
+= 2 * math
.pi
 
1881         elif self
._rotation 
> 2 * math
.pi
: 
1882             self
._rotation 
-= 2 * math
.pi
 
1884     def GetBackgroundPen(self
): 
1885         """Return pen of the right colour for the background.""" 
1886         if self
.GetCanvas(): 
1887             return wx
.Pen(self
.GetCanvas().GetBackgroundColour(), 1, wx
.SOLID
) 
1888         return WhiteBackgroundPen
 
1890     def GetBackgroundBrush(self
): 
1891         """Return brush of the right colour for the background.""" 
1892         if self
.GetCanvas(): 
1893             return wx
.Brush(self
.GetCanvas().GetBackgroundColour(), wx
.SOLID
) 
1894         return WhiteBackgroundBrush
 
1897         """Get the x position of the centre of the shape.""" 
1901         """Get the y position of the centre of the shape.""" 
1905         """Set the x position of the shape.""" 
1909         """Set the y position of the shape.""" 
1912     def GetParent(self
): 
1913         """Return the parent of this shape, if it is part of a composite.""" 
1916     def SetParent(self
, p
): 
1919     def GetChildren(self
): 
1920         """Return the list of children for this shape.""" 
1921         return self
._children
 
1923     def GetDrawHandles(self
): 
1924         """Return the list of drawhandles.""" 
1925         return self
._drawHandles
 
1927     def GetEventHandler(self
): 
1928         """Return the event handler for this shape.""" 
1929         return self
._eventHandler
 
1931     def SetEventHandler(self
, handler
): 
1932         """Set the event handler for this shape.""" 
1933         self
._eventHandler 
= handler
 
1935     def Recompute(self
): 
1936         """Recomputes any constraints associated with the shape. 
1938         Normally applicable to CompositeShapes only, but harmless for 
1939         other classes of Shape. 
1943     def IsHighlighted(self
): 
1944         """TRUE if the shape is highlighted. Shape highlighting is unimplemented.""" 
1945         return self
._highlighted
 
1947     def GetSensitivityFilter(self
): 
1948         """Return the sensitivity filter, a bitlist of values. 
1950         See Shape.SetSensitivityFilter. 
1952         return self
._sensitivity
 
1954     def SetFixedSize(self
, x
, y
): 
1955         """Set the shape to be fixed size.""" 
1956         self
._fixedWidth 
= x
 
1957         self
._fixedHeight 
= y
 
1959     def GetFixedSize(self
): 
1960         """Return flags indicating whether the shape is of fixed size in 
1963         return self
._fixedWidth
, self
._fixedHeight
 
1965     def GetFixedWidth(self
): 
1966         """TRUE if the shape cannot be resized in the horizontal plane.""" 
1967         return self
._fixedWidth
 
1969     def GetFixedHeight(self
): 
1970         """TRUE if the shape cannot be resized in the vertical plane.""" 
1971         return self
._fixedHeight
 
1973     def SetSpaceAttachments(self
, sp
): 
1974         """Indicate whether lines should be spaced out evenly at the point 
1975         they touch the node (sp = True), or whether they should join at a single 
1978         self
._spaceAttachments 
= sp
 
1980     def GetSpaceAttachments(self
): 
1981         """Return whether lines should be spaced out evenly at the point they 
1982         touch the node (True), or whether they should join at a single point 
1985         return self
._spaceAttachments
 
1987     def SetCentreResize(self
, cr
): 
1988         """Specify whether the shape is to be resized from the centre (the 
1989         centre stands still) or from the corner or side being dragged (the 
1990         other corner or side stands still). 
1992         self
._centreResize 
= cr
 
1994     def GetCentreResize(self
): 
1995         """TRUE if the shape is to be resized from the centre (the centre stands 
1996         still), or FALSE if from the corner or side being dragged (the other 
1997         corner or side stands still) 
1999         return self
._centreResize
 
2001     def SetMaintainAspectRatio(self
, ar
): 
2002         """Set whether a shape that resizes should not change the aspect ratio 
2003         (width and height should be in the original proportion). 
2005         self
._maintainAspectRatio 
= ar
 
2007     def GetMaintainAspectRatio(self
): 
2008         """TRUE if shape keeps aspect ratio during resize.""" 
2009         return self
._maintainAspectRatio
 
2012         """Return the list of lines connected to this shape.""" 
2015     def SetDisableLabel(self
, flag
): 
2016         """Set flag to TRUE to stop the default region being shown.""" 
2017         self
._disableLabel 
= flag
 
2019     def GetDisableLabel(self
): 
2020         """TRUE if the default region will not be shown, FALSE otherwise.""" 
2021         return self
._disableLabel
 
2023     def SetAttachmentMode(self
, mode
): 
2024         """Set the attachment mode. 
2026         If TRUE, attachment points will be significant when drawing lines to 
2027         and from this shape. 
2028         If FALSE, lines will be drawn as if to the centre of the shape. 
2030         self
._attachmentMode 
= mode
 
2032     def GetAttachmentMode(self
): 
2033         """Return the attachment mode. 
2035         See Shape.SetAttachmentMode. 
2037         return self
._attachmentMode
 
2040         """Set the integer identifier for this shape.""" 
2044         """Return the integer identifier for this shape.""" 
2048         """TRUE if the shape is in a visible state, FALSE otherwise. 
2050         Note that this has nothing to do with whether the window is hidden 
2051         or the shape has scrolled off the canvas; it refers to the internal 
2054         return self
._visible
 
2057         """Return the pen used for drawing the shape's outline.""" 
2061         """Return the brush used for filling the shape.""" 
2064     def GetNumberOfTextRegions(self
): 
2065         """Return the number of text regions for this shape.""" 
2066         return len(self
._regions
) 
2068     def GetRegions(self
): 
2069         """Return the list of ShapeRegions.""" 
2070         return self
._regions
 
2072     # Control points ('handles') redirect control to the actual shape, to 
2073     # make it easier to override sizing behaviour. 
2074     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2075         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2077         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2078         self
.GetCanvas().PrepareDC(dc
) 
2080         dc
.SetLogicalFunction(OGLRBLF
) 
2082         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2083         dc
.SetPen(dottedPen
) 
2084         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2086         if self
.GetCentreResize(): 
2087             # Maintain the same centre point 
2088             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2089             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2091             # Constrain sizing according to what control point you're dragging 
2092             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2093                 if self
.GetMaintainAspectRatio(): 
2094                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2096                     new_height 
= bound_y
 
2097             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2098                 if self
.GetMaintainAspectRatio(): 
2099                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2102             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2103                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2105             if self
.GetFixedWidth(): 
2108             if self
.GetFixedHeight(): 
2109                 new_height 
= bound_y
 
2111             pt
._controlPointDragEndWidth 
= new_width
 
2112             pt
._controlPointDragEndHeight 
= new_height
 
2114             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2116             # Don't maintain the same centre point 
2117             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2118             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2119             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2120             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2121             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2122                 newY1 
= pt
._controlPointDragStartY
 
2123                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2124             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2125                 newX1 
= pt
._controlPointDragStartX
 
2126                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2127             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2128                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2129                 if self
.GetY() > pt
._controlPointDragStartY
: 
2130                     newY2 
= newY1 
+ newH
 
2132                     newY1 
= newY2 
- newH
 
2134             newWidth 
= float(newX2 
- newX1
) 
2135             newHeight 
= float(newY2 
- newY1
) 
2137             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2138                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2140             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2141                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2143             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2144             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2145             if self
.GetFixedWidth(): 
2148             if self
.GetFixedHeight(): 
2151             pt
._controlPointDragEndWidth 
= newWidth
 
2152             pt
._controlPointDragEndHeight 
= newHeight
 
2153             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2155     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2156         self
._canvas
.CaptureMouse() 
2158         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2159         self
.GetCanvas().PrepareDC(dc
) 
2161         dc
.SetLogicalFunction(OGLRBLF
) 
2163         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2164         self
.GetEventHandler().OnEndSize(bound_x
, bound_y
) 
2166         # Choose the 'opposite corner' of the object as the stationary 
2167         # point in case this is non-centring resizing. 
2168         if pt
.GetX() < self
.GetX(): 
2169             pt
._controlPointDragStartX 
= self
.GetX() + bound_x 
/ 2.0 
2171             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2173         if pt
.GetY() < self
.GetY(): 
2174             pt
._controlPointDragStartY 
= self
.GetY() + bound_y 
/ 2.0 
2176             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2178         if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2179             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2180         elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2181             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2183         # We may require the old width and height 
2184         pt
._controlPointDragStartWidth 
= bound_x
 
2185         pt
._controlPointDragStartHeight 
= bound_y
 
2187         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2188         dc
.SetPen(dottedPen
) 
2189         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2191         if self
.GetCentreResize(): 
2192             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2193             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2195             # Constrain sizing according to what control point you're dragging 
2196             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2197                 if self
.GetMaintainAspectRatio(): 
2198                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2200                     new_height 
= bound_y
 
2201             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2202                 if self
.GetMaintainAspectRatio(): 
2203                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2206             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2207                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2209             if self
.GetFixedWidth(): 
2212             if self
.GetFixedHeight(): 
2213                 new_height 
= bound_y
 
2215             pt
._controlPointDragEndWidth 
= new_width
 
2216             pt
._controlPointDragEndHeight 
= new_height
 
2217             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2219             # Don't maintain the same centre point 
2220             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2221             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2222             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2223             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2224             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2225                 newY1 
= pt
._controlPointDragStartY
 
2226                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2227             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2228                 newX1 
= pt
._controlPointDragStartX
 
2229                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2230             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2231                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2232                 if pt
.GetY() > pt
._controlPointDragStartY
: 
2233                     newY2 
= newY1 
+ newH
 
2235                     newY1 
= newY2 
- newH
 
2237             newWidth 
= float(newX2 
- newX1
) 
2238             newHeight 
= float(newY2 
- newY1
) 
2240             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2241                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2243             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2244                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2246             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2247             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2248             if self
.GetFixedWidth(): 
2251             if self
.GetFixedHeight(): 
2254             pt
._controlPointDragEndWidth 
= newWidth
 
2255             pt
._controlPointDragEndHeight 
= newHeight
 
2256             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2258     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2259         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2260         self
.GetCanvas().PrepareDC(dc
) 
2262         if self
._canvas
.HasCapture(): 
2263             self
._canvas
.ReleaseMouse() 
2264         dc
.SetLogicalFunction(wx
.COPY
) 
2266         self
.ResetControlPoints() 
2270         self
.SetSize(pt
._controlPointDragEndWidth
, pt
._controlPointDragEndHeight
) 
2272         # The next operation could destroy this control point (it does for 
2273         # label objects, via formatting the text), so save all values we're 
2274         # going to use, or we'll be accessing garbage. 
2278         if self
.GetCentreResize(): 
2279             self
.Move(dc
, self
.GetX(), self
.GetY()) 
2281             self
.Move(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
) 
2283         # Recursively redraw links if we have a composite 
2284         if len(self
.GetChildren()): 
2285             self
.DrawLinks(dc
, -1, True) 
2287         width
, height 
= self
.GetBoundingBoxMax() 
2288         self
.GetEventHandler().OnEndSize(width
, height
) 
2290         if not self
._canvas
.GetQuickEditMode() and pt
._eraseObject
: 
2291             self
._canvas
.Redraw(dc
) 
2295 class RectangleShape(Shape
): 
2297     The wxRectangleShape has rounded or square corners. 
2302     def __init__(self
, w 
= 0.0, h 
= 0.0): 
2303         Shape
.__init
__(self
) 
2306         self
._cornerRadius 
= 0.0 
2307         self
.SetDefaultRegionSize() 
2309     def OnDraw(self
, dc
): 
2310         x1 
= self
._xpos 
- self
._width 
/ 2.0 
2311         y1 
= self
._ypos 
- self
._height 
/ 2.0 
2313         if self
._shadowMode 
!= SHADOW_NONE
: 
2314             if self
._shadowBrush
: 
2315                 dc
.SetBrush(self
._shadowBrush
) 
2316             dc
.SetPen(TransparentPen
) 
2318             if self
._cornerRadius
: 
2319                 dc
.DrawRoundedRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
, self
._cornerRadius
) 
2321                 dc
.DrawRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
) 
2324             if self
._pen
.GetWidth() == 0: 
2325                 dc
.SetPen(TransparentPen
) 
2327                 dc
.SetPen(self
._pen
) 
2329             dc
.SetBrush(self
._brush
) 
2331         if self
._cornerRadius
: 
2332             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
2334             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
2336     def GetBoundingBoxMin(self
): 
2337         return self
._width
, self
._height
 
2339     def SetSize(self
, x
, y
, recursive 
= False): 
2340         self
.SetAttachmentSize(x
, y
) 
2341         self
._width 
= max(x
, 1) 
2342         self
._height 
= max(y
, 1) 
2343         self
.SetDefaultRegionSize() 
2345     def GetCornerRadius(self
): 
2346         """Get the radius of the rectangle's rounded corners.""" 
2347         return self
._cornerRadius
 
2349     def SetCornerRadius(self
, rad
): 
2350         """Set the radius of the rectangle's rounded corners. 
2352         If the radius is zero, a non-rounded rectangle will be drawn. 
2353         If the radius is negative, the value is the proportion of the smaller 
2354         dimension of the rectangle. 
2356         self
._cornerRadius 
= rad
 
2358     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2359     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2360         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2361         return FindEndForBox(bound_x
, bound_y
, self
._xpos
, self
._ypos
, x2
, y2
) 
2366     def GetHeight(self
): 
2369     def SetWidth(self
, w
): 
2372     def SetHeight(self
, h
): 
2377 class PolygonShape(Shape
): 
2378     """A PolygonShape's shape is defined by a number of points passed to 
2379     the object's constructor. It can be used to create new shapes such as 
2380     diamonds and triangles. 
2383         Shape
.__init
__(self
) 
2386         self
._originalPoints 
= None 
2388     def Create(self
, the_points 
= None): 
2389         """Takes a list of wx.RealPoints or tuples; each point is an offset 
2395             self
._originalPoints 
= [] 
2398             self
._originalPoints 
= the_points
 
2400             # Duplicate the list of points 
2402             for point 
in the_points
: 
2403                 new_point 
= wx
.Point(point
[0], point
[1]) 
2404                 self
._points
.append(new_point
) 
2405             self
.CalculateBoundingBox() 
2406             self
._originalWidth 
= self
._boundWidth
 
2407             self
._originalHeight 
= self
._boundHeight
 
2408             self
.SetDefaultRegionSize() 
2410     def ClearPoints(self
): 
2412         self
._originalPoints 
= [] 
2414     # Width and height. Centre of object is centre of box 
2415     def GetBoundingBoxMin(self
): 
2416         return self
._boundWidth
, self
._boundHeight
 
2418     def GetPoints(self
): 
2419         """Return the internal list of polygon vertices.""" 
2422     def GetOriginalPoints(self
): 
2423         return self
._originalPoints
 
2425     def GetOriginalWidth(self
): 
2426         return self
._originalWidth
 
2428     def GetOriginalHeight(self
): 
2429         return self
._originalHeight
 
2431     def SetOriginalWidth(self
, w
): 
2432         self
._originalWidth 
= w
 
2434     def SetOriginalHeight(self
, h
): 
2435         self
._originalHeight 
= h
 
2437     def CalculateBoundingBox(self
): 
2438         # Calculate bounding box at construction (and presumably resize) time 
2444         for point 
in self
._points
: 
2447             if point
[0] > right
: 
2452             if point
[1] > bottom
: 
2455         self
._boundWidth 
= right 
- left
 
2456         self
._boundHeight 
= bottom 
- top
 
2458     def CalculatePolygonCentre(self
): 
2459         """Recalculates the centre of the polygon, and 
2460         readjusts the point offsets accordingly. 
2461         Necessary since the centre of the polygon 
2462         is expected to be the real centre of the bounding 
2470         for point 
in self
._points
: 
2473             if point
[0] > right
: 
2478             if point
[1] > bottom
: 
2481         bwidth 
= right 
- left
 
2482         bheight 
= bottom 
- top
 
2484         newCentreX 
= left 
+ bwidth 
/ 2.0 
2485         newCentreY 
= top 
+ bheight 
/ 2.0 
2487         for i 
in range(len(self
._points
)): 
2488             self
._points
[i
] = self
._points
[i
][0] - newCentreX
, self
._points
[i
][1] - newCentreY
 
2489         self
._xpos 
+= newCentreX
 
2490         self
._ypos 
+= newCentreY
 
2492     def HitTest(self
, x
, y
): 
2493         # Imagine four lines radiating from this point. If all of these lines 
2494         # hit the polygon, we're inside it, otherwise we're not. Obviously 
2495         # we'd need more radiating lines to be sure of correct results for 
2496         # very strange (concave) shapes. 
2497         endPointsX 
= [x
, x 
+ 1000, x
, x 
- 1000] 
2498         endPointsY 
= [y 
- 1000, y
, y 
+ 1000, y
] 
2503         for point 
in self
._points
: 
2504             xpoints
.append(point
[0] + self
._xpos
) 
2505             ypoints
.append(point
[1] + self
._ypos
) 
2507         # We assume it's inside the polygon UNLESS one or more 
2508         # lines don't hit the outline. 
2512             if not PolylineHitTest(xpoints
, ypoints
, x
, y
, endPointsX
[i
], endPointsY
[i
]): 
2518         nearest_attachment 
= 0 
2520         # If a hit, check the attachment points within the object 
2523         for i 
in range(self
.GetNumberOfAttachments()): 
2524             e 
= self
.GetAttachmentPositionEdge(i
) 
2527                 l 
= math
.sqrt((xp 
- x
) * (xp 
- x
) + (yp 
- y
) * (yp 
- y
)) 
2530                     nearest_attachment 
= i
 
2532         return nearest_attachment
, nearest
 
2534     # Really need to be able to reset the shape! Otherwise, if the 
2535     # points ever go to zero, we've lost it, and can't resize. 
2536     def SetSize(self
, new_width
, new_height
, recursive 
= True): 
2537         self
.SetAttachmentSize(new_width
, new_height
) 
2539         # Multiply all points by proportion of new size to old size 
2540         x_proportion 
= abs(float(new_width
) / self
._originalWidth
) 
2541         y_proportion 
= abs(float(new_height
) / self
._originalHeight
) 
2543         for i 
in range(max(len(self
._points
), len(self
._originalPoints
))): 
2544             self
._points
[i
] = wx
.Point(self
._originalPoints
[i
][0] * x_proportion
, self
._originalPoints
[i
][1] * y_proportion
) 
2546         self
._boundWidth 
= abs(new_width
) 
2547         self
._boundHeight 
= abs(new_height
) 
2548         self
.SetDefaultRegionSize() 
2550     # Make the original points the same as the working points 
2551     def UpdateOriginalPoints(self
): 
2552         """If we've changed the shape, must make the original points match the 
2553         working points with this function. 
2555         self
._originalPoints 
= [] 
2557         for point 
in self
._points
: 
2558             original_point 
= wx
.RealPoint(point
[0], point
[1]) 
2559             self
._originalPoints
.append(original_point
) 
2561         self
.CalculateBoundingBox() 
2562         self
._originalWidth 
= self
._boundWidth
 
2563         self
._originalHeight 
= self
._boundHeight
 
2565     def AddPolygonPoint(self
, pos
): 
2566         """Add a control point after the given point.""" 
2568             firstPoint 
= self
._points
[pos
] 
2570             firstPoint 
= self
._points
[0] 
2573             secondPoint 
= self
._points
[pos 
+ 1] 
2575             secondPoint 
= self
._points
[0] 
2577         x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 + firstPoint
[0] 
2578         y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 + firstPoint
[1] 
2579         point 
= wx
.RealPoint(x
, y
) 
2581         if pos 
>= len(self
._points
) - 1: 
2582             self
._points
.append(point
) 
2584             self
._points
.insert(pos 
+ 1, point
) 
2586         self
.UpdateOriginalPoints() 
2589             self
.DeleteControlPoints() 
2590             self
.MakeControlPoints() 
2592     def DeletePolygonPoint(self
, pos
): 
2593         """Delete the given control point.""" 
2594         if pos 
< len(self
._points
): 
2595             del self
._points
[pos
] 
2596             self
.UpdateOriginalPoints() 
2598                 self
.DeleteControlPoints() 
2599                 self
.MakeControlPoints() 
2601     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2602     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2603         # First check for situation where the line is vertical, 
2604         # and we would want to connect to a point on that vertical -- 
2605         # oglFindEndForPolyline can't cope with this (the arrow 
2606         # gets drawn to the wrong place). 
2607         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE 
and x1 
== x2
: 
2608             # Look for the point we'd be connecting to. This is 
2610             for point 
in self
._points
: 
2612                     if y2 
> y1 
and point
[1] > 0: 
2613                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2614                     elif y2 
< y1 
and point
[1] < 0: 
2615                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2619         for point 
in self
._points
: 
2620             xpoints
.append(point
[0] + self
._xpos
) 
2621             ypoints
.append(point
[1] + self
._ypos
) 
2623         return FindEndForPolyline(xpoints
, ypoints
, x1
, y1
, x2
, y2
) 
2625     def OnDraw(self
, dc
): 
2626         if self
._shadowMode 
!= SHADOW_NONE
: 
2627             if self
._shadowBrush
: 
2628                 dc
.SetBrush(self
._shadowBrush
) 
2629             dc
.SetPen(TransparentPen
) 
2631             dc
.DrawPolygon(self
._points
, self
._xpos 
+ self
._shadowOffsetX
, self
._ypos
, self
._shadowOffsetY
) 
2634             if self
._pen
.GetWidth() == 0: 
2635                 dc
.SetPen(TransparentPen
) 
2637                 dc
.SetPen(self
._pen
) 
2639             dc
.SetBrush(self
._brush
) 
2640         dc
.DrawPolygon(self
._points
, self
._xpos
, self
._ypos
) 
2642     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
2643         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2644         # Multiply all points by proportion of new size to old size 
2645         x_proportion 
= abs(float(w
) / self
._originalWidth
) 
2646         y_proportion 
= abs(float(h
) / self
._originalHeight
) 
2649         for point 
in self
._originalPoints
: 
2650             intPoints
.append(wx
.Point(x_proportion 
* point
[0], y_proportion 
* point
[1])) 
2651         dc
.DrawPolygon(intPoints
, x
, y
) 
2653     # Make as many control points as there are vertices 
2654     def MakeControlPoints(self
): 
2655         for point 
in self
._points
: 
2656             control 
= PolygonControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
, point
[0], point
[1]) 
2657             self
._canvas
.AddShape(control
) 
2658             self
._controlPoints
.append(control
) 
2660     def ResetControlPoints(self
): 
2661         for i 
in range(min(len(self
._points
), len(self
._controlPoints
))): 
2662             point 
= self
._points
[i
] 
2663             self
._controlPoints
[i
]._xoffset 
= point
[0] 
2664             self
._controlPoints
[i
]._yoffset 
= point
[1] 
2665             self
._controlPoints
[i
].polygonVertex 
= point
 
2667     def GetNumberOfAttachments(self
): 
2668         maxN 
= max(len(self
._points
) - 1, 0) 
2669         for point 
in self
._attachmentPoints
: 
2670             if point
._id 
> maxN
: 
2674     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2675         if self
._attachmentMode 
== ATTACHMENT_MODE_EDGE 
and self
._points 
and attachment 
< len(self
._points
): 
2676             point 
= self
._points
[0] 
2677             return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2678         return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2680     def AttachmentIsValid(self
, attachment
): 
2681         if not self
._points
: 
2684         if attachment 
>= 0 and attachment 
< len(self
._points
): 
2687         for point 
in self
._attachmentPoints
: 
2688             if point
._id 
== attachment
: 
2693     # Rotate about the given axis by the given amount in radians 
2694     def Rotate(self
, x
, y
, theta
): 
2695         actualTheta 
= theta 
- self
._rotation
 
2697         # Rotate attachment points 
2698         sinTheta 
= math
.sin(actualTheta
) 
2699         cosTheta 
= math
.cos(actualTheta
) 
2701         for point 
in self
._attachmentPoints
: 
2705             point
._x 
= x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
 
2706             point
._y 
= x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2708         for i 
in range(len(self
._points
)): 
2709             x1
, y1 
= self
._points
[i
] 
2711             self
._points
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2713         for i 
in range(len(self
._originalPoints
)): 
2714             x1
, y1 
= self
._originalPoints
[i
] 
2716             self
._originalPoints
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2718         # Added by Pierre Hjälm. If we don't do this the outline will be 
2719         # the wrong size. Hopefully it won't have any ill effects. 
2720         self
.UpdateOriginalPoints() 
2722         self
._rotation 
= theta
 
2724         self
.CalculatePolygonCentre() 
2725         self
.CalculateBoundingBox() 
2726         self
.ResetControlPoints() 
2728     # Control points ('handles') redirect control to the actual shape, to 
2729     # make it easier to override sizing behaviour. 
2730     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2731         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2732         self
.GetCanvas().PrepareDC(dc
) 
2734         dc
.SetLogicalFunction(OGLRBLF
) 
2736         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2737         dc
.SetPen(dottedPen
) 
2738         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2740         # Code for CTRL-drag in C++ version commented out 
2742         pt
.CalculateNewSize(x
, y
) 
2744         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2746     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2747         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2748         self
.GetCanvas().PrepareDC(dc
) 
2752         dc
.SetLogicalFunction(OGLRBLF
) 
2754         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2756         dist 
= math
.sqrt((x 
- self
.GetX()) * (x 
- self
.GetX()) + (y 
- self
.GetY()) * (y 
- self
.GetY())) 
2758         pt
._originalDistance 
= dist
 
2759         pt
._originalSize
[0] = bound_x
 
2760         pt
._originalSize
[1] = bound_y
 
2762         if pt
._originalDistance 
== 0: 
2763             pt
._originalDistance 
= 0.0001 
2765         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2766         dc
.SetPen(dottedPen
) 
2767         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2769         # Code for CTRL-drag in C++ version commented out 
2771         pt
.CalculateNewSize(x
, y
) 
2773         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2775         self
._canvas
.CaptureMouse() 
2777     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2778         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2779         self
.GetCanvas().PrepareDC(dc
) 
2781         if self
._canvas
.HasCapture(): 
2782             self
._canvas
.ReleaseMouse() 
2783         dc
.SetLogicalFunction(wx
.COPY
) 
2785         # If we're changing shape, must reset the original points 
2787             self
.CalculateBoundingBox() 
2788             self
.CalculatePolygonCentre() 
2790             self
.SetSize(pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2793         self
.ResetControlPoints() 
2794         self
.Move(dc
, self
.GetX(), self
.GetY()) 
2795         if not self
._canvas
.GetQuickEditMode(): 
2796             self
._canvas
.Redraw(dc
) 
2800 class EllipseShape(Shape
): 
2801     """The EllipseShape behaves similarly to the RectangleShape but is 
2807     def __init__(self
, w
, h
): 
2808         Shape
.__init
__(self
) 
2811         self
.SetDefaultRegionSize() 
2813     def GetBoundingBoxMin(self
): 
2814         return self
._width
, self
._height
 
2816     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2817         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2819         return DrawArcToEllipse(self
._xpos
, self
._ypos
, bound_x
, bound_y
, x2
, y2
, x1
, y1
) 
2824     def GetHeight(self
): 
2827     def SetWidth(self
, w
): 
2830     def SetHeight(self
, h
): 
2833     def OnDraw(self
, dc
): 
2834         if self
._shadowMode 
!= SHADOW_NONE
: 
2835             if self
._shadowBrush
: 
2836                 dc
.SetBrush(self
._shadowBrush
) 
2837             dc
.SetPen(TransparentPen
) 
2838             dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0 + self
._shadowOffsetX
, 
2839                            self
._ypos 
- self
.GetHeight() / 2.0 + self
._shadowOffsetY
, 
2840                            self
.GetWidth(), self
.GetHeight()) 
2843             if self
._pen
.GetWidth() == 0: 
2844                 dc
.SetPen(TransparentPen
) 
2846                 dc
.SetPen(self
._pen
) 
2848             dc
.SetBrush(self
._brush
) 
2849         dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0, self
._ypos 
- self
.GetHeight() / 2.0, self
.GetWidth(), self
.GetHeight()) 
2851     def SetSize(self
, x
, y
, recursive 
= True): 
2852         self
.SetAttachmentSize(x
, y
) 
2855         self
.SetDefaultRegionSize() 
2857     def GetNumberOfAttachments(self
): 
2858         return Shape
.GetNumberOfAttachments(self
) 
2860     # There are 4 attachment points on an ellipse - 0 = top, 1 = right, 
2861     # 2 = bottom, 3 = left. 
2862     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2863         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
2864             return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2866         if self
._attachmentMode 
!= ATTACHMENT_MODE_NONE
: 
2867             top 
= self
._ypos 
+ self
._height 
/ 2.0 
2868             bottom 
= self
._ypos 
- self
._height 
/ 2.0 
2869             left 
= self
._xpos 
- self
._width 
/ 2.0 
2870             right 
= self
._xpos 
+ self
._width 
/ 2.0 
2872             physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
2874             if physicalAttachment 
== 0: 
2875                 if self
._spaceAttachments
: 
2876                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2880                 # We now have the point on the bounding box: but get the point 
2881                 # on the ellipse by imagining a vertical line from 
2882                 # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting 
2885                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
- self
._height 
- 500, x
, self
._ypos
) 
2886             elif physicalAttachment 
== 1: 
2888                 if self
._spaceAttachments
: 
2889                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2892                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
+ self
._width 
+ 500, y
, self
._xpos
, y
) 
2893             elif physicalAttachment 
== 2: 
2894                 if self
._spaceAttachments
: 
2895                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2899                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
+ self
._height 
+ 500, x
, self
._ypos
) 
2900             elif physicalAttachment 
== 3: 
2902                 if self
._spaceAttachments
: 
2903                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2906                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
- self
._width 
- 500, y
, self
._xpos
, y
) 
2908                 return Shape
.GetAttachmentPosition(self
, attachment
, x
, y
, nth
, no_arcs
, line
) 
2910             return self
._xpos
, self
._ypos
 
2914 class CircleShape(EllipseShape
): 
2915     """An EllipseShape whose width and height are the same.""" 
2916     def __init__(self
, diameter
): 
2917         EllipseShape
.__init
__(self
, diameter
, diameter
) 
2918         self
.SetMaintainAspectRatio(True) 
2920     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2921         return FindEndForCircle(self
._width 
/ 2.0, self
._xpos
, self
._ypos
, x2
, y2
) 
2925 class TextShape(RectangleShape
): 
2926     """As wxRectangleShape, but only the text is displayed.""" 
2927     def __init__(self
, width
, height
): 
2928         RectangleShape
.__init
__(self
, width
, height
) 
2930     def OnDraw(self
, dc
): 
2935 class ShapeRegion(object): 
2936     """Object region.""" 
2937     def __init__(self
, region 
= None): 
2939             self
._regionText 
= region
._regionText
 
2940             self
._regionName 
= region
._regionName
 
2941             self
._textColour 
= region
._textColour
 
2943             self
._font 
= region
._font
 
2944             self
._minHeight 
= region
._minHeight
 
2945             self
._minWidth 
= region
._minWidth
 
2946             self
._width 
= region
._width
 
2947             self
._height 
= region
._height
 
2951             self
._regionProportionX 
= region
._regionProportionX
 
2952             self
._regionProportionY 
= region
._regionProportionY
 
2953             self
._formatMode 
= region
._formatMode
 
2954             self
._actualColourObject 
= region
._actualColourObject
 
2955             self
._actualPenObject 
= None 
2956             self
._penStyle 
= region
._penStyle
 
2957             self
._penColour 
= region
._penColour
 
2960             for line 
in region
._formattedText
: 
2961                 new_line 
= ShapeTextLine(line
.GetX(), line
.GetY(), line
.GetText()) 
2962                 self
._formattedText
.append(new_line
) 
2964             self
._regionText 
= "" 
2965             self
._font 
= NormalFont
 
2966             self
._minHeight 
= 5.0 
2967             self
._minWidth 
= 5.0 
2973             self
._regionProportionX 
= -1.0 
2974             self
._regionProportionY 
= -1.0 
2975             self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
2976             self
._regionName 
= "" 
2977             self
._textColour 
= "BLACK" 
2978             self
._penColour 
= "BLACK" 
2979             self
._penStyle 
= wx
.SOLID
 
2980             self
._actualColourObject 
= wx
.TheColourDatabase
.Find("BLACK") 
2981             self
._actualPenObject 
= None 
2983         self
._formattedText 
= [] 
2985     def ClearText(self
): 
2986         self
._formattedText 
= [] 
2988     def SetFont(self
, f
): 
2991     def SetMinSize(self
, w
, h
): 
2995     def SetSize(self
, w
, h
): 
2999     def SetPosition(self
, xp
, yp
): 
3003     def SetProportions(self
, xp
, yp
): 
3004         self
._regionProportionX 
= xp
 
3005         self
._regionProportionY 
= yp
 
3007     def SetFormatMode(self
, mode
): 
3008         self
._formatMode 
= mode
 
3010     def SetColour(self
, col
): 
3011         self
._textColour 
= col
 
3012         self
._actualColourObject 
= col
 
3014     def GetActualColourObject(self
): 
3015         self
._actualColourObject 
= wx
.TheColourDatabase
.Find(self
.GetColour()) 
3016         return self
._actualColourObject
 
3018     def SetPenColour(self
, col
): 
3019         self
._penColour 
= col
 
3020         self
._actualPenObject 
= None 
3022     # Returns NULL if the pen is invisible 
3023     # (different to pen being transparent; indicates that 
3024     # region boundary should not be drawn.) 
3025     def GetActualPen(self
): 
3026         if self
._actualPenObject
: 
3027             return self
._actualPenObject
 
3029         if not self
._penColour
: 
3031         if self
._penColour
=="Invisible": 
3033         self
._actualPenObject 
= wx
.ThePenList
.FindOrCreatePen(self
._penColour
, 1, self
._penStyle
) 
3034         return self
._actualPenObject
 
3036     def SetText(self
, s
): 
3037         self
._regionText 
= s
 
3039     def SetName(self
, s
): 
3040         self
._regionName 
= s
 
3043         return self
._regionText
 
3048     def GetMinSize(self
): 
3049         return self
._minWidth
, self
._minHeight
 
3051     def GetProportion(self
): 
3052         return self
._regionProportionX
, self
._regionProportionY
 
3055         return self
._width
, self
._height
 
3057     def GetPosition(self
): 
3058         return self
._x
, self
._y
 
3060     def GetFormatMode(self
): 
3061         return self
._formatMode
 
3064         return self
._regionName
 
3066     def GetColour(self
): 
3067         return self
._textColour
 
3069     def GetFormattedText(self
): 
3070         return self
._formattedText
 
3072     def GetPenColour(self
): 
3073         return self
._penColour
 
3075     def GetPenStyle(self
): 
3076         return self
._penStyle
 
3078     def SetPenStyle(self
, style
): 
3079         self
._penStyle 
= style
 
3080         self
._actualPenObject 
= None 
3085     def GetHeight(self
): 
3090 class ControlPoint(RectangleShape
): 
3091     def __init__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, the_type
): 
3092         RectangleShape
.__init
__(self
, size
, size
) 
3094         self
._canvas 
= theCanvas
 
3095         self
._shape 
= object 
3096         self
._xoffset 
= the_xoffset
 
3097         self
._yoffset 
= the_yoffset
 
3098         self
._type 
= the_type
 
3099         self
.SetPen(BlackForegroundPen
) 
3100         self
.SetBrush(wx
.BLACK_BRUSH
) 
3101         self
._oldCursor 
= None 
3102         self
._visible 
= True 
3103         self
._eraseObject 
= True 
3105     # Don't even attempt to draw any text - waste of time 
3106     def OnDrawContents(self
, dc
): 
3109     def OnDraw(self
, dc
): 
3110         self
._xpos 
= self
._shape
.GetX() + self
._xoffset
 
3111         self
._ypos 
= self
._shape
.GetY() + self
._yoffset
 
3112         RectangleShape
.OnDraw(self
, dc
) 
3114     def OnErase(self
, dc
): 
3115         RectangleShape
.OnErase(self
, dc
) 
3117     # Implement resizing of canvas object 
3118     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3119         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3121     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3122         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3124     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3125         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3127     def GetNumberOfAttachments(self
): 
3130     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
3131         return self
._xpos
, self
._ypos
 
3133     def SetEraseObject(self
, er
): 
3134         self
._eraseObject 
= er
 
3137 class PolygonControlPoint(ControlPoint
): 
3138     def __init__(self
, theCanvas
, object, size
, vertex
, the_xoffset
, the_yoffset
): 
3139         ControlPoint
.__init
__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, 0) 
3140         self
._polygonVertex 
= vertex
 
3141         self
._originalDistance 
= 0.0 
3142         self
._newSize 
= wx
.RealPoint() 
3143         self
._originalSize 
= wx
.RealPoint() 
3145     def GetNewSize(self
): 
3146         return self
._newSize
 
3148     # Calculate what new size would be, at end of resize 
3149     def CalculateNewSize(self
, x
, y
): 
3150         bound_x
, bound_y 
= self
.GetShape().GetBoundingBoxMax() 
3151         dist 
= math
.sqrt((x 
- self
._shape
.GetX()) * (x 
- self
._shape
.GetX()) + (y 
- self
._shape
.GetY()) * (y 
- self
._shape
.GetY())) 
3153         self
._newSize
[0] = dist 
/ self
._originalDistance 
* self
._originalSize
[0] 
3154         self
._newSize
[1] = dist 
/ self
._originalDistance 
* self
._originalSize
[1] 
3156     # Implement resizing polygon or moving the vertex 
3157     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3158         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3160     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3161         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3163     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3164         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3166 from _canvas 
import * 
3167 from _lines 
import * 
3168 from _composit 
import *