1 # -*- coding: iso-8859-1 -*- 
   2 #---------------------------------------------------------------------------- 
   4 # Purpose:      The basic OGL shapes 
   6 # Author:       Pierre Hjälm (from C++ original by Julian Smart) 
  10 # Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart 
  11 # Licence:      wxWindows license 
  12 #---------------------------------------------------------------------------- 
  17 from _oglmisc 
import * 
  23     global WhiteBackgroundPen
, WhiteBackgroundBrush
, TransparentPen
 
  24     global BlackForegroundPen
, NormalFont
 
  26     WhiteBackgroundPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
) 
  27     WhiteBackgroundBrush 
= wx
.Brush(wx
.WHITE
, wx
.SOLID
) 
  29     TransparentPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
) 
  30     BlackForegroundPen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
  32     NormalFont 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
  39 class ShapeTextLine(object): 
  40     def __init__(self
, the_x
, the_y
, the_line
): 
  57     def SetText(self
, text
): 
  65 class ShapeEvtHandler(object): 
  66     def __init__(self
, prev 
= None, shape 
= None): 
  67         self
._previousHandler 
= prev
 
  68         self
._handlerShape 
= shape
 
  73     def SetShape(self
, sh
): 
  74         self
._handlerShape 
= sh
 
  77         return self
._handlerShape
 
  79     def SetPreviousHandler(self
, handler
): 
  80         self
._previousHandler 
= handler
 
  82     def GetPreviousHandler(self
): 
  83         return self
._previousHandler
 
  86         if self
!=self
.GetShape(): 
  90         if self
._previousHandler
: 
  91             self
._previousHandler
.OnDraw(dc
) 
  93     def OnMoveLinks(self
, dc
): 
  94         if self
._previousHandler
: 
  95             self
._previousHandler
.OnMoveLinks(dc
) 
  97     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
  98         if self
._previousHandler
: 
  99             self
._previousHandler
.OnMoveLink(dc
, moveControlPoints
) 
 101     def OnDrawContents(self
, dc
): 
 102         if self
._previousHandler
: 
 103             self
._previousHandler
.OnDrawContents(dc
) 
 105     def OnDrawBranches(self
, dc
, erase 
= False): 
 106         if self
._previousHandler
: 
 107             self
._previousHandler
.OnDrawBranches(dc
, erase 
= erase
) 
 109     def OnSize(self
, x
, y
): 
 110         if self
._previousHandler
: 
 111             self
._previousHandler
.OnSize(x
, y
) 
 113     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 114         if self
._previousHandler
: 
 115             return self
._previousHandler
.OnMovePre(dc
, x
, y
, old_x
, old_y
, display
) 
 119     def OnMovePost(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 120         if self
._previousHandler
: 
 121             return self
._previousHandler
.OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
 125     def OnErase(self
, dc
): 
 126         if self
._previousHandler
: 
 127             self
._previousHandler
.OnErase(dc
) 
 129     def OnEraseContents(self
, dc
): 
 130         if self
._previousHandler
: 
 131             self
._previousHandler
.OnEraseContents(dc
) 
 133     def OnHighlight(self
, dc
): 
 134         if self
._previousHandler
: 
 135             self
._previousHandler
.OnHighlight(dc
) 
 137     def OnLeftClick(self
, x
, y
, keys
, attachment
): 
 138         if self
._previousHandler
: 
 139             self
._previousHandler
.OnLeftClick(x
, y
, keys
, attachment
) 
 141     def OnLeftDoubleClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 142         if self
._previousHandler
: 
 143             self
._previousHandler
.OnLeftDoubleClick(x
, y
, keys
, attachment
) 
 145     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 146         if self
._previousHandler
: 
 147             self
._previousHandler
.OnRightClick(x
, y
, keys
, attachment
) 
 149     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 150         if self
._previousHandler
: 
 151             self
._previousHandler
.OnDragLeft(draw
, x
, y
, keys
, attachment
) 
 153     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 154         if self
._previousHandler
: 
 155             self
._previousHandler
.OnBeginDragLeft(x
, y
, keys
, attachment
) 
 157     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 158         if self
._previousHandler
: 
 159             self
._previousHandler
.OnEndDragLeft(x
, y
, keys
, attachment
) 
 161     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 162         if self
._previousHandler
: 
 163             self
._previousHandler
.OnDragRight(draw
, x
, y
, keys
, attachment
) 
 165     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 166         if self
._previousHandler
: 
 167             self
._previousHandler
.OnBeginDragRight(x
, y
, keys
, attachment
) 
 169     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 170         if self
._previousHandler
: 
 171             self
._previousHandler
.OnEndDragRight(x
, y
, keys
, attachment
) 
 173     # Control points ('handles') redirect control to the actual shape, 
 174     # to make it easier to override sizing behaviour. 
 175     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 176         if self
._previousHandler
: 
 177             self
._previousHandler
.OnSizingDragLeft(pt
, draw
, x
, y
, keys
, attachment
) 
 179     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 180         if self
._previousHandler
: 
 181             self
._previousHandler
.OnSizingBeginDragLeft(pt
, x
, y
, keys
, attachment
) 
 183     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 184         if self
._previousHandler
: 
 185             self
._previousHandler
.OnSizingEndDragLeft(pt
, x
, y
, keys
, attachment
) 
 187     def OnBeginSize(self
, w
, h
): 
 190     def OnEndSize(self
, w
, h
): 
 193     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 194         if self
._previousHandler
: 
 195             self
._previousHandler
.OnDrawOutline(dc
, x
, y
, w
, h
) 
 197     def OnDrawControlPoints(self
, dc
): 
 198         if self
._previousHandler
: 
 199             self
._previousHandler
.OnDrawControlPoints(dc
) 
 201     def OnEraseControlPoints(self
, dc
): 
 202         if self
._previousHandler
: 
 203             self
._previousHandler
.OnEraseControlPoints(dc
) 
 205     # Can override this to prevent or intercept line reordering. 
 206     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 207         if self
._previousHandler
: 
 208             self
._previousHandler
.OnChangeAttachment(attachment
, line
, ordering
) 
 212 class Shape(ShapeEvtHandler
): 
 217     The wxShape is the top-level, abstract object that all other objects 
 218     are derived from. All common functionality is represented by wxShape's 
 219     members, and overriden members that appear in derived classes and have 
 220     behaviour as documented for wxShape, are not documented separately. 
 223     GraphicsInSizeToContents 
= False 
 225     def __init__(self
, canvas 
= None): 
 226         ShapeEvtHandler
.__init
__(self
) 
 228         self
._eventHandler 
= self
 
 231         self
._formatted 
= False 
 232         self
._canvas 
= canvas
 
 235         self
._pen 
= BlackForegroundPen
 
 236         self
._brush 
= wx
.WHITE_BRUSH
 
 237         self
._font 
= NormalFont
 
 238         self
._textColour 
= wx
.BLACK
 
 239         self
._textColourName 
= wx
.BLACK
 
 240         self
._visible 
= False 
 241         self
._selected 
= False 
 242         self
._attachmentMode 
= ATTACHMENT_MODE_NONE
 
 243         self
._spaceAttachments 
= True 
 244         self
._disableLabel 
= False 
 245         self
._fixedWidth 
= False 
 246         self
._fixedHeight 
= False 
 247         self
._drawHandles 
= True 
 248         self
._sensitivity 
= OP_ALL
 
 249         self
._draggable 
= True 
 251         self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
 252         self
._shadowMode 
= SHADOW_NONE
 
 253         self
._shadowOffsetX 
= 6 
 254         self
._shadowOffsetY 
= 6 
 255         self
._shadowBrush 
= wx
.BLACK_BRUSH
 
 256         self
._textMarginX 
= 5 
 257         self
._textMarginY 
= 5 
 258         self
._regionName 
= "0" 
 259         self
._centreResize 
= True 
 260         self
._maintainAspectRatio 
= False 
 261         self
._highlighted 
= False 
 263         self
._branchNeckLength 
= 10 
 264         self
._branchStemLength 
= 10 
 265         self
._branchSpacing 
= 10 
 266         self
._branchStyle 
= BRANCHING_ATTACHMENT_NORMAL
 
 270         self
._controlPoints 
= [] 
 271         self
._attachmentPoints 
= [] 
 275         # Set up a default region. Much of the above will be put into 
 276         # the region eventually (the duplication is for compatibility) 
 277         region 
= ShapeRegion() 
 279         region
.SetFont(NormalFont
) 
 280         region
.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
) 
 281         region
.SetColour("BLACK") 
 282         self
._regions
.append(region
) 
 285         return "<%s.%s>" % (self
.__class
__.__module
__, self
.__class
__.__name
__) 
 287     def GetClassName(self
): 
 288         return str(self
.__class
__).split(".")[-1][:-2] 
 292         Fully disconnect this shape from parents, children, the 
 296             self
._parent
.GetChildren().remove(self
) 
 298         for child 
in self
.GetChildren(): 
 303         self
.ClearAttachments() 
 305         self
._handlerShape 
= None 
 308             self
.RemoveFromCanvas(self
._canvas
) 
 310         if self
.GetEventHandler(): 
 311             self
.GetEventHandler().OnDelete() 
 312         self
._eventHandler 
= None 
 315         ShapeEvtHandler
.__del
__(self
) 
 318         """TRUE if the shape may be dragged by the user.""" 
 321     def SetShape(self
, sh
): 
 322         self
._handlerShape 
= sh
 
 325         """Get the internal canvas.""" 
 328     def GetBranchStyle(self
): 
 329         return self
._branchStyle
 
 331     def GetRotation(self
): 
 332         """Return the angle of rotation in radians.""" 
 333         return self
._rotation
 
 335     def SetRotation(self
, rotation
): 
 336         self
._rotation 
= rotation
 
 338     def SetHighlight(self
, hi
, recurse 
= False): 
 339         """Set the highlight for a shape. Shape highlighting is unimplemented.""" 
 340         self
._highlighted 
= hi
 
 342             for shape 
in self
._children
: 
 343                 shape
.SetHighlight(hi
, recurse
) 
 345     def SetSensitivityFilter(self
, sens 
= OP_ALL
, recursive 
= False): 
 346         """Set the shape to be sensitive or insensitive to specific mouse 
 349         sens is a bitlist of the following: 
 355         * OP_ALL (equivalent to a combination of all the above). 
 357         self
._draggable 
= sens 
& OP_DRAG_LEFT
 
 359         self
._sensitivity 
= sens
 
 361             for shape 
in self
._children
: 
 362                 shape
.SetSensitivityFilter(sens
, True) 
 364     def SetDraggable(self
, drag
, recursive 
= False): 
 365         """Set the shape to be draggable or not draggable.""" 
 366         self
._draggable 
= drag
 
 368             self
._sensitivity |
= OP_DRAG_LEFT
 
 369         elif self
._sensitivity 
& OP_DRAG_LEFT
: 
 370             self
._sensitivity 
-= OP_DRAG_LEFT
 
 373             for shape 
in self
._children
: 
 374                 shape
.SetDraggable(drag
, True) 
 376     def SetDrawHandles(self
, drawH
): 
 377         """Set the drawHandles flag for this shape and all descendants. 
 378         If drawH is TRUE (the default), any handles (control points) will 
 379         be drawn. Otherwise, the handles will not be drawn. 
 381         self
._drawHandles 
= drawH
 
 382         for shape 
in self
._children
: 
 383             shape
.SetDrawHandles(drawH
) 
 385     def SetShadowMode(self
, mode
, redraw 
= False): 
 386         """Set the shadow mode (whether a shadow is drawn or not). 
 387         mode can be one of the following: 
 390           No shadow (the default).  
 392           Shadow on the left side.  
 394           Shadow on the right side. 
 396         if redraw 
and self
.GetCanvas(): 
 397             dc 
= wx
.ClientDC(self
.GetCanvas()) 
 398             self
.GetCanvas().PrepareDC(dc
) 
 400             self
._shadowMode 
= mode
 
 403             self
._shadowMode 
= mode
 
 405     def GetShadowMode(self
): 
 406         """Return the current shadow mode setting""" 
 407         return self
._shadowMode
 
 409     def SetCanvas(self
, theCanvas
): 
 410         """Identical to Shape.Attach.""" 
 411         self
._canvas 
= theCanvas
 
 412         for shape 
in self
._children
: 
 413             shape
.SetCanvas(theCanvas
) 
 415     def AddToCanvas(self
, theCanvas
, addAfter 
= None): 
 416         """Add the shape to the canvas's shape list. 
 417         If addAfter is non-NULL, will add the shape after this one. 
 419         theCanvas
.AddShape(self
, addAfter
) 
 422         for object in self
._children
: 
 423             object.AddToCanvas(theCanvas
, lastImage
) 
 426     def InsertInCanvas(self
, theCanvas
): 
 427         """Insert the shape at the front of the shape list of canvas.""" 
 428         theCanvas
.InsertShape(self
) 
 431         for object in self
._children
: 
 432             object.AddToCanvas(theCanvas
, lastImage
) 
 435     def RemoveFromCanvas(self
, theCanvas
): 
 436         """Remove the shape from the canvas.""" 
 441         theCanvas
.RemoveShape(self
) 
 442         for object in self
._children
: 
 443             object.RemoveFromCanvas(theCanvas
) 
 445     def ClearAttachments(self
): 
 446         """Clear internal custom attachment point shapes (of class 
 449         self
._attachmentPoints 
= [] 
 451     def ClearText(self
, regionId 
= 0): 
 452         """Clear the text from the specified text region.""" 
 455         if regionId 
< len(self
._regions
): 
 456             self
._regions
[regionId
].ClearText() 
 458     def ClearRegions(self
): 
 459         """Clear the ShapeRegions from the shape.""" 
 462     def AddRegion(self
, region
): 
 463         """Add a region to the shape.""" 
 464         self
._regions
.append(region
) 
 466     def SetDefaultRegionSize(self
): 
 467         """Set the default region to be consistent with the shape size.""" 
 468         if not self
._regions
: 
 470         w
, h 
= self
.GetBoundingBoxMax() 
 471         self
._regions
[0].SetSize(w
, h
) 
 473     def HitTest(self
, x
, y
): 
 474         """Given a point on a canvas, returns TRUE if the point was on the 
 475         shape, and returns the nearest attachment point and distance from 
 476         the given point and target. 
 478         width
, height 
= self
.GetBoundingBoxMax() 
 484         width 
+= 4 # Allowance for inaccurate mousing 
 487         left 
= self
._xpos 
- width 
/ 2.0 
 488         top 
= self
._ypos 
- height 
/ 2.0 
 489         right 
= self
._xpos 
+ width 
/ 2.0 
 490         bottom 
= self
._ypos 
+ height 
/ 2.0 
 492         nearest_attachment 
= 0 
 494         # If within the bounding box, check the attachment points 
 496         if x 
>= left 
and x 
<= right 
and y 
>= top 
and y 
<= bottom
: 
 497             n 
= self
.GetNumberOfAttachments() 
 500             # GetAttachmentPosition[Edge] takes a logical attachment position, 
 501             # i.e. if it's rotated through 90%, position 0 is East-facing. 
 504                 e 
= self
.GetAttachmentPositionEdge(i
) 
 507                     l 
= math
.sqrt(((xp 
- x
) * (xp 
- x
)) + (yp 
- y
) * (yp 
- y
)) 
 510                         nearest_attachment 
= i
 
 512             return nearest_attachment
, nearest
 
 515     # Format a text string according to the region size, adding 
 516     # strings with positions to region text list 
 518     def FormatText(self
, dc
, s
, i 
= 0): 
 519         """Reformat the given text region; defaults to formatting the 
 524         if not self
._regions
: 
 527         if i 
> len(self
._regions
): 
 530         region 
= self
._regions
[i
] 
 531         region
._regionText 
= s
 
 532         dc
.SetFont(region
.GetFont()) 
 534         w
, h 
= region
.GetSize() 
 536         stringList 
= FormatText(dc
, s
, (w 
- 2 * self
._textMarginX
), (h 
- 2 * self
._textMarginY
), region
.GetFormatMode()) 
 538             line 
= ShapeTextLine(0.0, 0.0, s
) 
 539             region
.GetFormattedText().append(line
) 
 543         # Don't try to resize an object with more than one image (this 
 544         # case should be dealt with by overriden handlers) 
 545         if (region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
) and \
 
 546            len(region
.GetFormattedText()) and \
 
 547            len(self
._regions
) == 1 and \
 
 548            not Shape
.GraphicsInSizeToContents
: 
 550             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText()) 
 551             if actualW 
+ 2 * self
._textMarginX 
!= w 
or actualH 
+ 2 * self
._textMarginY 
!= h
: 
 552                 # If we are a descendant of a composite, must make sure 
 553                 # the composite gets resized properly 
 555                 topAncestor 
= self
.GetTopAncestor() 
 556                 if topAncestor 
!= self
: 
 557                     Shape
.GraphicsInSizeToContents 
= True 
 559                     composite 
= topAncestor
 
 561                     self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 562                     self
.Move(dc
, self
._xpos
, self
._ypos
) 
 563                     composite
.CalculateSize() 
 564                     if composite
.Selected(): 
 565                         composite
.DeleteControlPoints(dc
) 
 566                         composite
.MakeControlPoints() 
 567                         composite
.MakeMandatoryControlPoints() 
 568                     # Where infinite recursion might happen if we didn't stop it 
 570                     Shape
.GraphicsInSizeToContents 
= False 
 574                 self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 575                 self
.Move(dc
, self
._xpos
, self
._ypos
) 
 576                 self
.EraseContents(dc
) 
 577         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW 
- 2 * self
._textMarginX
, actualH 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 578         self
._formatted 
= True 
 580     def Recentre(self
, dc
): 
 581         """Do recentring (or other formatting) for all the text regions 
 584         w
, h 
= self
.GetBoundingBoxMin() 
 585         for region 
in self
._regions
: 
 586             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w 
- 2 * self
._textMarginX
, h 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 588     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
 589         """Get the point at which the line from (x1, y1) to (x2, y2) hits 
 590         the shape. Returns False if the line doesn't hit the perimeter. 
 594     def SetPen(self
, the_pen
): 
 595         """Set the pen for drawing the shape's outline.""" 
 598     def SetBrush(self
, the_brush
): 
 599         """Set the brush for filling the shape's shape.""" 
 600         self
._brush 
= the_brush
 
 602     # Get the top - most (non-division) ancestor, or self 
 603     def GetTopAncestor(self
): 
 604         """Return the top-most ancestor of this shape (the root of 
 607         if not self
.GetParent(): 
 610         if isinstance(self
.GetParent(), DivisionShape
): 
 612         return self
.GetParent().GetTopAncestor() 
 615     def SetFont(self
, the_font
, regionId 
= 0): 
 616         """Set the font for the specified text region.""" 
 617         self
._font 
= the_font
 
 618         if regionId 
< len(self
._regions
): 
 619             self
._regions
[regionId
].SetFont(the_font
) 
 621     def GetFont(self
, regionId 
= 0): 
 622         """Get the font for the specified text region.""" 
 623         if regionId 
>= len(self
._regions
): 
 625         return self
._regions
[regionId
].GetFont() 
 627     def SetFormatMode(self
, mode
, regionId 
= 0): 
 628         """Set the format mode of the default text region. The argument 
 629         can be a bit list of the following: 
 638         if regionId 
< len(self
._regions
): 
 639             self
._regions
[regionId
].SetFormatMode(mode
) 
 641     def GetFormatMode(self
, regionId 
= 0): 
 642         if regionId 
>= len(self
._regions
): 
 644         return self
._regions
[regionId
].GetFormatMode() 
 646     def SetTextColour(self
, the_colour
, regionId 
= 0): 
 647         """Set the colour for the specified text region.""" 
 648         self
._textColour 
= wx
.TheColourDatabase
.Find(the_colour
) 
 649         self
._textColourName 
= the_colour
 
 651         if regionId 
< len(self
._regions
): 
 652             self
._regions
[regionId
].SetColour(the_colour
) 
 654     def GetTextColour(self
, regionId 
= 0): 
 655         """Get the colour for the specified text region.""" 
 656         if regionId 
>= len(self
._regions
): 
 658         return self
._regions
[regionId
].GetColour() 
 660     def SetRegionName(self
, name
, regionId 
= 0): 
 661         """Set the name for this region. 
 662         The name for a region is unique within the scope of the whole 
 663         composite, whereas a region id is unique only for a single image. 
 665         if regionId 
< len(self
._regions
): 
 666             self
._regions
[regionId
].SetName(name
) 
 668     def GetRegionName(self
, regionId 
= 0): 
 669         """Get the region's name. 
 670         A region's name can be used to uniquely determine a region within 
 671         an entire composite image hierarchy. See also Shape.SetRegionName. 
 673         if regionId 
>= len(self
._regions
): 
 675         return self
._regions
[regionId
].GetName() 
 677     def GetRegionId(self
, name
): 
 678         """Get the region's identifier by name. 
 679         This is not unique for within an entire composite, but is unique 
 682         for i
, r 
in enumerate(self
._regions
): 
 683             if r
.GetName() == name
: 
 687     # Name all _regions in all subimages recursively 
 688     def NameRegions(self
, parentName
=""): 
 689         """Make unique names for all the regions in a shape or composite shape.""" 
 690         n 
= self
.GetNumberOfTextRegions() 
 693                 buff 
= parentName
+"."+str(i
) 
 696             self
.SetRegionName(buff
, i
) 
 698         for j
, child 
in enumerate(self
._children
): 
 700                 buff 
= parentName
+"."+str(j
) 
 703             child
.NameRegions(buff
) 
 705     # Get a region by name, possibly looking recursively into composites 
 706     def FindRegion(self
, name
): 
 707         """Find the actual image ('this' if non-composite) and region id 
 708         for the given region name. 
 710         id = self
.GetRegionId(name
) 
 714         for child 
in self
._children
: 
 715             actualImage
, regionId 
= child
.FindRegion(name
) 
 717                 return actualImage
, regionId
 
 721     # Finds all region names for this image (composite or simple). 
 722     def FindRegionNames(self
): 
 723         """Get a list of all region names for this image (composite or simple).""" 
 725         n 
= self
.GetNumberOfTextRegions() 
 727             list.append(self
.GetRegionName(i
)) 
 729         for child 
in self
._children
: 
 730             list += child
.FindRegionNames() 
 734     def AssignNewIds(self
): 
 735         """Assign new ids to this image and its children.""" 
 736         self
._id 
= wx
.NewId() 
 737         for child 
in self
._children
: 
 740     def OnDraw(self
, dc
): 
 743     def OnMoveLinks(self
, dc
): 
 744         # Want to set the ends of all attached links 
 745         # to point to / from this object 
 747         for line 
in self
._lines
: 
 748             line
.GetEventHandler().OnMoveLink(dc
) 
 750     def OnDrawContents(self
, dc
): 
 751         if not self
._regions
: 
 754         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
 759         for region 
in self
._regions
: 
 761                 dc
.SetFont(region
.GetFont()) 
 763             dc
.SetTextForeground(region
.GetActualColourObject()) 
 764             dc
.SetBackgroundMode(wx
.TRANSPARENT
) 
 765             if not self
._formatted
: 
 766                 CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 767                 self
._formatted 
= True 
 769             if not self
.GetDisableLabel(): 
 770                 DrawFormattedText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 773     def DrawContents(self
, dc
): 
 774         """Draw the internal graphic of the shape (such as text). 
 776         Do not override this function: override OnDrawContents, which 
 777         is called by this function. 
 779         self
.GetEventHandler().OnDrawContents(dc
) 
 781     def OnSize(self
, x
, y
): 
 784     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 787     def OnErase(self
, dc
): 
 788         if not self
._visible
: 
 792         for line 
in self
._lines
: 
 793             line
.GetEventHandler().OnErase(dc
) 
 795         self
.GetEventHandler().OnEraseContents(dc
) 
 797     def OnEraseContents(self
, dc
): 
 798         if not self
._visible
: 
 801         xp
, yp 
= self
.GetX(), self
.GetY() 
 802         minX
, minY 
= self
.GetBoundingBoxMin() 
 803         maxX
, maxY 
= self
.GetBoundingBoxMax() 
 805         topLeftX 
= xp 
- maxX 
/ 2.0 - 2 
 806         topLeftY 
= yp 
- maxY 
/ 2.0 - 2 
 810             penWidth 
= self
._pen
.GetWidth() 
 812         dc
.SetPen(self
.GetBackgroundPen()) 
 813         dc
.SetBrush(self
.GetBackgroundBrush()) 
 815         dc
.DrawRectangle(topLeftX 
- penWidth
, topLeftY 
- penWidth
, maxX 
+ penWidth 
* 2 + 4, maxY 
+ penWidth 
* 2 + 4) 
 817     def EraseLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 818         """Erase links attached to this shape, but do not repair damage 
 819         caused to other shapes. 
 821         if not self
._visible
: 
 824         for line 
in self
._lines
: 
 825             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 826                 line
.GetEventHandler().OnErase(dc
) 
 829             for child 
in self
._children
: 
 830                 child
.EraseLinks(dc
, attachment
, recurse
) 
 832     def DrawLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 833         """Draws any lines linked to this shape.""" 
 834         if not self
._visible
: 
 837         for line 
in self
._lines
: 
 838             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 842             for child 
in self
._children
: 
 843                 child
.DrawLinks(dc
, attachment
, recurse
) 
 845     #  Returns TRUE if pt1 <= pt2 in the sense that one point comes before 
 846     #  another on an edge of the shape. 
 847     # attachmentPoint is the attachment point (= side) in question. 
 849     # This is the default, rectangular implementation. 
 850     def AttachmentSortTest(self
, attachmentPoint
, pt1
, pt2
): 
 851         """Return TRUE if pt1 is less than or equal to pt2, in the sense 
 852         that one point comes before another on an edge of the shape. 
 854         attachment is the attachment point (side) in question. 
 856         This function is used in Shape.MoveLineToNewAttachment to determine 
 857         the new line ordering. 
 859         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachmentPoint
) 
 860         if physicalAttachment 
in [0, 2]: 
 861             return pt1
[0] <= pt2
[0] 
 862         elif physicalAttachment 
in [1, 3]: 
 863             return pt1
[1] <= pt2
[1] 
 867     def MoveLineToNewAttachment(self
, dc
, to_move
, x
, y
): 
 868         """Move the given line (which must already be attached to the shape) 
 869         to a different attachment point on the shape, or a different order 
 870         on the same attachment. 
 872         Calls Shape.AttachmentSortTest and then 
 873         ShapeEvtHandler.OnChangeAttachment. 
 875         if self
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 878         # Is (x, y) on this object? If so, find the new attachment point 
 879         # the user has moved the point to 
 880         hit 
= self
.HitTest(x
, y
) 
 884         newAttachment
, distance 
= hit
 
 888         if to_move
.GetTo() == self
: 
 889             oldAttachment 
= to_move
.GetAttachmentTo() 
 891             oldAttachment 
= to_move
.GetAttachmentFrom() 
 893         # The links in a new ordering 
 894         # First, add all links to the new list 
 895         newOrdering 
= self
._lines
[:] 
 897         # Delete the line object from the list of links; we're going to move 
 898         # it to another position in the list 
 899         del newOrdering
[newOrdering
.index(to_move
)] 
 906         for line 
in newOrdering
: 
 907             if line
.GetTo() == self 
and oldAttachment 
== line
.GetAttachmentTo() or \
 
 908                line
.GetFrom() == self 
and oldAttachment 
== line
.GetAttachmentFrom(): 
 909                 startX
, startY
, endX
, endY 
= line
.GetEnds() 
 910                 if line
.GetTo() == self
: 
 917                 thisPoint 
= wx
.RealPoint(xp
, yp
) 
 918                 lastPoint 
= wx
.RealPoint(old_x
, old_y
) 
 919                 newPoint 
= wx
.RealPoint(x
, y
) 
 921                 if self
.AttachmentSortTest(newAttachment
, newPoint
, thisPoint
) and self
.AttachmentSortTest(newAttachment
, lastPoint
, newPoint
): 
 923                     newOrdering
.insert(newOrdering
.index(line
), to_move
) 
 931             newOrdering
.append(to_move
) 
 933         self
.GetEventHandler().OnChangeAttachment(newAttachment
, to_move
, newOrdering
) 
 936     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 937         if line
.GetTo() == self
: 
 938             line
.SetAttachmentTo(attachment
) 
 940             line
.SetAttachmentFrom(attachment
) 
 942         self
.ApplyAttachmentOrdering(ordering
) 
 944         dc 
= wx
.ClientDC(self
.GetCanvas()) 
 945         self
.GetCanvas().PrepareDC(dc
) 
 948         if not self
.GetCanvas().GetQuickEditMode(): 
 949             self
.GetCanvas().Redraw(dc
) 
 951     # Reorders the lines according to the given list 
 952     def ApplyAttachmentOrdering(self
, linesToSort
): 
 953         """Apply the line ordering in linesToSort to the shape, to reorder 
 954         the way lines are attached. 
 956         linesStore 
= self
._lines
[:] 
 960         for line 
in linesToSort
: 
 961             if line 
in linesStore
: 
 962                 del linesStore
[linesStore
.index(line
)] 
 963                 self
._lines
.append(line
) 
 965         # Now add any lines that haven't been listed in linesToSort 
 966         self
._lines 
+= linesStore
 
 968     def SortLines(self
, attachment
, linesToSort
): 
 969         """ Reorder the lines coming into the node image at this attachment 
 970         position, in the order in which they appear in linesToSort. 
 972         Any remaining lines not in the list will be added to the end. 
 974         # This is a temporary store of all the lines at this attachment 
 975         # point. We'll tick them off as we've processed them. 
 976         linesAtThisAttachment 
= [] 
 978         for line 
in self
._lines
[:]: 
 979             if line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or \
 
 980                line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
: 
 981                 linesAtThisAttachment
.append(line
) 
 982                 del self
._lines
[self
._lines
.index(line
)] 
 984         for line 
in linesToSort
: 
 985             if line 
in linesAtThisAttachment
: 
 987                 del linesAtThisAttachment
[linesAtThisAttachment
.index(line
)] 
 988                 self
._lines
.append(line
) 
 990         # Now add any lines that haven't been listed in linesToSort 
 991         self
._lines 
+= linesAtThisAttachment
 
 993     def OnHighlight(self
, dc
): 
 996     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 997         if self
._sensitivity 
& OP_CLICK_LEFT 
!= OP_CLICK_LEFT
: 
 999                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1000                 self
._parent
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
1002     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1003         if self
._sensitivity 
& OP_CLICK_RIGHT 
!= OP_CLICK_RIGHT
: 
1004             attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1005             self
._parent
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
1007     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1008         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1010                 hit 
= self
._parent
.HitTest(x
, y
) 
1012                     attachment
, dist 
= hit
 
1013                 self
._parent
.GetEventHandler().OnDragLeft(draw
, x
, y
, keys
, attachment
) 
1016         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1017         self
.GetCanvas().PrepareDC(dc
) 
1018         dc
.SetLogicalFunction(OGLRBLF
) 
1020         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1021         dc
.SetPen(dottedPen
) 
1022         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1024         xx 
= x 
+ DragOffsetX
 
1025         yy 
= y 
+ DragOffsetY
 
1027         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1028         w
, h 
= self
.GetBoundingBoxMax() 
1029         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1031     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1032         global DragOffsetX
, DragOffsetY
 
1034         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1036                 hit 
= self
._parent
.HitTest(x
, y
) 
1038                     attachment
, dist 
= hit
 
1039                 self
._parent
.GetEventHandler().OnBeginDragLeft(x
, y
, keys
, attachment
) 
1042         DragOffsetX 
= self
._xpos 
- x
 
1043         DragOffsetY 
= self
._ypos 
- y
 
1045         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1046         self
.GetCanvas().PrepareDC(dc
) 
1048         # New policy: don't erase shape until end of drag. 
1050         xx 
= x 
+ DragOffsetX
 
1051         yy 
= y 
+ DragOffsetY
 
1052         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1053         dc
.SetLogicalFunction(OGLRBLF
) 
1055         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1056         dc
.SetPen(dottedPen
) 
1057         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1059         w
, h 
= self
.GetBoundingBoxMax() 
1060         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1061         self
._canvas
.CaptureMouse() 
1063     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1064         if self
._canvas
.HasCapture(): 
1065             self
._canvas
.ReleaseMouse() 
1066         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1068                 hit 
= self
._parent
.HitTest(x
, y
) 
1070                     attachment
, dist 
= hit
 
1071                 self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, attachment
) 
1074         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1075         self
.GetCanvas().PrepareDC(dc
) 
1077         dc
.SetLogicalFunction(wx
.COPY
) 
1078         xx 
= x 
+ DragOffsetX
 
1079         yy 
= y 
+ DragOffsetY
 
1080         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1082         # New policy: erase shape at end of drag. 
1085         self
.Move(dc
, xx
, yy
) 
1086         if self
._canvas 
and not self
._canvas
.GetQuickEditMode(): 
1087             self
._canvas
.Redraw(dc
) 
1089     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1090         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1092                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1093                 self
._parent
.GetEventHandler().OnDragRight(draw
, x
, y
, keys
, attachment
) 
1096     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1097         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1099                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1100                 self
._parent
.GetEventHandler().OnBeginDragRight(x
, y
, keys
, attachment
) 
1103     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1104         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1106                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1107                 self
._parent
.GetEventHandler().OnEndDragRight(x
, y
, keys
, attachment
) 
1110     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
1111         points 
= [[x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1112                 [x 
+ w 
/ 2.0, y 
- h 
/ 2.0], 
1113                 [x 
+ w 
/ 2.0, y 
+ h 
/ 2.0], 
1114                 [x 
- w 
/ 2.0, y 
+ h 
/ 2.0], 
1115                 [x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1118         dc
.DrawLines(points
) 
1120     def Attach(self
, can
): 
1121         """Set the shape's internal canvas pointer to point to the given canvas.""" 
1125         """Disassociates the shape from its canvas.""" 
1128     def Move(self
, dc
, x
, y
, display 
= True): 
1129         """Move the shape to the given position. 
1130         Redraw if display is TRUE. 
1135         if not self
.GetEventHandler().OnMovePre(dc
, x
, y
, old_x
, old_y
, display
): 
1138         self
._xpos
, self
._ypos 
= x
, y
 
1140         self
.ResetControlPoints() 
1147         self
.GetEventHandler().OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
1149     def MoveLinks(self
, dc
): 
1150         """Redraw all the lines attached to the shape.""" 
1151         self
.GetEventHandler().OnMoveLinks(dc
) 
1154         """Draw the whole shape and any lines attached to it. 
1156         Do not override this function: override OnDraw, which is called 
1160             self
.GetEventHandler().OnDraw(dc
) 
1161             self
.GetEventHandler().OnDrawContents(dc
) 
1162             self
.GetEventHandler().OnDrawControlPoints(dc
) 
1163             self
.GetEventHandler().OnDrawBranches(dc
) 
1166         """Flash the shape.""" 
1167         if self
.GetCanvas(): 
1168             dc 
= wx
.ClientDC(self
.GetCanvas()) 
1169             self
.GetCanvas().PrepareDC(dc
) 
1171             dc
.SetLogicalFunction(OGLRBLF
) 
1173             dc
.SetLogicalFunction(wx
.COPY
) 
1176     def Show(self
, show
): 
1177         """Set a flag indicating whether the shape should be drawn.""" 
1178         self
._visible 
= show
 
1179         for child 
in self
._children
: 
1182     def Erase(self
, dc
): 
1184         Does not repair damage caused to other shapes. 
1186         self
.GetEventHandler().OnErase(dc
) 
1187         self
.GetEventHandler().OnEraseControlPoints(dc
) 
1188         self
.GetEventHandler().OnDrawBranches(dc
, erase 
= True) 
1190     def EraseContents(self
, dc
): 
1191         """Erase the shape contents, that is, the area within the shape's 
1192         minimum bounding box. 
1194         self
.GetEventHandler().OnEraseContents(dc
) 
1196     def AddText(self
, string
): 
1197         """Add a line of text to the shape's default text region.""" 
1198         if not self
._regions
: 
1201         region 
= self
._regions
[0] 
1203         new_line 
= ShapeTextLine(0, 0, string
) 
1204         text 
= region
.GetFormattedText() 
1205         text
.append(new_line
) 
1207         self
._formatted 
= False 
1209     def SetSize(self
, x
, y
, recursive 
= True): 
1210         """Set the shape's size.""" 
1211         self
.SetAttachmentSize(x
, y
) 
1212         self
.SetDefaultRegionSize() 
1214     def SetAttachmentSize(self
, w
, h
): 
1215         width
, height 
= self
.GetBoundingBoxMin() 
1219             scaleX 
= float(w
) / width
 
1223             scaleY 
= float(h
) / height
 
1225         for point 
in self
._attachmentPoints
: 
1226             point
._x 
= point
._x 
* scaleX
 
1227             point
._y 
= point
._y 
* scaleY
 
1229     # Add line FROM this object 
1230     def AddLine(self
, line
, other
, attachFrom 
= 0, attachTo 
= 0, positionFrom 
= -1, positionTo 
= -1): 
1231         """Add a line between this shape and the given other shape, at the 
1232         specified attachment points. 
1234         The position in the list of lines at each end can also be specified, 
1235         so that the line will be drawn at a particular point on its attachment 
1238         if positionFrom 
== -1: 
1239             if not line 
in self
._lines
: 
1240                 self
._lines
.append(line
) 
1242             # Don't preserve old ordering if we have new ordering instructions 
1244                 self
._lines
.remove(line
) 
1247             if positionFrom 
< len(self
._lines
): 
1248                 self
._lines
.insert(positionFrom
, line
) 
1250                 self
._lines
.append(line
) 
1252         if positionTo 
== -1: 
1253             if not other 
in other
._lines
: 
1254                 other
._lines
.append(line
) 
1256             # Don't preserve old ordering if we have new ordering instructions 
1258                 other
._lines
.remove(line
) 
1261             if positionTo 
< len(other
._lines
): 
1262                 other
._lines
.insert(positionTo
, line
) 
1264                 other
._lines
.append(line
) 
1268         line
.SetAttachments(attachFrom
, attachTo
) 
1270         dc 
= wx
.ClientDC(self
._canvas
) 
1271         self
._canvas
.PrepareDC(dc
) 
1274     def RemoveLine(self
, line
): 
1275         """Remove the given line from the shape's list of attached lines.""" 
1276         if line
.GetFrom() == self
: 
1277             line
.GetTo()._lines
.remove(line
) 
1279             line
.GetFrom()._lines
.remove(line
) 
1281         self
._lines
.remove(line
) 
1283     # Default - make 6 control points 
1284     def MakeControlPoints(self
): 
1285         """Make a list of control points (draggable handles) appropriate to 
1288         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1289         minX
, minY 
= self
.GetBoundingBoxMin() 
1291         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1292         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1294         # Offsets from main object 
1295         top 
= -heightMin 
/ 2.0 
1296         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1297         left 
= -widthMin 
/ 2.0 
1298         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1300         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, top
, CONTROL_POINT_DIAGONAL
) 
1301         self
._canvas
.AddShape(control
) 
1302         self
._controlPoints
.append(control
) 
1304         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, top
, CONTROL_POINT_VERTICAL
) 
1305         self
._canvas
.AddShape(control
) 
1306         self
._controlPoints
.append(control
) 
1308         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, top
, CONTROL_POINT_DIAGONAL
) 
1309         self
._canvas
.AddShape(control
) 
1310         self
._controlPoints
.append(control
) 
1312         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, 0, CONTROL_POINT_HORIZONTAL
) 
1313         self
._canvas
.AddShape(control
) 
1314         self
._controlPoints
.append(control
) 
1316         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, bottom
, CONTROL_POINT_DIAGONAL
) 
1317         self
._canvas
.AddShape(control
) 
1318         self
._controlPoints
.append(control
) 
1320         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, bottom
, CONTROL_POINT_VERTICAL
) 
1321         self
._canvas
.AddShape(control
) 
1322         self
._controlPoints
.append(control
) 
1324         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, bottom
, CONTROL_POINT_DIAGONAL
) 
1325         self
._canvas
.AddShape(control
) 
1326         self
._controlPoints
.append(control
) 
1328         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, 0, CONTROL_POINT_HORIZONTAL
) 
1329         self
._canvas
.AddShape(control
) 
1330         self
._controlPoints
.append(control
) 
1332     def MakeMandatoryControlPoints(self
): 
1333         """Make the mandatory control points. 
1335         For example, the control point on a dividing line should appear even 
1336         if the divided rectangle shape's handles should not appear (because 
1337         it is the child of a composite, and children are not resizable). 
1339         for child 
in self
._children
: 
1340             child
.MakeMandatoryControlPoints() 
1342     def ResetMandatoryControlPoints(self
): 
1343         """Reset the mandatory control points.""" 
1344         for child 
in self
._children
: 
1345             child
.ResetMandatoryControlPoints() 
1347     def ResetControlPoints(self
): 
1348         """Reset the positions of the control points (for instance when the 
1349         shape's shape has changed). 
1351         self
.ResetMandatoryControlPoints() 
1353         if len(self
._controlPoints
) == 0: 
1356         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1357         minX
, minY 
= self
.GetBoundingBoxMin() 
1359         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1360         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1362         # Offsets from main object 
1363         top 
= -heightMin 
/ 2.0 
1364         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1365         left 
= -widthMin 
/ 2.0 
1366         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1368         self
._controlPoints
[0]._xoffset 
= left
 
1369         self
._controlPoints
[0]._yoffset 
= top
 
1371         self
._controlPoints
[1]._xoffset 
= 0 
1372         self
._controlPoints
[1]._yoffset 
= top
 
1374         self
._controlPoints
[2]._xoffset 
= right
 
1375         self
._controlPoints
[2]._yoffset 
= top
 
1377         self
._controlPoints
[3]._xoffset 
= right
 
1378         self
._controlPoints
[3]._yoffset 
= 0 
1380         self
._controlPoints
[4]._xoffset 
= right
 
1381         self
._controlPoints
[4]._yoffset 
= bottom
 
1383         self
._controlPoints
[5]._xoffset 
= 0 
1384         self
._controlPoints
[5]._yoffset 
= bottom
 
1386         self
._controlPoints
[6]._xoffset 
= left
 
1387         self
._controlPoints
[6]._yoffset 
= bottom
 
1389         self
._controlPoints
[7]._xoffset 
= left
 
1390         self
._controlPoints
[7]._yoffset 
= 0 
1392     def DeleteControlPoints(self
, dc 
= None): 
1393         """Delete the control points (or handles) for the shape. 
1395         Does not redraw the shape. 
1397         for control 
in self
._controlPoints
[:]: 
1399                 control
.GetEventHandler().OnErase(dc
) 
1401             self
._controlPoints
.remove(control
) 
1402         self
._controlPoints 
= [] 
1404         # Children of divisions are contained objects, 
1406         if not isinstance(self
, DivisionShape
): 
1407             for child 
in self
._children
: 
1408                 child
.DeleteControlPoints(dc
) 
1410     def OnDrawControlPoints(self
, dc
): 
1411         if not self
._drawHandles
: 
1414         dc
.SetBrush(wx
.BLACK_BRUSH
) 
1415         dc
.SetPen(wx
.BLACK_PEN
) 
1417         for control 
in self
._controlPoints
: 
1420         # Children of divisions are contained objects, 
1422         # This test bypasses the type facility for speed 
1423         # (critical when drawing) 
1425         if not isinstance(self
, DivisionShape
): 
1426             for child 
in self
._children
: 
1427                 child
.GetEventHandler().OnDrawControlPoints(dc
) 
1429     def OnEraseControlPoints(self
, dc
): 
1430         for control 
in self
._controlPoints
: 
1433         if not isinstance(self
, DivisionShape
): 
1434             for child 
in self
._children
: 
1435                 child
.GetEventHandler().OnEraseControlPoints(dc
) 
1437     def Select(self
, select
, dc 
= None): 
1438         """Select or deselect the given shape, drawing or erasing control points 
1439         (handles) as necessary. 
1441         self
._selected 
= select
 
1443             self
.MakeControlPoints() 
1444             # Children of divisions are contained objects, 
1446             if not isinstance(self
, DivisionShape
): 
1447                 for child 
in self
._children
: 
1448                     child
.MakeMandatoryControlPoints() 
1450                 self
.GetEventHandler().OnDrawControlPoints(dc
) 
1452             self
.DeleteControlPoints(dc
) 
1453             if not isinstance(self
, DivisionShape
): 
1454                 for child 
in self
._children
: 
1455                     child
.DeleteControlPoints(dc
) 
1458         """TRUE if the shape is currently selected.""" 
1459         return self
._selected
 
1461     def AncestorSelected(self
): 
1462         """TRUE if the shape's ancestor is currently selected.""" 
1465         if not self
.GetParent(): 
1467         return self
.GetParent().AncestorSelected() 
1469     def GetNumberOfAttachments(self
): 
1470         """Get the number of attachment points for this shape.""" 
1471         # Should return the MAXIMUM attachment point id here, 
1472         # so higher-level functions can iterate through all attachments, 
1473         # even if they're not contiguous. 
1475         if len(self
._attachmentPoints
) == 0: 
1479             for point 
in self
._attachmentPoints
: 
1480                 if point
._id 
> maxN
: 
1484     def AttachmentIsValid(self
, attachment
): 
1485         """TRUE if attachment is a valid attachment point.""" 
1486         if len(self
._attachmentPoints
) == 0: 
1487             return attachment 
in range(4) 
1489         for point 
in self
._attachmentPoints
: 
1490             if point
._id 
== attachment
: 
1494     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1495         """Get the position at which the given attachment point should be drawn. 
1497         If attachment isn't found among the attachment points of the shape, 
1500         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE
: 
1501             return self
._xpos
, self
._ypos
 
1502         elif self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1503             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, nth
) 
1505         elif self
._attachmentMode 
== ATTACHMENT_MODE_EDGE
: 
1506             if len(self
._attachmentPoints
): 
1507                 for point 
in self
._attachmentPoints
: 
1508                     if point
._id 
== attachment
: 
1509                         return self
._xpos 
+ point
._x
, self
._ypos 
+ point
._y
 
1512                 # Assume is rectangular 
1513                 w
, h 
= self
.GetBoundingBoxMax() 
1514                 top 
= self
._ypos 
+ h 
/ 2.0 
1515                 bottom 
= self
._ypos 
- h 
/ 2.0 
1516                 left 
= self
._xpos 
- w 
/ 2.0 
1517                 right 
= self
._xpos 
+ w 
/ 2.0 
1520                 line 
and line
.IsEnd(self
) 
1522                 physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1525                 if physicalAttachment 
== 0: 
1526                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (right
, bottom
), nth
, no_arcs
, line
) 
1527                 elif physicalAttachment 
== 1: 
1528                     pt 
= self
.CalcSimpleAttachment((right
, bottom
), (right
, top
), nth
, no_arcs
, line
) 
1529                 elif physicalAttachment 
== 2: 
1530                     pt 
= self
.CalcSimpleAttachment((left
, top
), (right
, top
), nth
, no_arcs
, line
) 
1531                 elif physicalAttachment 
== 3: 
1532                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (left
, top
), nth
, no_arcs
, line
) 
1538     def GetBoundingBoxMax(self
): 
1539         """Get the maximum bounding box for the shape, taking into account 
1540         external features such as shadows. 
1542         ww
, hh 
= self
.GetBoundingBoxMin() 
1543         if self
._shadowMode 
!= SHADOW_NONE
: 
1544             ww 
+= self
._shadowOffsetX
 
1545             hh 
+= self
._shadowOffsetY
 
1548     def GetBoundingBoxMin(self
): 
1549         """Get the minimum bounding box for the shape, that defines the area 
1550         available for drawing the contents (such as text). 
1556     def HasDescendant(self
, image
): 
1557         """TRUE if image is a descendant of this composite.""" 
1560         for child 
in self
._children
: 
1561             if child
.HasDescendant(image
): 
1565     # Assuming the attachment lies along a vertical or horizontal line, 
1566     # calculate the position on that point. 
1567     def CalcSimpleAttachment(self
, pt1
, pt2
, nth
, noArcs
, line
): 
1568         """Assuming the attachment lies along a vertical or horizontal line, 
1569         calculate the position on that point. 
1574             The first point of the line repesenting the edge of the shape. 
1577             The second point of the line representing the edge of the shape. 
1580             The position on the edge (for example there may be 6 lines at 
1581             this attachment point, and this may be the 2nd line. 
1584             The number of lines at this edge. 
1591         This function expects the line to be either vertical or horizontal, 
1592         and determines which. 
1594         isEnd 
= line 
and line
.IsEnd(self
) 
1596         # Are we horizontal or vertical? 
1597         isHorizontal 
= RoughlyEqual(pt1
[1], pt2
[1]) 
1607             if self
._spaceAttachments
: 
1608                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1609                     # Align line according to the next handle along 
1610                     point 
= line
.GetNextControlPoint(self
) 
1611                     if point
[0] < firstPoint
[0]: 
1613                     elif point
[0] > secondPoint
[0]: 
1618                     x 
= firstPoint
[0] + (nth 
+ 1) * (secondPoint
[0] - firstPoint
[0]) / (noArcs 
+ 1.0) 
1620                 x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 # Midpoint 
1623             assert RoughlyEqual(pt1
[0], pt2
[0]) 
1632             if self
._spaceAttachments
: 
1633                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1634                     # Align line according to the next handle along 
1635                     point 
= line
.GetNextControlPoint(self
) 
1636                     if point
[1] < firstPoint
[1]: 
1638                     elif point
[1] > secondPoint
[1]: 
1643                     y 
= firstPoint
[1] + (nth 
+ 1) * (secondPoint
[1] - firstPoint
[1]) / (noArcs 
+ 1.0) 
1645                 y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 # Midpoint 
1650     # Return the zero-based position in m_lines of line 
1651     def GetLinePosition(self
, line
): 
1652         """Get the zero-based position of line in the list of lines 
1656             return self
._lines
.index(line
) 
1664     # shoulder1 ->---------<- shoulder2 
1666     #                      <- branching attachment point N-1 
1668     def GetBranchingAttachmentInfo(self
, attachment
): 
1669         """Get information about where branching connections go. 
1671         Returns FALSE if there are no lines at this attachment. 
1673         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1675         # Number of lines at this attachment 
1676         lineCount 
= self
.GetAttachmentLineCount(attachment
) 
1681         totalBranchLength 
= self
._branchSpacing 
* (lineCount 
- 1) 
1682         root 
= self
.GetBranchingAttachmentRoot(attachment
) 
1684         neck 
= wx
.RealPoint() 
1685         shoulder1 
= wx
.RealPoint() 
1686         shoulder2 
= wx
.RealPoint() 
1688         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1689         if physicalAttachment 
== 0: 
1690             neck
[0] = self
.GetX() 
1691             neck
[1] = root
[1] - self
._branchNeckLength
 
1693             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1694             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1696             shoulder1
[1] = neck
[1] 
1697             shoulder2
[1] = neck
[1] 
1698         elif physicalAttachment 
== 1: 
1699             neck
[0] = root
[0] + self
._branchNeckLength
 
1702             shoulder1
[0] = neck
[0] 
1703             shoulder2
[0] = neck
[0] 
1705             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1706             shoulder1
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1707         elif physicalAttachment 
== 2: 
1708             neck
[0] = self
.GetX() 
1709             neck
[1] = root
[1] + self
._branchNeckLength
 
1711             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1712             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1714             shoulder1
[1] = neck
[1] 
1715             shoulder2
[1] = neck
[1] 
1716         elif physicalAttachment 
== 3: 
1717             neck
[0] = root
[0] - self
._branchNeckLength
 
1720             shoulder1
[0] = neck
[0] 
1721             shoulder2
[0] = neck
[0] 
1723             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1724             shoulder2
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1726             raise "Unrecognised attachment point in GetBranchingAttachmentInfo" 
1727         return root
, neck
, shoulder1
, shoulder2
 
1729     def GetBranchingAttachmentPoint(self
, attachment
, n
): 
1730         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1732         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1734         stemPt 
= wx
.RealPoint() 
1736         if physicalAttachment 
== 0: 
1737             pt
[1] = neck
[1] - self
._branchStemLength
 
1738             pt
[0] = shoulder1
[0] + n 
* self
._branchSpacing
 
1742         elif physicalAttachment 
== 2: 
1743             pt
[1] = neck
[1] + self
._branchStemLength
 
1744             pt
[0] = shoulder1
[0] + n 
* self
._branchStemLength
 
1748         elif physicalAttachment 
== 1: 
1749             pt
[0] = neck
[0] + self
._branchStemLength
 
1750             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1754         elif physicalAttachment 
== 3: 
1755             pt
[0] = neck
[0] - self
._branchStemLength
 
1756             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1761             raise "Unrecognised attachment point in GetBranchingAttachmentPoint" 
1765     def GetAttachmentLineCount(self
, attachment
): 
1766         """Get the number of lines at this attachment position.""" 
1768         for lineShape 
in self
._lines
: 
1769             if lineShape
.GetFrom() == self 
and lineShape
.GetAttachmentFrom() == attachment
: 
1771             elif lineShape
.GetTo() == self 
and lineShape
.GetAttachmentTo() == attachment
: 
1775     def GetBranchingAttachmentRoot(self
, attachment
): 
1776         """Get the root point at the given attachment.""" 
1777         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1779         root 
= wx
.RealPoint() 
1781         width
, height 
= self
.GetBoundingBoxMax() 
1783         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1784         if physicalAttachment 
== 0: 
1785             root
[0] = self
.GetX() 
1786             root
[1] = self
.GetY() - height 
/ 2.0 
1787         elif physicalAttachment 
== 1: 
1788             root
[0] = self
.GetX() + width 
/ 2.0 
1789             root
[1] = self
.GetY() 
1790         elif physicalAttachment 
== 2: 
1791             root
[0] = self
.GetX() 
1792             root
[1] = self
.GetY() + height 
/ 2.0 
1793         elif physicalAttachment 
== 3: 
1794             root
[0] = self
.GetX() - width 
/ 2.0 
1795             root
[1] = self
.GetY() 
1797             raise "Unrecognised attachment point in GetBranchingAttachmentRoot" 
1801     # Draw or erase the branches (not the actual arcs though) 
1802     def OnDrawBranchesAttachment(self
, dc
, attachment
, erase 
= False): 
1803         count 
= self
.GetAttachmentLineCount(attachment
) 
1807         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1810             dc
.SetPen(wx
.WHITE_PEN
) 
1811             dc
.SetBrush(wx
.WHITE_BRUSH
) 
1813             dc
.SetPen(wx
.BLACK_PEN
) 
1814             dc
.SetBrush(wx
.BLACK_BRUSH
) 
1817         dc
.DrawLine(root
[0], root
[1], neck
[0], neck
[1]) 
1820             # Draw shoulder-to-shoulder line 
1821             dc
.DrawLine(shoulder1
[0], shoulder1
[1], shoulder2
[0], shoulder2
[1]) 
1822         # Draw all the little branches 
1823         for i 
in range(count
): 
1824             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, i
) 
1825             dc
.DrawLine(stemPt
[0], stemPt
[1], pt
[0], pt
[1]) 
1827             if self
.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB 
and count 
> 1: 
1829                 dc
.DrawEllipse(stemPt
[0] - blobSize 
/ 2.0, stemPt
[1] - blobSize 
/ 2.0, blobSize
, blobSize
) 
1831     def OnDrawBranches(self
, dc
, erase 
= False): 
1832         if self
._attachmentMode 
!= ATTACHMENT_MODE_BRANCHING
: 
1834         for i 
in range(self
.GetNumberOfAttachments()): 
1835             self
.OnDrawBranchesAttachment(dc
, i
, erase
) 
1837     def GetAttachmentPositionEdge(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1838         """ Only get the attachment position at the _edge_ of the shape, 
1839         ignoring branching mode. This is used e.g. to indicate the edge of 
1840         interest, not the point on the attachment branch. 
1842         oldMode 
= self
._attachmentMode
 
1844         # Calculate as if to edge, not branch 
1845         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1846             self
._attachmentMode 
= ATTACHMENT_MODE_EDGE
 
1847         res 
= self
.GetAttachmentPosition(attachment
, nth
, no_arcs
, line
) 
1848         self
._attachmentMode 
= oldMode
 
1852     def PhysicalToLogicalAttachment(self
, physicalAttachment
): 
1853         """ Rotate the standard attachment point from physical 
1854         (0 is always North) to logical (0 -> 1 if rotated by 90 degrees) 
1856         if RoughlyEqual(self
.GetRotation(), 0): 
1857             i 
= physicalAttachment
 
1858         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1859             i 
= physicalAttachment 
- 1 
1860         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1861             i 
= physicalAttachment 
- 2 
1862         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1863             i 
= physicalAttachment 
- 3 
1865             # Can't handle -- assume the same 
1866             return physicalAttachment
 
1873     def LogicalToPhysicalAttachment(self
, logicalAttachment
): 
1874         """Rotate the standard attachment point from logical 
1875         to physical (0 is always North). 
1877         if RoughlyEqual(self
.GetRotation(), 0): 
1878             i 
= logicalAttachment
 
1879         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1880             i 
= logicalAttachment 
+ 1 
1881         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1882             i 
= logicalAttachment 
+ 2 
1883         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1884             i 
= logicalAttachment 
+ 3 
1886             return logicalAttachment
 
1893     def Rotate(self
, x
, y
, theta
): 
1894         """Rotate about the given axis by the given amount in radians.""" 
1895         self
._rotation 
= theta
 
1896         if self
._rotation 
< 0: 
1897             self
._rotation 
+= 2 * math
.pi
 
1898         elif self
._rotation 
> 2 * math
.pi
: 
1899             self
._rotation 
-= 2 * math
.pi
 
1901     def GetBackgroundPen(self
): 
1902         """Return pen of the right colour for the background.""" 
1903         if self
.GetCanvas(): 
1904             return wx
.Pen(self
.GetCanvas().GetBackgroundColour(), 1, wx
.SOLID
) 
1905         return WhiteBackgroundPen
 
1907     def GetBackgroundBrush(self
): 
1908         """Return brush of the right colour for the background.""" 
1909         if self
.GetCanvas(): 
1910             return wx
.Brush(self
.GetCanvas().GetBackgroundColour(), wx
.SOLID
) 
1911         return WhiteBackgroundBrush
 
1914         """Get the x position of the centre of the shape.""" 
1918         """Get the y position of the centre of the shape.""" 
1922         """Set the x position of the shape.""" 
1926         """Set the y position of the shape.""" 
1929     def GetParent(self
): 
1930         """Return the parent of this shape, if it is part of a composite.""" 
1933     def SetParent(self
, p
): 
1936     def GetChildren(self
): 
1937         """Return the list of children for this shape.""" 
1938         return self
._children
 
1940     def GetDrawHandles(self
): 
1941         """Return the list of drawhandles.""" 
1942         return self
._drawHandles
 
1944     def GetEventHandler(self
): 
1945         """Return the event handler for this shape.""" 
1946         return self
._eventHandler
 
1948     def SetEventHandler(self
, handler
): 
1949         """Set the event handler for this shape.""" 
1950         self
._eventHandler 
= handler
 
1952     def Recompute(self
): 
1953         """Recomputes any constraints associated with the shape. 
1955         Normally applicable to CompositeShapes only, but harmless for 
1956         other classes of Shape. 
1960     def IsHighlighted(self
): 
1961         """TRUE if the shape is highlighted. Shape highlighting is unimplemented.""" 
1962         return self
._highlighted
 
1964     def GetSensitivityFilter(self
): 
1965         """Return the sensitivity filter, a bitlist of values. 
1967         See Shape.SetSensitivityFilter. 
1969         return self
._sensitivity
 
1971     def SetFixedSize(self
, x
, y
): 
1972         """Set the shape to be fixed size.""" 
1973         self
._fixedWidth 
= x
 
1974         self
._fixedHeight 
= y
 
1976     def GetFixedSize(self
): 
1977         """Return flags indicating whether the shape is of fixed size in 
1980         return self
._fixedWidth
, self
._fixedHeight
 
1982     def GetFixedWidth(self
): 
1983         """TRUE if the shape cannot be resized in the horizontal plane.""" 
1984         return self
._fixedWidth
 
1986     def GetFixedHeight(self
): 
1987         """TRUE if the shape cannot be resized in the vertical plane.""" 
1988         return self
._fixedHeight
 
1990     def SetSpaceAttachments(self
, sp
): 
1991         """Indicate whether lines should be spaced out evenly at the point 
1992         they touch the node (sp = True), or whether they should join at a single 
1995         self
._spaceAttachments 
= sp
 
1997     def GetSpaceAttachments(self
): 
1998         """Return whether lines should be spaced out evenly at the point they 
1999         touch the node (True), or whether they should join at a single point 
2002         return self
._spaceAttachments
 
2004     def SetCentreResize(self
, cr
): 
2005         """Specify whether the shape is to be resized from the centre (the 
2006         centre stands still) or from the corner or side being dragged (the 
2007         other corner or side stands still). 
2009         self
._centreResize 
= cr
 
2011     def GetCentreResize(self
): 
2012         """TRUE if the shape is to be resized from the centre (the centre stands 
2013         still), or FALSE if from the corner or side being dragged (the other 
2014         corner or side stands still) 
2016         return self
._centreResize
 
2018     def SetMaintainAspectRatio(self
, ar
): 
2019         """Set whether a shape that resizes should not change the aspect ratio 
2020         (width and height should be in the original proportion). 
2022         self
._maintainAspectRatio 
= ar
 
2024     def GetMaintainAspectRatio(self
): 
2025         """TRUE if shape keeps aspect ratio during resize.""" 
2026         return self
._maintainAspectRatio
 
2029         """Return the list of lines connected to this shape.""" 
2032     def SetDisableLabel(self
, flag
): 
2033         """Set flag to TRUE to stop the default region being shown.""" 
2034         self
._disableLabel 
= flag
 
2036     def GetDisableLabel(self
): 
2037         """TRUE if the default region will not be shown, FALSE otherwise.""" 
2038         return self
._disableLabel
 
2040     def SetAttachmentMode(self
, mode
): 
2041         """Set the attachment mode. 
2043         If TRUE, attachment points will be significant when drawing lines to 
2044         and from this shape. 
2045         If FALSE, lines will be drawn as if to the centre of the shape. 
2047         self
._attachmentMode 
= mode
 
2049     def GetAttachmentMode(self
): 
2050         """Return the attachment mode. 
2052         See Shape.SetAttachmentMode. 
2054         return self
._attachmentMode
 
2057         """Set the integer identifier for this shape.""" 
2061         """Return the integer identifier for this shape.""" 
2065         """TRUE if the shape is in a visible state, FALSE otherwise. 
2067         Note that this has nothing to do with whether the window is hidden 
2068         or the shape has scrolled off the canvas; it refers to the internal 
2071         return self
._visible
 
2074         """Return the pen used for drawing the shape's outline.""" 
2078         """Return the brush used for filling the shape.""" 
2081     def GetNumberOfTextRegions(self
): 
2082         """Return the number of text regions for this shape.""" 
2083         return len(self
._regions
) 
2085     def GetRegions(self
): 
2086         """Return the list of ShapeRegions.""" 
2087         return self
._regions
 
2089     # Control points ('handles') redirect control to the actual shape, to 
2090     # make it easier to override sizing behaviour. 
2091     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2092         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2094         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2095         self
.GetCanvas().PrepareDC(dc
) 
2097         dc
.SetLogicalFunction(OGLRBLF
) 
2099         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2100         dc
.SetPen(dottedPen
) 
2101         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2103         if self
.GetCentreResize(): 
2104             # Maintain the same centre point 
2105             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2106             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2108             # Constrain sizing according to what control point you're dragging 
2109             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2110                 if self
.GetMaintainAspectRatio(): 
2111                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2113                     new_height 
= bound_y
 
2114             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2115                 if self
.GetMaintainAspectRatio(): 
2116                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2119             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2120                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2122             if self
.GetFixedWidth(): 
2125             if self
.GetFixedHeight(): 
2126                 new_height 
= bound_y
 
2128             pt
._controlPointDragEndWidth 
= new_width
 
2129             pt
._controlPointDragEndHeight 
= new_height
 
2131             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2133             # Don't maintain the same centre point 
2134             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2135             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2136             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2137             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2138             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2139                 newY1 
= pt
._controlPointDragStartY
 
2140                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2141             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2142                 newX1 
= pt
._controlPointDragStartX
 
2143                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2144             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2145                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2146                 if self
.GetY() > pt
._controlPointDragStartY
: 
2147                     newY2 
= newY1 
+ newH
 
2149                     newY1 
= newY2 
- newH
 
2151             newWidth 
= float(newX2 
- newX1
) 
2152             newHeight 
= float(newY2 
- newY1
) 
2154             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2155                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2157             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2158                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2160             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2161             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2162             if self
.GetFixedWidth(): 
2165             if self
.GetFixedHeight(): 
2168             pt
._controlPointDragEndWidth 
= newWidth
 
2169             pt
._controlPointDragEndHeight 
= newHeight
 
2170             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2172     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2173         self
._canvas
.CaptureMouse() 
2175         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2176         self
.GetCanvas().PrepareDC(dc
) 
2178         dc
.SetLogicalFunction(OGLRBLF
) 
2180         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2181         self
.GetEventHandler().OnBeginSize(bound_x
, bound_y
) 
2183         # Choose the 'opposite corner' of the object as the stationary 
2184         # point in case this is non-centring resizing. 
2185         if pt
.GetX() < self
.GetX(): 
2186             pt
._controlPointDragStartX 
= self
.GetX() + bound_x 
/ 2.0 
2188             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2190         if pt
.GetY() < self
.GetY(): 
2191             pt
._controlPointDragStartY 
= self
.GetY() + bound_y 
/ 2.0 
2193             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2195         if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2196             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2197         elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2198             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2200         # We may require the old width and height 
2201         pt
._controlPointDragStartWidth 
= bound_x
 
2202         pt
._controlPointDragStartHeight 
= bound_y
 
2204         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2205         dc
.SetPen(dottedPen
) 
2206         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2208         if self
.GetCentreResize(): 
2209             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2210             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2212             # Constrain sizing according to what control point you're dragging 
2213             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2214                 if self
.GetMaintainAspectRatio(): 
2215                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2217                     new_height 
= bound_y
 
2218             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2219                 if self
.GetMaintainAspectRatio(): 
2220                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2223             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2224                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2226             if self
.GetFixedWidth(): 
2229             if self
.GetFixedHeight(): 
2230                 new_height 
= bound_y
 
2232             pt
._controlPointDragEndWidth 
= new_width
 
2233             pt
._controlPointDragEndHeight 
= new_height
 
2234             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2236             # Don't maintain the same centre point 
2237             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2238             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2239             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2240             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2241             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2242                 newY1 
= pt
._controlPointDragStartY
 
2243                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2244             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2245                 newX1 
= pt
._controlPointDragStartX
 
2246                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2247             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2248                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2249                 if pt
.GetY() > pt
._controlPointDragStartY
: 
2250                     newY2 
= newY1 
+ newH
 
2252                     newY1 
= newY2 
- newH
 
2254             newWidth 
= float(newX2 
- newX1
) 
2255             newHeight 
= float(newY2 
- newY1
) 
2257             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2258                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2260             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2261                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2263             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2264             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2265             if self
.GetFixedWidth(): 
2268             if self
.GetFixedHeight(): 
2271             pt
._controlPointDragEndWidth 
= newWidth
 
2272             pt
._controlPointDragEndHeight 
= newHeight
 
2273             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2275     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2276         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2277         self
.GetCanvas().PrepareDC(dc
) 
2279         if self
._canvas
.HasCapture(): 
2280             self
._canvas
.ReleaseMouse() 
2281         dc
.SetLogicalFunction(wx
.COPY
) 
2283         self
.ResetControlPoints() 
2287         self
.SetSize(pt
._controlPointDragEndWidth
, pt
._controlPointDragEndHeight
) 
2289         # The next operation could destroy this control point (it does for 
2290         # label objects, via formatting the text), so save all values we're 
2291         # going to use, or we'll be accessing garbage. 
2295         if self
.GetCentreResize(): 
2296             self
.Move(dc
, self
.GetX(), self
.GetY()) 
2298             self
.Move(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
) 
2300         # Recursively redraw links if we have a composite 
2301         if len(self
.GetChildren()): 
2302             self
.DrawLinks(dc
, -1, True) 
2304         width
, height 
= self
.GetBoundingBoxMax() 
2305         self
.GetEventHandler().OnEndSize(width
, height
) 
2307         if not self
._canvas
.GetQuickEditMode() and pt
._eraseObject
: 
2308             self
._canvas
.Redraw(dc
) 
2312 class RectangleShape(Shape
): 
2314     The wxRectangleShape has rounded or square corners. 
2319     def __init__(self
, w 
= 0.0, h 
= 0.0): 
2320         Shape
.__init
__(self
) 
2323         self
._cornerRadius 
= 0.0 
2324         self
.SetDefaultRegionSize() 
2326     def OnDraw(self
, dc
): 
2327         x1 
= self
._xpos 
- self
._width 
/ 2.0 
2328         y1 
= self
._ypos 
- self
._height 
/ 2.0 
2330         if self
._shadowMode 
!= SHADOW_NONE
: 
2331             if self
._shadowBrush
: 
2332                 dc
.SetBrush(self
._shadowBrush
) 
2333             dc
.SetPen(TransparentPen
) 
2335             if self
._cornerRadius
: 
2336                 dc
.DrawRoundedRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
, self
._cornerRadius
) 
2338                 dc
.DrawRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
) 
2341             if self
._pen
.GetWidth() == 0: 
2342                 dc
.SetPen(TransparentPen
) 
2344                 dc
.SetPen(self
._pen
) 
2346             dc
.SetBrush(self
._brush
) 
2348         if self
._cornerRadius
: 
2349             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
2351             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
2353     def GetBoundingBoxMin(self
): 
2354         return self
._width
, self
._height
 
2356     def SetSize(self
, x
, y
, recursive 
= False): 
2357         self
.SetAttachmentSize(x
, y
) 
2358         self
._width 
= max(x
, 1) 
2359         self
._height 
= max(y
, 1) 
2360         self
.SetDefaultRegionSize() 
2362     def GetCornerRadius(self
): 
2363         """Get the radius of the rectangle's rounded corners.""" 
2364         return self
._cornerRadius
 
2366     def SetCornerRadius(self
, rad
): 
2367         """Set the radius of the rectangle's rounded corners. 
2369         If the radius is zero, a non-rounded rectangle will be drawn. 
2370         If the radius is negative, the value is the proportion of the smaller 
2371         dimension of the rectangle. 
2373         self
._cornerRadius 
= rad
 
2375     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2376     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2377         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2378         return FindEndForBox(bound_x
, bound_y
, self
._xpos
, self
._ypos
, x2
, y2
) 
2383     def GetHeight(self
): 
2386     def SetWidth(self
, w
): 
2389     def SetHeight(self
, h
): 
2394 class PolygonShape(Shape
): 
2395     """A PolygonShape's shape is defined by a number of points passed to 
2396     the object's constructor. It can be used to create new shapes such as 
2397     diamonds and triangles. 
2400         Shape
.__init
__(self
) 
2403         self
._originalPoints 
= None 
2405     def Create(self
, the_points 
= None): 
2406         """Takes a list of wx.RealPoints or tuples; each point is an offset 
2412             self
._originalPoints 
= [] 
2415             self
._originalPoints 
= the_points
 
2417             # Duplicate the list of points 
2419             for point 
in the_points
: 
2420                 new_point 
= wx
.Point(point
[0], point
[1]) 
2421                 self
._points
.append(new_point
) 
2422             self
.CalculateBoundingBox() 
2423             self
._originalWidth 
= self
._boundWidth
 
2424             self
._originalHeight 
= self
._boundHeight
 
2425             self
.SetDefaultRegionSize() 
2427     def ClearPoints(self
): 
2429         self
._originalPoints 
= [] 
2431     # Width and height. Centre of object is centre of box 
2432     def GetBoundingBoxMin(self
): 
2433         return self
._boundWidth
, self
._boundHeight
 
2435     def GetPoints(self
): 
2436         """Return the internal list of polygon vertices.""" 
2439     def GetOriginalPoints(self
): 
2440         return self
._originalPoints
 
2442     def GetOriginalWidth(self
): 
2443         return self
._originalWidth
 
2445     def GetOriginalHeight(self
): 
2446         return self
._originalHeight
 
2448     def SetOriginalWidth(self
, w
): 
2449         self
._originalWidth 
= w
 
2451     def SetOriginalHeight(self
, h
): 
2452         self
._originalHeight 
= h
 
2454     def CalculateBoundingBox(self
): 
2455         # Calculate bounding box at construction (and presumably resize) time 
2461         for point 
in self
._points
: 
2464             if point
[0] > right
: 
2469             if point
[1] > bottom
: 
2472         self
._boundWidth 
= right 
- left
 
2473         self
._boundHeight 
= bottom 
- top
 
2475     def CalculatePolygonCentre(self
): 
2476         """Recalculates the centre of the polygon, and 
2477         readjusts the point offsets accordingly. 
2478         Necessary since the centre of the polygon 
2479         is expected to be the real centre of the bounding 
2487         for point 
in self
._points
: 
2490             if point
[0] > right
: 
2495             if point
[1] > bottom
: 
2498         bwidth 
= right 
- left
 
2499         bheight 
= bottom 
- top
 
2501         newCentreX 
= left 
+ bwidth 
/ 2.0 
2502         newCentreY 
= top 
+ bheight 
/ 2.0 
2504         for i 
in range(len(self
._points
)): 
2505             self
._points
[i
] = self
._points
[i
][0] - newCentreX
, self
._points
[i
][1] - newCentreY
 
2506         self
._xpos 
+= newCentreX
 
2507         self
._ypos 
+= newCentreY
 
2509     def HitTest(self
, x
, y
): 
2510         # Imagine four lines radiating from this point. If all of these lines 
2511         # hit the polygon, we're inside it, otherwise we're not. Obviously 
2512         # we'd need more radiating lines to be sure of correct results for 
2513         # very strange (concave) shapes. 
2514         endPointsX 
= [x
, x 
+ 1000, x
, x 
- 1000] 
2515         endPointsY 
= [y 
- 1000, y
, y 
+ 1000, y
] 
2520         for point 
in self
._points
: 
2521             xpoints
.append(point
[0] + self
._xpos
) 
2522             ypoints
.append(point
[1] + self
._ypos
) 
2524         # We assume it's inside the polygon UNLESS one or more 
2525         # lines don't hit the outline. 
2529             if not PolylineHitTest(xpoints
, ypoints
, x
, y
, endPointsX
[i
], endPointsY
[i
]): 
2535         nearest_attachment 
= 0 
2537         # If a hit, check the attachment points within the object 
2540         for i 
in range(self
.GetNumberOfAttachments()): 
2541             e 
= self
.GetAttachmentPositionEdge(i
) 
2544                 l 
= math
.sqrt((xp 
- x
) * (xp 
- x
) + (yp 
- y
) * (yp 
- y
)) 
2547                     nearest_attachment 
= i
 
2549         return nearest_attachment
, nearest
 
2551     # Really need to be able to reset the shape! Otherwise, if the 
2552     # points ever go to zero, we've lost it, and can't resize. 
2553     def SetSize(self
, new_width
, new_height
, recursive 
= True): 
2554         self
.SetAttachmentSize(new_width
, new_height
) 
2556         # Multiply all points by proportion of new size to old size 
2557         x_proportion 
= abs(float(new_width
) / self
._originalWidth
) 
2558         y_proportion 
= abs(float(new_height
) / self
._originalHeight
) 
2560         for i 
in range(max(len(self
._points
), len(self
._originalPoints
))): 
2561             self
._points
[i
] = wx
.Point(self
._originalPoints
[i
][0] * x_proportion
, self
._originalPoints
[i
][1] * y_proportion
) 
2563         self
._boundWidth 
= abs(new_width
) 
2564         self
._boundHeight 
= abs(new_height
) 
2565         self
.SetDefaultRegionSize() 
2567     # Make the original points the same as the working points 
2568     def UpdateOriginalPoints(self
): 
2569         """If we've changed the shape, must make the original points match the 
2570         working points with this function. 
2572         self
._originalPoints 
= [] 
2574         for point 
in self
._points
: 
2575             original_point 
= wx
.RealPoint(point
[0], point
[1]) 
2576             self
._originalPoints
.append(original_point
) 
2578         self
.CalculateBoundingBox() 
2579         self
._originalWidth 
= self
._boundWidth
 
2580         self
._originalHeight 
= self
._boundHeight
 
2582     def AddPolygonPoint(self
, pos
): 
2583         """Add a control point after the given point.""" 
2585             firstPoint 
= self
._points
[pos
] 
2587             firstPoint 
= self
._points
[0] 
2590             secondPoint 
= self
._points
[pos 
+ 1] 
2592             secondPoint 
= self
._points
[0] 
2594         x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 + firstPoint
[0] 
2595         y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 + firstPoint
[1] 
2596         point 
= wx
.RealPoint(x
, y
) 
2598         if pos 
>= len(self
._points
) - 1: 
2599             self
._points
.append(point
) 
2601             self
._points
.insert(pos 
+ 1, point
) 
2603         self
.UpdateOriginalPoints() 
2606             self
.DeleteControlPoints() 
2607             self
.MakeControlPoints() 
2609     def DeletePolygonPoint(self
, pos
): 
2610         """Delete the given control point.""" 
2611         if pos 
< len(self
._points
): 
2612             del self
._points
[pos
] 
2613             self
.UpdateOriginalPoints() 
2615                 self
.DeleteControlPoints() 
2616                 self
.MakeControlPoints() 
2618     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2619     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2620         # First check for situation where the line is vertical, 
2621         # and we would want to connect to a point on that vertical -- 
2622         # oglFindEndForPolyline can't cope with this (the arrow 
2623         # gets drawn to the wrong place). 
2624         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE 
and x1 
== x2
: 
2625             # Look for the point we'd be connecting to. This is 
2627             for point 
in self
._points
: 
2629                     if y2 
> y1 
and point
[1] > 0: 
2630                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2631                     elif y2 
< y1 
and point
[1] < 0: 
2632                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2636         for point 
in self
._points
: 
2637             xpoints
.append(point
[0] + self
._xpos
) 
2638             ypoints
.append(point
[1] + self
._ypos
) 
2640         return FindEndForPolyline(xpoints
, ypoints
, x1
, y1
, x2
, y2
) 
2642     def OnDraw(self
, dc
): 
2643         if self
._shadowMode 
!= SHADOW_NONE
: 
2644             if self
._shadowBrush
: 
2645                 dc
.SetBrush(self
._shadowBrush
) 
2646             dc
.SetPen(TransparentPen
) 
2648             dc
.DrawPolygon(self
._points
, self
._xpos 
+ self
._shadowOffsetX
, self
._ypos
, self
._shadowOffsetY
) 
2651             if self
._pen
.GetWidth() == 0: 
2652                 dc
.SetPen(TransparentPen
) 
2654                 dc
.SetPen(self
._pen
) 
2656             dc
.SetBrush(self
._brush
) 
2657         dc
.DrawPolygon(self
._points
, self
._xpos
, self
._ypos
) 
2659     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
2660         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2661         # Multiply all points by proportion of new size to old size 
2662         x_proportion 
= abs(float(w
) / self
._originalWidth
) 
2663         y_proportion 
= abs(float(h
) / self
._originalHeight
) 
2666         for point 
in self
._originalPoints
: 
2667             intPoints
.append(wx
.Point(x_proportion 
* point
[0], y_proportion 
* point
[1])) 
2668         dc
.DrawPolygon(intPoints
, x
, y
) 
2670     # Make as many control points as there are vertices 
2671     def MakeControlPoints(self
): 
2672         for point 
in self
._points
: 
2673             control 
= PolygonControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
, point
[0], point
[1]) 
2674             self
._canvas
.AddShape(control
) 
2675             self
._controlPoints
.append(control
) 
2677     def ResetControlPoints(self
): 
2678         for i 
in range(min(len(self
._points
), len(self
._controlPoints
))): 
2679             point 
= self
._points
[i
] 
2680             self
._controlPoints
[i
]._xoffset 
= point
[0] 
2681             self
._controlPoints
[i
]._yoffset 
= point
[1] 
2682             self
._controlPoints
[i
].polygonVertex 
= point
 
2684     def GetNumberOfAttachments(self
): 
2685         maxN 
= max(len(self
._points
) - 1, 0) 
2686         for point 
in self
._attachmentPoints
: 
2687             if point
._id 
> maxN
: 
2691     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2692         if self
._attachmentMode 
== ATTACHMENT_MODE_EDGE 
and self
._points 
and attachment 
< len(self
._points
): 
2693             point 
= self
._points
[0] 
2694             return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2695         return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2697     def AttachmentIsValid(self
, attachment
): 
2698         if not self
._points
: 
2701         if attachment 
>= 0 and attachment 
< len(self
._points
): 
2704         for point 
in self
._attachmentPoints
: 
2705             if point
._id 
== attachment
: 
2710     # Rotate about the given axis by the given amount in radians 
2711     def Rotate(self
, x
, y
, theta
): 
2712         actualTheta 
= theta 
- self
._rotation
 
2714         # Rotate attachment points 
2715         sinTheta 
= math
.sin(actualTheta
) 
2716         cosTheta 
= math
.cos(actualTheta
) 
2718         for point 
in self
._attachmentPoints
: 
2722             point
._x 
= x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
 
2723             point
._y 
= x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2725         for i 
in range(len(self
._points
)): 
2726             x1
, y1 
= self
._points
[i
] 
2728             self
._points
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2730         for i 
in range(len(self
._originalPoints
)): 
2731             x1
, y1 
= self
._originalPoints
[i
] 
2733             self
._originalPoints
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2735         # Added by Pierre Hjälm. If we don't do this the outline will be 
2736         # the wrong size. Hopefully it won't have any ill effects. 
2737         self
.UpdateOriginalPoints() 
2739         self
._rotation 
= theta
 
2741         self
.CalculatePolygonCentre() 
2742         self
.CalculateBoundingBox() 
2743         self
.ResetControlPoints() 
2745     # Control points ('handles') redirect control to the actual shape, to 
2746     # make it easier to override sizing behaviour. 
2747     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2748         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2749         self
.GetCanvas().PrepareDC(dc
) 
2751         dc
.SetLogicalFunction(OGLRBLF
) 
2753         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2754         dc
.SetPen(dottedPen
) 
2755         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2757         # Code for CTRL-drag in C++ version commented out 
2759         pt
.CalculateNewSize(x
, y
) 
2761         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2763     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2764         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2765         self
.GetCanvas().PrepareDC(dc
) 
2769         dc
.SetLogicalFunction(OGLRBLF
) 
2771         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2773         dist 
= math
.sqrt((x 
- self
.GetX()) * (x 
- self
.GetX()) + (y 
- self
.GetY()) * (y 
- self
.GetY())) 
2775         pt
._originalDistance 
= dist
 
2776         pt
._originalSize
[0] = bound_x
 
2777         pt
._originalSize
[1] = bound_y
 
2779         if pt
._originalDistance 
== 0: 
2780             pt
._originalDistance 
= 0.0001 
2782         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2783         dc
.SetPen(dottedPen
) 
2784         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2786         # Code for CTRL-drag in C++ version commented out 
2788         pt
.CalculateNewSize(x
, y
) 
2790         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2792         self
._canvas
.CaptureMouse() 
2794     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2795         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2796         self
.GetCanvas().PrepareDC(dc
) 
2798         if self
._canvas
.HasCapture(): 
2799             self
._canvas
.ReleaseMouse() 
2800         dc
.SetLogicalFunction(wx
.COPY
) 
2802         # If we're changing shape, must reset the original points 
2804             self
.CalculateBoundingBox() 
2805             self
.CalculatePolygonCentre() 
2807             self
.SetSize(pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2810         self
.ResetControlPoints() 
2811         self
.Move(dc
, self
.GetX(), self
.GetY()) 
2812         if not self
._canvas
.GetQuickEditMode(): 
2813             self
._canvas
.Redraw(dc
) 
2817 class EllipseShape(Shape
): 
2818     """The EllipseShape behaves similarly to the RectangleShape but is 
2824     def __init__(self
, w
, h
): 
2825         Shape
.__init
__(self
) 
2828         self
.SetDefaultRegionSize() 
2830     def GetBoundingBoxMin(self
): 
2831         return self
._width
, self
._height
 
2833     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2834         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2836         return DrawArcToEllipse(self
._xpos
, self
._ypos
, bound_x
, bound_y
, x2
, y2
, x1
, y1
) 
2841     def GetHeight(self
): 
2844     def SetWidth(self
, w
): 
2847     def SetHeight(self
, h
): 
2850     def OnDraw(self
, dc
): 
2851         if self
._shadowMode 
!= SHADOW_NONE
: 
2852             if self
._shadowBrush
: 
2853                 dc
.SetBrush(self
._shadowBrush
) 
2854             dc
.SetPen(TransparentPen
) 
2855             dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0 + self
._shadowOffsetX
, 
2856                            self
._ypos 
- self
.GetHeight() / 2.0 + self
._shadowOffsetY
, 
2857                            self
.GetWidth(), self
.GetHeight()) 
2860             if self
._pen
.GetWidth() == 0: 
2861                 dc
.SetPen(TransparentPen
) 
2863                 dc
.SetPen(self
._pen
) 
2865             dc
.SetBrush(self
._brush
) 
2866         dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0, self
._ypos 
- self
.GetHeight() / 2.0, self
.GetWidth(), self
.GetHeight()) 
2868     def SetSize(self
, x
, y
, recursive 
= True): 
2869         self
.SetAttachmentSize(x
, y
) 
2872         self
.SetDefaultRegionSize() 
2874     def GetNumberOfAttachments(self
): 
2875         return Shape
.GetNumberOfAttachments(self
) 
2877     # There are 4 attachment points on an ellipse - 0 = top, 1 = right, 
2878     # 2 = bottom, 3 = left. 
2879     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2880         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
2881             return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2883         if self
._attachmentMode 
!= ATTACHMENT_MODE_NONE
: 
2884             top 
= self
._ypos 
+ self
._height 
/ 2.0 
2885             bottom 
= self
._ypos 
- self
._height 
/ 2.0 
2886             left 
= self
._xpos 
- self
._width 
/ 2.0 
2887             right 
= self
._xpos 
+ self
._width 
/ 2.0 
2889             physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
2891             if physicalAttachment 
== 0: 
2892                 if self
._spaceAttachments
: 
2893                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2897                 # We now have the point on the bounding box: but get the point 
2898                 # on the ellipse by imagining a vertical line from 
2899                 # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting 
2902                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
- self
._height 
- 500, x
, self
._ypos
) 
2903             elif physicalAttachment 
== 1: 
2905                 if self
._spaceAttachments
: 
2906                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2909                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
+ self
._width 
+ 500, y
, self
._xpos
, y
) 
2910             elif physicalAttachment 
== 2: 
2911                 if self
._spaceAttachments
: 
2912                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2916                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
+ self
._height 
+ 500, x
, self
._ypos
) 
2917             elif physicalAttachment 
== 3: 
2919                 if self
._spaceAttachments
: 
2920                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2923                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
- self
._width 
- 500, y
, self
._xpos
, y
) 
2925                 return Shape
.GetAttachmentPosition(self
, attachment
, x
, y
, nth
, no_arcs
, line
) 
2927             return self
._xpos
, self
._ypos
 
2931 class CircleShape(EllipseShape
): 
2932     """An EllipseShape whose width and height are the same.""" 
2933     def __init__(self
, diameter
): 
2934         EllipseShape
.__init
__(self
, diameter
, diameter
) 
2935         self
.SetMaintainAspectRatio(True) 
2937     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2938         return FindEndForCircle(self
._width 
/ 2.0, self
._xpos
, self
._ypos
, x2
, y2
) 
2942 class TextShape(RectangleShape
): 
2943     """As wxRectangleShape, but only the text is displayed.""" 
2944     def __init__(self
, width
, height
): 
2945         RectangleShape
.__init
__(self
, width
, height
) 
2947     def OnDraw(self
, dc
): 
2952 class ShapeRegion(object): 
2953     """Object region.""" 
2954     def __init__(self
, region 
= None): 
2956             self
._regionText 
= region
._regionText
 
2957             self
._regionName 
= region
._regionName
 
2958             self
._textColour 
= region
._textColour
 
2960             self
._font 
= region
._font
 
2961             self
._minHeight 
= region
._minHeight
 
2962             self
._minWidth 
= region
._minWidth
 
2963             self
._width 
= region
._width
 
2964             self
._height 
= region
._height
 
2968             self
._regionProportionX 
= region
._regionProportionX
 
2969             self
._regionProportionY 
= region
._regionProportionY
 
2970             self
._formatMode 
= region
._formatMode
 
2971             self
._actualColourObject 
= region
._actualColourObject
 
2972             self
._actualPenObject 
= None 
2973             self
._penStyle 
= region
._penStyle
 
2974             self
._penColour 
= region
._penColour
 
2977             for line 
in region
._formattedText
: 
2978                 new_line 
= ShapeTextLine(line
.GetX(), line
.GetY(), line
.GetText()) 
2979                 self
._formattedText
.append(new_line
) 
2981             self
._regionText 
= "" 
2982             self
._font 
= NormalFont
 
2983             self
._minHeight 
= 5.0 
2984             self
._minWidth 
= 5.0 
2990             self
._regionProportionX 
= -1.0 
2991             self
._regionProportionY 
= -1.0 
2992             self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
2993             self
._regionName 
= "" 
2994             self
._textColour 
= "BLACK" 
2995             self
._penColour 
= "BLACK" 
2996             self
._penStyle 
= wx
.SOLID
 
2997             self
._actualColourObject 
= wx
.TheColourDatabase
.Find("BLACK") 
2998             self
._actualPenObject 
= None 
3000         self
._formattedText 
= [] 
3002     def ClearText(self
): 
3003         self
._formattedText 
= [] 
3005     def SetFont(self
, f
): 
3008     def SetMinSize(self
, w
, h
): 
3012     def SetSize(self
, w
, h
): 
3016     def SetPosition(self
, xp
, yp
): 
3020     def SetProportions(self
, xp
, yp
): 
3021         self
._regionProportionX 
= xp
 
3022         self
._regionProportionY 
= yp
 
3024     def SetFormatMode(self
, mode
): 
3025         self
._formatMode 
= mode
 
3027     def SetColour(self
, col
): 
3028         self
._textColour 
= col
 
3029         self
._actualColourObject 
= col
 
3031     def GetActualColourObject(self
): 
3032         self
._actualColourObject 
= wx
.TheColourDatabase
.Find(self
.GetColour()) 
3033         return self
._actualColourObject
 
3035     def SetPenColour(self
, col
): 
3036         self
._penColour 
= col
 
3037         self
._actualPenObject 
= None 
3039     # Returns NULL if the pen is invisible 
3040     # (different to pen being transparent; indicates that 
3041     # region boundary should not be drawn.) 
3042     def GetActualPen(self
): 
3043         if self
._actualPenObject
: 
3044             return self
._actualPenObject
 
3046         if not self
._penColour
: 
3048         if self
._penColour
=="Invisible": 
3050         self
._actualPenObject 
= wx
.Pen(self
._penColour
, 1, self
._penStyle
) 
3051         return self
._actualPenObject
 
3053     def SetText(self
, s
): 
3054         self
._regionText 
= s
 
3056     def SetName(self
, s
): 
3057         self
._regionName 
= s
 
3060         return self
._regionText
 
3065     def GetMinSize(self
): 
3066         return self
._minWidth
, self
._minHeight
 
3068     def GetProportion(self
): 
3069         return self
._regionProportionX
, self
._regionProportionY
 
3072         return self
._width
, self
._height
 
3074     def GetPosition(self
): 
3075         return self
._x
, self
._y
 
3077     def GetFormatMode(self
): 
3078         return self
._formatMode
 
3081         return self
._regionName
 
3083     def GetColour(self
): 
3084         return self
._textColour
 
3086     def GetFormattedText(self
): 
3087         return self
._formattedText
 
3089     def GetPenColour(self
): 
3090         return self
._penColour
 
3092     def GetPenStyle(self
): 
3093         return self
._penStyle
 
3095     def SetPenStyle(self
, style
): 
3096         self
._penStyle 
= style
 
3097         self
._actualPenObject 
= None 
3102     def GetHeight(self
): 
3107 class ControlPoint(RectangleShape
): 
3108     def __init__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, the_type
): 
3109         RectangleShape
.__init
__(self
, size
, size
) 
3111         self
._canvas 
= theCanvas
 
3112         self
._shape 
= object 
3113         self
._xoffset 
= the_xoffset
 
3114         self
._yoffset 
= the_yoffset
 
3115         self
._type 
= the_type
 
3116         self
.SetPen(BlackForegroundPen
) 
3117         self
.SetBrush(wx
.BLACK_BRUSH
) 
3118         self
._oldCursor 
= None 
3119         self
._visible 
= True 
3120         self
._eraseObject 
= True 
3122     # Don't even attempt to draw any text - waste of time 
3123     def OnDrawContents(self
, dc
): 
3126     def OnDraw(self
, dc
): 
3127         self
._xpos 
= self
._shape
.GetX() + self
._xoffset
 
3128         self
._ypos 
= self
._shape
.GetY() + self
._yoffset
 
3129         RectangleShape
.OnDraw(self
, dc
) 
3131     def OnErase(self
, dc
): 
3132         RectangleShape
.OnErase(self
, dc
) 
3134     # Implement resizing of canvas object 
3135     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3136         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3138     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3139         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3141     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3142         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3144     def GetNumberOfAttachments(self
): 
3147     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
3148         return self
._xpos
, self
._ypos
 
3150     def SetEraseObject(self
, er
): 
3151         self
._eraseObject 
= er
 
3154 class PolygonControlPoint(ControlPoint
): 
3155     def __init__(self
, theCanvas
, object, size
, vertex
, the_xoffset
, the_yoffset
): 
3156         ControlPoint
.__init
__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, 0) 
3157         self
._polygonVertex 
= vertex
 
3158         self
._originalDistance 
= 0.0 
3159         self
._newSize 
= wx
.RealPoint() 
3160         self
._originalSize 
= wx
.RealPoint() 
3162     def GetNewSize(self
): 
3163         return self
._newSize
 
3165     # Calculate what new size would be, at end of resize 
3166     def CalculateNewSize(self
, x
, y
): 
3167         bound_x
, bound_y 
= self
.GetShape().GetBoundingBoxMax() 
3168         dist 
= math
.sqrt((x 
- self
._shape
.GetX()) * (x 
- self
._shape
.GetX()) + (y 
- self
._shape
.GetY()) * (y 
- self
._shape
.GetY())) 
3170         self
._newSize
[0] = dist 
/ self
._originalDistance 
* self
._originalSize
[0] 
3171         self
._newSize
[1] = dist 
/ self
._originalDistance 
* self
._originalSize
[1] 
3173     # Implement resizing polygon or moving the vertex 
3174     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3175         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3177     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3178         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3180     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3181         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3183 from _canvas 
import * 
3184 from _lines 
import * 
3185 from _composit 
import *