1 # -*- coding: iso-8859-1 -*- 
   2 #---------------------------------------------------------------------------- 
   4 # Purpose:      The basic OGL shapes 
   6 # Author:       Pierre Hjälm (from C++ original by Julian Smart) 
  10 # Copyright:    (c) 2004 Pierre Hjälm - 1998 Julian Smart 
  11 # Licence:      wxWindows license 
  12 #---------------------------------------------------------------------------- 
  17 from _oglmisc 
import * 
  23     global WhiteBackgroundPen
, WhiteBackgroundBrush
, TransparentPen
 
  24     global BlackForegroundPen
, NormalFont
 
  26     WhiteBackgroundPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.SOLID
) 
  27     WhiteBackgroundBrush 
= wx
.Brush(wx
.WHITE
, wx
.SOLID
) 
  29     TransparentPen 
= wx
.Pen(wx
.WHITE
, 1, wx
.TRANSPARENT
) 
  30     BlackForegroundPen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
  32     NormalFont 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
  39 class ShapeTextLine(object): 
  40     def __init__(self
, the_x
, the_y
, the_line
): 
  57     def SetText(self
, text
): 
  65 class ShapeEvtHandler(object): 
  66     def __init__(self
, prev 
= None, shape 
= None): 
  67         self
._previousHandler 
= prev
 
  68         self
._handlerShape 
= shape
 
  73     def SetShape(self
, sh
): 
  74         self
._handlerShape 
= sh
 
  77         return self
._handlerShape
 
  79     def SetPreviousHandler(self
, handler
): 
  80         self
._previousHandler 
= handler
 
  82     def GetPreviousHandler(self
): 
  83         return self
._previousHandler
 
  86         if self
!=self
.GetShape(): 
  90         if self
._previousHandler
: 
  91             self
._previousHandler
.OnDraw(dc
) 
  93     def OnMoveLinks(self
, dc
): 
  94         if self
._previousHandler
: 
  95             self
._previousHandler
.OnMoveLinks(dc
) 
  97     def OnMoveLink(self
, dc
, moveControlPoints 
= True): 
  98         if self
._previousHandler
: 
  99             self
._previousHandler
.OnMoveLink(dc
, moveControlPoints
) 
 101     def OnDrawContents(self
, dc
): 
 102         if self
._previousHandler
: 
 103             self
._previousHandler
.OnDrawContents(dc
) 
 105     def OnDrawBranches(self
, dc
, erase 
= False): 
 106         if self
._previousHandler
: 
 107             self
._previousHandler
.OnDrawBranches(dc
, erase 
= erase
) 
 109     def OnSize(self
, x
, y
): 
 110         if self
._previousHandler
: 
 111             self
._previousHandler
.OnSize(x
, y
) 
 113     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 114         if self
._previousHandler
: 
 115             return self
._previousHandler
.OnMovePre(dc
, x
, y
, old_x
, old_y
, display
) 
 119     def OnMovePost(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 120         if self
._previousHandler
: 
 121             return self
._previousHandler
.OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
 125     def OnErase(self
, dc
): 
 126         if self
._previousHandler
: 
 127             self
._previousHandler
.OnErase(dc
) 
 129     def OnEraseContents(self
, dc
): 
 130         if self
._previousHandler
: 
 131             self
._previousHandler
.OnEraseContents(dc
) 
 133     def OnHighlight(self
, dc
): 
 134         if self
._previousHandler
: 
 135             self
._previousHandler
.OnHighlight(dc
) 
 137     def OnLeftClick(self
, x
, y
, keys
, attachment
): 
 138         if self
._previousHandler
: 
 139             self
._previousHandler
.OnLeftClick(x
, y
, keys
, attachment
) 
 141     def OnLeftDoubleClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 142         if self
._previousHandler
: 
 143             self
._previousHandler
.OnLeftDoubleClick(x
, y
, keys
, attachment
) 
 145     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 146         if self
._previousHandler
: 
 147             self
._previousHandler
.OnRightClick(x
, y
, keys
, attachment
) 
 149     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 150         if self
._previousHandler
: 
 151             self
._previousHandler
.OnDragLeft(draw
, x
, y
, keys
, attachment
) 
 153     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 154         if self
._previousHandler
: 
 155             self
._previousHandler
.OnBeginDragLeft(x
, y
, keys
, attachment
) 
 157     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 158         if self
._previousHandler
: 
 159             self
._previousHandler
.OnEndDragLeft(x
, y
, keys
, attachment
) 
 161     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 162         if self
._previousHandler
: 
 163             self
._previousHandler
.OnDragRight(draw
, x
, y
, keys
, attachment
) 
 165     def OnBeginDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 166         if self
._previousHandler
: 
 167             self
._previousHandler
.OnBeginDragRight(x
, y
, keys
, attachment
) 
 169     def OnEndDragRight(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 170         if self
._previousHandler
: 
 171             self
._previousHandler
.OnEndDragRight(x
, y
, keys
, attachment
) 
 173     # Control points ('handles') redirect control to the actual shape, 
 174     # to make it easier to override sizing behaviour. 
 175     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
 176         if self
._previousHandler
: 
 177             self
._previousHandler
.OnSizingDragLeft(pt
, draw
, x
, y
, keys
, attachment
) 
 179     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 180         if self
._previousHandler
: 
 181             self
._previousHandler
.OnSizingBeginDragLeft(pt
, x
, y
, keys
, attachment
) 
 183     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
 184         if self
._previousHandler
: 
 185             self
._previousHandler
.OnSizingEndDragLeft(pt
, x
, y
, keys
, attachment
) 
 187     def OnBeginSize(self
, w
, h
): 
 190     def OnEndSize(self
, w
, h
): 
 193     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
 194         if self
._previousHandler
: 
 195             self
._previousHandler
.OnDrawOutline(dc
, x
, y
, w
, h
) 
 197     def OnDrawControlPoints(self
, dc
): 
 198         if self
._previousHandler
: 
 199             self
._previousHandler
.OnDrawControlPoints(dc
) 
 201     def OnEraseControlPoints(self
, dc
): 
 202         if self
._previousHandler
: 
 203             self
._previousHandler
.OnEraseControlPoints(dc
) 
 205     # Can override this to prevent or intercept line reordering. 
 206     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 207         if self
._previousHandler
: 
 208             self
._previousHandler
.OnChangeAttachment(attachment
, line
, ordering
) 
 212 class Shape(ShapeEvtHandler
): 
 217     The wxShape is the top-level, abstract object that all other objects 
 218     are derived from. All common functionality is represented by wxShape's 
 219     members, and overriden members that appear in derived classes and have 
 220     behaviour as documented for wxShape, are not documented separately. 
 223     GraphicsInSizeToContents 
= False 
 225     def __init__(self
, canvas 
= None): 
 226         ShapeEvtHandler
.__init
__(self
) 
 228         self
._eventHandler 
= self
 
 231         self
._formatted 
= False 
 232         self
._canvas 
= canvas
 
 235         self
._pen 
= wx
.Pen(wx
.BLACK
, 1, wx
.SOLID
) 
 236         self
._brush 
= wx
.WHITE_BRUSH
 
 237         self
._font 
= wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
) 
 238         self
._textColour 
= wx
.BLACK
 
 239         self
._textColourName 
= wx
.BLACK
 
 240         self
._visible 
= False 
 241         self
._selected 
= False 
 242         self
._attachmentMode 
= ATTACHMENT_MODE_NONE
 
 243         self
._spaceAttachments 
= True 
 244         self
._disableLabel 
= False 
 245         self
._fixedWidth 
= False 
 246         self
._fixedHeight 
= False 
 247         self
._drawHandles 
= True 
 248         self
._sensitivity 
= OP_ALL
 
 249         self
._draggable 
= True 
 251         self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
 252         self
._shadowMode 
= SHADOW_NONE
 
 253         self
._shadowOffsetX 
= 6 
 254         self
._shadowOffsetY 
= 6 
 255         self
._shadowBrush 
= wx
.BLACK_BRUSH
 
 256         self
._textMarginX 
= 5 
 257         self
._textMarginY 
= 5 
 258         self
._regionName 
= "0" 
 259         self
._centreResize 
= True 
 260         self
._maintainAspectRatio 
= False 
 261         self
._highlighted 
= False 
 263         self
._branchNeckLength 
= 10 
 264         self
._branchStemLength 
= 10 
 265         self
._branchSpacing 
= 10 
 266         self
._branchStyle 
= BRANCHING_ATTACHMENT_NORMAL
 
 270         self
._controlPoints 
= [] 
 271         self
._attachmentPoints 
= [] 
 275         # Set up a default region. Much of the above will be put into 
 276         # the region eventually (the duplication is for compatibility) 
 277         region 
= ShapeRegion() 
 279         region
.SetFont(wx
.Font(10, wx
.SWISS
, wx
.NORMAL
, wx
.NORMAL
)) 
 280         region
.SetFormatMode(FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
) 
 281         region
.SetColour("BLACK") 
 282         self
._regions
.append(region
) 
 285         return "<%s.%s>" % (self
.__class
__.__module
__, self
.__class
__.__name
__) 
 287     def GetClassName(self
): 
 288         return str(self
.__class
__).split(".")[-1][:-2] 
 292             i 
= self
._parent
.GetChildren().index(self
) 
 293             self
._parent
.GetChildren(i
).remove(self
) 
 297         self
.ClearAttachments() 
 299         self
._handlerShape 
= None 
 302             self
.RemoveFromCanvas(self
._canvas
) 
 304         self
.GetEventHandler().OnDelete() 
 305         self
._eventHandler 
= None 
 308         ShapeEvtHandler
.__del
__(self
) 
 311         """TRUE if the shape may be dragged by the user.""" 
 314     def SetShape(self
, sh
): 
 315         self
._handlerShape 
= sh
 
 318         """Get the internal canvas.""" 
 321     def GetBranchStyle(self
): 
 322         return self
._branchStyle
 
 324     def GetRotation(self
): 
 325         """Return the angle of rotation in radians.""" 
 326         return self
._rotation
 
 328     def SetRotation(self
, rotation
): 
 329         self
._rotation 
= rotation
 
 331     def SetHighlight(self
, hi
, recurse 
= False): 
 332         """Set the highlight for a shape. Shape highlighting is unimplemented.""" 
 333         self
._highlighted 
= hi
 
 335             for shape 
in self
._children
: 
 336                 shape
.SetHighlight(hi
, recurse
) 
 338     def SetSensitivityFilter(self
, sens 
= OP_ALL
, recursive 
= False): 
 339         """Set the shape to be sensitive or insensitive to specific mouse 
 342         sens is a bitlist of the following: 
 348         * OP_ALL (equivalent to a combination of all the above). 
 350         self
._draggable 
= sens 
& OP_DRAG_LEFT
 
 352         self
._sensitivity 
= sens
 
 354             for shape 
in self
._children
: 
 355                 shape
.SetSensitivityFilter(sens
, True) 
 357     def SetDraggable(self
, drag
, recursive 
= False): 
 358         """Set the shape to be draggable or not draggable.""" 
 359         self
._draggable 
= drag
 
 361             self
._sensitivity |
= OP_DRAG_LEFT
 
 362         elif self
._sensitivity 
& OP_DRAG_LEFT
: 
 363             self
._sensitivity 
-= OP_DRAG_LEFT
 
 366             for shape 
in self
._children
: 
 367                 shape
.SetDraggable(drag
, True) 
 369     def SetDrawHandles(self
, drawH
): 
 370         """Set the drawHandles flag for this shape and all descendants. 
 371         If drawH is TRUE (the default), any handles (control points) will 
 372         be drawn. Otherwise, the handles will not be drawn. 
 374         self
._drawHandles 
= drawH
 
 375         for shape 
in self
._children
: 
 376             shape
.SetDrawHandles(drawH
) 
 378     def SetShadowMode(self
, mode
, redraw 
= False): 
 379         """Set the shadow mode (whether a shadow is drawn or not). 
 380         mode can be one of the following: 
 383           No shadow (the default).  
 385           Shadow on the left side.  
 387           Shadow on the right side. 
 389         if redraw 
and self
.GetCanvas(): 
 390             dc 
= wx
.ClientDC(self
.GetCanvas()) 
 391             self
.GetCanvas().PrepareDC(dc
) 
 393             self
._shadowMode 
= mode
 
 396             self
._shadowMode 
= mode
 
 398     def GetShadowMode(self
): 
 399         """Return the current shadow mode setting""" 
 400         return self
._shadowMode
 
 402     def SetCanvas(self
, theCanvas
): 
 403         """Identical to Shape.Attach.""" 
 404         self
._canvas 
= theCanvas
 
 405         for shape 
in self
._children
: 
 406             shape
.SetCanvas(theCanvas
) 
 408     def AddToCanvas(self
, theCanvas
, addAfter 
= None): 
 409         """Add the shape to the canvas's shape list. 
 410         If addAfter is non-NULL, will add the shape after this one. 
 412         theCanvas
.AddShape(self
, addAfter
) 
 415         for object in self
._children
: 
 416             object.AddToCanvas(theCanvas
, lastImage
) 
 419     def InsertInCanvas(self
, theCanvas
): 
 420         """Insert the shape at the front of the shape list of canvas.""" 
 421         theCanvas
.InsertShape(self
) 
 424         for object in self
._children
: 
 425             object.AddToCanvas(theCanvas
, lastImage
) 
 428     def RemoveFromCanvas(self
, theCanvas
): 
 429         """Remove the shape from the canvas.""" 
 434         theCanvas
.RemoveShape(self
) 
 435         for object in self
._children
: 
 436             object.RemoveFromCanvas(theCanvas
) 
 438     def ClearAttachments(self
): 
 439         """Clear internal custom attachment point shapes (of class 
 442         self
._attachmentPoints 
= [] 
 444     def ClearText(self
, regionId 
= 0): 
 445         """Clear the text from the specified text region.""" 
 448         if regionId 
< len(self
._regions
): 
 449             self
._regions
[regionId
].ClearText() 
 451     def ClearRegions(self
): 
 452         """Clear the ShapeRegions from the shape.""" 
 455     def AddRegion(self
, region
): 
 456         """Add a region to the shape.""" 
 457         self
._regions
.append(region
) 
 459     def SetDefaultRegionSize(self
): 
 460         """Set the default region to be consistent with the shape size.""" 
 461         if not self
._regions
: 
 463         w
, h 
= self
.GetBoundingBoxMax() 
 464         self
._regions
[0].SetSize(w
, h
) 
 466     def HitTest(self
, x
, y
): 
 467         """Given a point on a canvas, returns TRUE if the point was on the 
 468         shape, and returns the nearest attachment point and distance from 
 469         the given point and target. 
 471         width
, height 
= self
.GetBoundingBoxMax() 
 477         width 
+= 4 # Allowance for inaccurate mousing 
 480         left 
= self
._xpos 
- width 
/ 2.0 
 481         top 
= self
._ypos 
- height 
/ 2.0 
 482         right 
= self
._xpos 
+ width 
/ 2.0 
 483         bottom 
= self
._ypos 
+ height 
/ 2.0 
 485         nearest_attachment 
= 0 
 487         # If within the bounding box, check the attachment points 
 489         if x 
>= left 
and x 
<= right 
and y 
>= top 
and y 
<= bottom
: 
 490             n 
= self
.GetNumberOfAttachments() 
 493             # GetAttachmentPosition[Edge] takes a logical attachment position, 
 494             # i.e. if it's rotated through 90%, position 0 is East-facing. 
 497                 e 
= self
.GetAttachmentPositionEdge(i
) 
 500                     l 
= math
.sqrt(((xp 
- x
) * (xp 
- x
)) + (yp 
- y
) * (yp 
- y
)) 
 503                         nearest_attachment 
= i
 
 505             return nearest_attachment
, nearest
 
 508     # Format a text string according to the region size, adding 
 509     # strings with positions to region text list 
 511     def FormatText(self
, dc
, s
, i 
= 0): 
 512         """Reformat the given text region; defaults to formatting the 
 517         if not self
._regions
: 
 520         if i 
> len(self
._regions
): 
 523         region 
= self
._regions
[i
] 
 524         region
._regionText 
= s
 
 525         dc
.SetFont(region
.GetFont()) 
 527         w
, h 
= region
.GetSize() 
 529         stringList 
= FormatText(dc
, s
, (w 
- 2 * self
._textMarginX
), (h 
- 2 * self
._textMarginY
), region
.GetFormatMode()) 
 531             line 
= ShapeTextLine(0.0, 0.0, s
) 
 532             region
.GetFormattedText().append(line
) 
 536         # Don't try to resize an object with more than one image (this 
 537         # case should be dealt with by overriden handlers) 
 538         if (region
.GetFormatMode() & FORMAT_SIZE_TO_CONTENTS
) and \
 
 539            len(region
.GetFormattedText()) and \
 
 540            len(self
._regions
) == 1 and \
 
 541            not Shape
.GraphicsInSizeToContents
: 
 543             actualW
, actualH 
= GetCentredTextExtent(dc
, region
.GetFormattedText()) 
 544             if actualW 
+ 2 * self
._textMarginX 
!= w 
or actualH 
+ 2 * self
._textMarginY 
!= h
: 
 545                 # If we are a descendant of a composite, must make sure 
 546                 # the composite gets resized properly 
 548                 topAncestor 
= self
.GetTopAncestor() 
 549                 if topAncestor 
!= self
: 
 550                     Shape
.GraphicsInSizeToContents 
= True 
 552                     composite 
= topAncestor
 
 554                     self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 555                     self
.Move(dc
, self
._xpos
, self
._ypos
) 
 556                     composite
.CalculateSize() 
 557                     if composite
.Selected(): 
 558                         composite
.DeleteControlPoints(dc
) 
 559                         composite
.MakeControlPoints() 
 560                         composite
.MakeMandatoryControlPoints() 
 561                     # Where infinite recursion might happen if we didn't stop it 
 563                     Shape
.GraphicsInSizeToContents 
= False 
 567                 self
.SetSize(actualW 
+ 2 * self
._textMarginX
, actualH 
+ 2 * self
._textMarginY
) 
 568                 self
.Move(dc
, self
._xpos
, self
._ypos
) 
 569                 self
.EraseContents(dc
) 
 570         CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, actualW 
- 2 * self
._textMarginX
, actualH 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 571         self
._formatted 
= True 
 573     def Recentre(self
, dc
): 
 574         """Do recentring (or other formatting) for all the text regions 
 577         w
, h 
= self
.GetBoundingBoxMin() 
 578         for region 
in self
._regions
: 
 579             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, w 
- 2 * self
._textMarginX
, h 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 581     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
 582         """Get the point at which the line from (x1, y1) to (x2, y2) hits 
 583         the shape. Returns False if the line doesn't hit the perimeter. 
 587     def SetPen(self
, the_pen
): 
 588         """Set the pen for drawing the shape's outline.""" 
 591     def SetBrush(self
, the_brush
): 
 592         """Set the brush for filling the shape's shape.""" 
 593         self
._brush 
= the_brush
 
 595     # Get the top - most (non-division) ancestor, or self 
 596     def GetTopAncestor(self
): 
 597         """Return the top-most ancestor of this shape (the root of 
 600         if not self
.GetParent(): 
 603         if isinstance(self
.GetParent(), DivisionShape
): 
 605         return self
.GetParent().GetTopAncestor() 
 608     def SetFont(self
, the_font
, regionId 
= 0): 
 609         """Set the font for the specified text region.""" 
 610         self
._font 
= the_font
 
 611         if regionId 
< len(self
._regions
): 
 612             self
._regions
[regionId
].SetFont(the_font
) 
 614     def GetFont(self
, regionId 
= 0): 
 615         """Get the font for the specified text region.""" 
 616         if regionId 
>= len(self
._regions
): 
 618         return self
._regions
[regionId
].GetFont() 
 620     def SetFormatMode(self
, mode
, regionId 
= 0): 
 621         """Set the format mode of the default text region. The argument 
 622         can be a bit list of the following: 
 631         if regionId 
< len(self
._regions
): 
 632             self
._regions
[regionId
].SetFormatMode(mode
) 
 634     def GetFormatMode(self
, regionId 
= 0): 
 635         if regionId 
>= len(self
._regions
): 
 637         return self
._regions
[regionId
].GetFormatMode() 
 639     def SetTextColour(self
, the_colour
, regionId 
= 0): 
 640         """Set the colour for the specified text region.""" 
 641         self
._textColour 
= wx
.TheColourDatabase
.Find(the_colour
) 
 642         self
._textColourName 
= the_colour
 
 644         if regionId 
< len(self
._regions
): 
 645             self
._regions
[regionId
].SetColour(the_colour
) 
 647     def GetTextColour(self
, regionId 
= 0): 
 648         """Get the colour for the specified text region.""" 
 649         if regionId 
>= len(self
._regions
): 
 651         return self
._regions
[regionId
].GetColour() 
 653     def SetRegionName(self
, name
, regionId 
= 0): 
 654         """Set the name for this region. 
 655         The name for a region is unique within the scope of the whole 
 656         composite, whereas a region id is unique only for a single image. 
 658         if regionId 
< len(self
._regions
): 
 659             self
._regions
[regionId
].SetName(name
) 
 661     def GetRegionName(self
, regionId 
= 0): 
 662         """Get the region's name. 
 663         A region's name can be used to uniquely determine a region within 
 664         an entire composite image hierarchy. See also Shape.SetRegionName. 
 666         if regionId 
>= len(self
._regions
): 
 668         return self
._regions
[regionId
].GetName() 
 670     def GetRegionId(self
, name
): 
 671         """Get the region's identifier by name. 
 672         This is not unique for within an entire composite, but is unique 
 675         for i
, r 
in enumerate(self
._regions
): 
 676             if r
.GetName() == name
: 
 680     # Name all _regions in all subimages recursively 
 681     def NameRegions(self
, parentName
=""): 
 682         """Make unique names for all the regions in a shape or composite shape.""" 
 683         n 
= self
.GetNumberOfTextRegions() 
 686                 buff 
= parentName
+"."+str(i
) 
 689             self
.SetRegionName(buff
, i
) 
 691         for j
, child 
in enumerate(self
._children
): 
 693                 buff 
= parentName
+"."+str(j
) 
 696             child
.NameRegions(buff
) 
 698     # Get a region by name, possibly looking recursively into composites 
 699     def FindRegion(self
, name
): 
 700         """Find the actual image ('this' if non-composite) and region id 
 701         for the given region name. 
 703         id = self
.GetRegionId(name
) 
 707         for child 
in self
._children
: 
 708             actualImage
, regionId 
= child
.FindRegion(name
) 
 710                 return actualImage
, regionId
 
 714     # Finds all region names for this image (composite or simple). 
 715     def FindRegionNames(self
): 
 716         """Get a list of all region names for this image (composite or simple).""" 
 718         n 
= self
.GetNumberOfTextRegions() 
 720             list.append(self
.GetRegionName(i
)) 
 722         for child 
in self
._children
: 
 723             list += child
.FindRegionNames() 
 727     def AssignNewIds(self
): 
 728         """Assign new ids to this image and its children.""" 
 729         self
._id 
= wx
.NewId() 
 730         for child 
in self
._children
: 
 733     def OnDraw(self
, dc
): 
 736     def OnMoveLinks(self
, dc
): 
 737         # Want to set the ends of all attached links 
 738         # to point to / from this object 
 740         for line 
in self
._lines
: 
 741             line
.GetEventHandler().OnMoveLink(dc
) 
 743     def OnDrawContents(self
, dc
): 
 744         if not self
._regions
: 
 747         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
 752         region 
= self
._regions
[0] 
 754             dc
.SetFont(region
.GetFont()) 
 756         dc
.SetTextForeground(region
.GetActualColourObject()) 
 757         dc
.SetBackgroundMode(wx
.TRANSPARENT
) 
 758         if not self
._formatted
: 
 759             CentreText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 760             self
._formatted 
= True 
 762         if not self
.GetDisableLabel(): 
 763             DrawFormattedText(dc
, region
.GetFormattedText(), self
._xpos
, self
._ypos
, bound_x 
- 2 * self
._textMarginX
, bound_y 
- 2 * self
._textMarginY
, region
.GetFormatMode()) 
 766     def DrawContents(self
, dc
): 
 767         """Draw the internal graphic of the shape (such as text). 
 769         Do not override this function: override OnDrawContents, which 
 770         is called by this function. 
 772         self
.GetEventHandler().OnDrawContents(dc
) 
 774     def OnSize(self
, x
, y
): 
 777     def OnMovePre(self
, dc
, x
, y
, old_x
, old_y
, display 
= True): 
 780     def OnErase(self
, dc
): 
 781         if not self
._visible
: 
 785         for line 
in self
._lines
: 
 786             line
.GetEventHandler().OnErase(dc
) 
 788         self
.GetEventHandler().OnEraseContents(dc
) 
 790     def OnEraseContents(self
, dc
): 
 791         if not self
._visible
: 
 794         xp
, yp 
= self
.GetX(), self
.GetY() 
 795         minX
, minY 
= self
.GetBoundingBoxMin() 
 796         maxX
, maxY 
= self
.GetBoundingBoxMax() 
 798         topLeftX 
= xp 
- maxX 
/ 2.0 - 2 
 799         topLeftY 
= yp 
- maxY 
/ 2.0 - 2 
 803             penWidth 
= self
._pen
.GetWidth() 
 805         dc
.SetPen(self
.GetBackgroundPen()) 
 806         dc
.SetBrush(self
.GetBackgroundBrush()) 
 808         dc
.DrawRectangle(topLeftX 
- penWidth
, topLeftY 
- penWidth
, maxX 
+ penWidth 
* 2 + 4, maxY 
+ penWidth 
* 2 + 4) 
 810     def EraseLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 811         """Erase links attached to this shape, but do not repair damage 
 812         caused to other shapes. 
 814         if not self
._visible
: 
 817         for line 
in self
._lines
: 
 818             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 819                 line
.GetEventHandler().OnErase(dc
) 
 822             for child 
in self
._children
: 
 823                 child
.EraseLinks(dc
, attachment
, recurse
) 
 825     def DrawLinks(self
, dc
, attachment 
= -1, recurse 
= False): 
 826         """Draws any lines linked to this shape.""" 
 827         if not self
._visible
: 
 830         for line 
in self
._lines
: 
 831             if attachment 
== -1 or (line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
): 
 835             for child 
in self
._children
: 
 836                 child
.DrawLinks(dc
, attachment
, recurse
) 
 838     #  Returns TRUE if pt1 <= pt2 in the sense that one point comes before 
 839     #  another on an edge of the shape. 
 840     # attachmentPoint is the attachment point (= side) in question. 
 842     # This is the default, rectangular implementation. 
 843     def AttachmentSortTest(self
, attachmentPoint
, pt1
, pt2
): 
 844         """Return TRUE if pt1 is less than or equal to pt2, in the sense 
 845         that one point comes before another on an edge of the shape. 
 847         attachment is the attachment point (side) in question. 
 849         This function is used in Shape.MoveLineToNewAttachment to determine 
 850         the new line ordering. 
 852         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachmentPoint
) 
 853         if physicalAttachment 
in [0, 2]: 
 854             return pt1
[0] <= pt2
[0] 
 855         elif physicalAttachment 
in [1, 3]: 
 856             return pt1
[1] <= pt2
[1] 
 860     def MoveLineToNewAttachment(self
, dc
, to_move
, x
, y
): 
 861         """Move the given line (which must already be attached to the shape) 
 862         to a different attachment point on the shape, or a different order 
 863         on the same attachment. 
 865         Calls Shape.AttachmentSortTest and then 
 866         ShapeEvtHandler.OnChangeAttachment. 
 868         if self
.GetAttachmentMode() == ATTACHMENT_MODE_NONE
: 
 871         # Is (x, y) on this object? If so, find the new attachment point 
 872         # the user has moved the point to 
 873         hit 
= self
.HitTest(x
, y
) 
 877         newAttachment
, distance 
= hit
 
 881         if to_move
.GetTo() == self
: 
 882             oldAttachment 
= to_move
.GetAttachmentTo() 
 884             oldAttachment 
= to_move
.GetAttachmentFrom() 
 886         # The links in a new ordering 
 887         # First, add all links to the new list 
 888         newOrdering 
= self
._lines
[:] 
 890         # Delete the line object from the list of links; we're going to move 
 891         # it to another position in the list 
 892         del newOrdering
[newOrdering
.index(to_move
)] 
 899         for line 
in newOrdering
: 
 900             if line
.GetTo() == self 
and oldAttachment 
== line
.GetAttachmentTo() or \
 
 901                line
.GetFrom() == self 
and oldAttachment 
== line
.GetAttachmentFrom(): 
 902                 startX
, startY
, endX
, endY 
= line
.GetEnds() 
 903                 if line
.GetTo() == self
: 
 910                 thisPoint 
= wx
.RealPoint(xp
, yp
) 
 911                 lastPoint 
= wx
.RealPoint(old_x
, old_y
) 
 912                 newPoint 
= wx
.RealPoint(x
, y
) 
 914                 if self
.AttachmentSortTest(newAttachment
, newPoint
, thisPoint
) and self
.AttachmentSortTest(newAttachment
, lastPoint
, newPoint
): 
 916                     newOrdering
.insert(newOrdering
.index(line
), to_move
) 
 924             newOrdering
.append(to_move
) 
 926         self
.GetEventHandler().OnChangeAttachment(newAttachment
, to_move
, newOrdering
) 
 929     def OnChangeAttachment(self
, attachment
, line
, ordering
): 
 930         if line
.GetTo() == self
: 
 931             line
.SetAttachmentTo(attachment
) 
 933             line
.SetAttachmentFrom(attachment
) 
 935         self
.ApplyAttachmentOrdering(ordering
) 
 937         dc 
= wx
.ClientDC(self
.GetCanvas()) 
 938         self
.GetCanvas().PrepareDC(dc
) 
 941         if not self
.GetCanvas().GetQuickEditMode(): 
 942             self
.GetCanvas().Redraw(dc
) 
 944     # Reorders the lines according to the given list 
 945     def ApplyAttachmentOrdering(self
, linesToSort
): 
 946         """Apply the line ordering in linesToSort to the shape, to reorder 
 947         the way lines are attached. 
 949         linesStore 
= self
._lines
[:] 
 953         for line 
in linesToSort
: 
 954             if line 
in linesStore
: 
 955                 del linesStore
[linesStore
.index(line
)] 
 956                 self
._lines
.append(line
) 
 958         # Now add any lines that haven't been listed in linesToSort 
 959         self
._lines 
+= linesStore
 
 961     def SortLines(self
, attachment
, linesToSort
): 
 962         """ Reorder the lines coming into the node image at this attachment 
 963         position, in the order in which they appear in linesToSort. 
 965         Any remaining lines not in the list will be added to the end. 
 967         # This is a temporary store of all the lines at this attachment 
 968         # point. We'll tick them off as we've processed them. 
 969         linesAtThisAttachment 
= [] 
 971         for line 
in self
._lines
[:]: 
 972             if line
.GetTo() == self 
and line
.GetAttachmentTo() == attachment 
or \
 
 973                line
.GetFrom() == self 
and line
.GetAttachmentFrom() == attachment
: 
 974                 linesAtThisAttachment
.append(line
) 
 975                 del self
._lines
[self
._lines
.index(line
)] 
 977         for line 
in linesToSort
: 
 978             if line 
in linesAtThisAttachment
: 
 980                 del linesAtThisAttachment
[linesAtThisAttachment
.index(line
)] 
 981                 self
._lines
.append(line
) 
 983         # Now add any lines that haven't been listed in linesToSort 
 984         self
._lines 
+= linesAtThisAttachment
 
 986     def OnHighlight(self
, dc
): 
 989     def OnLeftClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 990         if self
._sensitivity 
& OP_CLICK_LEFT 
!= OP_CLICK_LEFT
: 
 992                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 993                 self
._parent
.GetEventHandler().OnLeftClick(x
, y
, keys
, attachment
) 
 995     def OnRightClick(self
, x
, y
, keys 
= 0, attachment 
= 0): 
 996         if self
._sensitivity 
& OP_CLICK_RIGHT 
!= OP_CLICK_RIGHT
: 
 997             attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
 998             self
._parent
.GetEventHandler().OnRightClick(x
, y
, keys
, attachment
) 
1000     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1001         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1003                 hit 
= self
._parent
.HitTest(x
, y
) 
1005                     attachment
, dist 
= hit
 
1006                 self
._parent
.GetEventHandler().OnDragLeft(draw
, x
, y
, keys
, attachment
) 
1009         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1010         self
.GetCanvas().PrepareDC(dc
) 
1011         dc
.SetLogicalFunction(OGLRBLF
) 
1013         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1014         dc
.SetPen(dottedPen
) 
1015         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1017         xx 
= x 
+ DragOffsetX
 
1018         yy 
= y 
+ DragOffsetY
 
1020         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1021         w
, h 
= self
.GetBoundingBoxMax() 
1022         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1024     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1025         global DragOffsetX
, DragOffsetY
 
1027         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1029                 hit 
= self
._parent
.HitTest(x
, y
) 
1031                     attachment
, dist 
= hit
 
1032                 self
._parent
.GetEventHandler().OnBeginDragLeft(x
, y
, keys
, attachment
) 
1035         DragOffsetX 
= self
._xpos 
- x
 
1036         DragOffsetY 
= self
._ypos 
- y
 
1038         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1039         self
.GetCanvas().PrepareDC(dc
) 
1041         # New policy: don't erase shape until end of drag. 
1043         xx 
= x 
+ DragOffsetX
 
1044         yy 
= y 
+ DragOffsetY
 
1045         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1046         dc
.SetLogicalFunction(OGLRBLF
) 
1048         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
1049         dc
.SetPen(dottedPen
) 
1050         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1052         w
, h 
= self
.GetBoundingBoxMax() 
1053         self
.GetEventHandler().OnDrawOutline(dc
, xx
, yy
, w
, h
) 
1054         self
._canvas
.CaptureMouse() 
1056     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
1057         if self
._canvas
.HasCapture(): 
1058             self
._canvas
.ReleaseMouse() 
1059         if self
._sensitivity 
& OP_DRAG_LEFT 
!= OP_DRAG_LEFT
: 
1061                 hit 
= self
._parent
.HitTest(x
, y
) 
1063                     attachment
, dist 
= hit
 
1064                 self
._parent
.GetEventHandler().OnEndDragLeft(x
, y
, keys
, attachment
) 
1067         dc 
= wx
.ClientDC(self
.GetCanvas()) 
1068         self
.GetCanvas().PrepareDC(dc
) 
1070         dc
.SetLogicalFunction(wx
.COPY
) 
1071         xx 
= x 
+ DragOffsetX
 
1072         yy 
= y 
+ DragOffsetY
 
1073         xx
, yy 
= self
._canvas
.Snap(xx
, yy
) 
1075         # New policy: erase shape at end of drag. 
1078         self
.Move(dc
, xx
, yy
) 
1079         if self
._canvas 
and not self
._canvas
.GetQuickEditMode(): 
1080             self
._canvas
.Redraw(dc
) 
1082     def OnDragRight(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
1083         if self
._sensitivity 
& OP_DRAG_RIGHT 
!= OP_DRAG_RIGHT
: 
1085                 attachment
, dist 
= self
._parent
.HitTest(x
, y
) 
1086                 self
._parent
.GetEventHandler().OnDragRight(draw
, x
, y
, keys
, attachment
) 
1089     def OnBeginDragRight(self
, 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().OnBeginDragRight(x
, y
, keys
, attachment
) 
1096     def OnEndDragRight(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().OnEndDragRight(x
, y
, keys
, attachment
) 
1103     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
1104         points 
= [[x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1105                 [x 
+ w 
/ 2.0, y 
- h 
/ 2.0], 
1106                 [x 
+ w 
/ 2.0, y 
+ h 
/ 2.0], 
1107                 [x 
- w 
/ 2.0, y 
+ h 
/ 2.0], 
1108                 [x 
- w 
/ 2.0, y 
- h 
/ 2.0], 
1111         dc
.DrawLines(points
) 
1113     def Attach(self
, can
): 
1114         """Set the shape's internal canvas pointer to point to the given canvas.""" 
1118         """Disassociates the shape from its canvas.""" 
1121     def Move(self
, dc
, x
, y
, display 
= True): 
1122         """Move the shape to the given position. 
1123         Redraw if display is TRUE. 
1128         if not self
.GetEventHandler().OnMovePre(dc
, x
, y
, old_x
, old_y
, display
): 
1131         self
._xpos
, self
._ypos 
= x
, y
 
1133         self
.ResetControlPoints() 
1140         self
.GetEventHandler().OnMovePost(dc
, x
, y
, old_x
, old_y
, display
) 
1142     def MoveLinks(self
, dc
): 
1143         """Redraw all the lines attached to the shape.""" 
1144         self
.GetEventHandler().OnMoveLinks(dc
) 
1147         """Draw the whole shape and any lines attached to it. 
1149         Do not override this function: override OnDraw, which is called 
1153             self
.GetEventHandler().OnDraw(dc
) 
1154             self
.GetEventHandler().OnDrawContents(dc
) 
1155             self
.GetEventHandler().OnDrawControlPoints(dc
) 
1156             self
.GetEventHandler().OnDrawBranches(dc
) 
1159         """Flash the shape.""" 
1160         if self
.GetCanvas(): 
1161             dc 
= wx
.ClientDC(self
.GetCanvas()) 
1162             self
.GetCanvas
.PrepareDC(dc
) 
1164             dc
.SetLogicalFunction(OGLRBLF
) 
1166             dc
.SetLogicalFunction(wx
.COPY
) 
1169     def Show(self
, show
): 
1170         """Set a flag indicating whether the shape should be drawn.""" 
1171         self
._visible 
= show
 
1172         for child 
in self
._children
: 
1175     def Erase(self
, dc
): 
1177         Does not repair damage caused to other shapes. 
1179         self
.GetEventHandler().OnErase(dc
) 
1180         self
.GetEventHandler().OnEraseControlPoints(dc
) 
1181         self
.GetEventHandler().OnDrawBranches(dc
, erase 
= True) 
1183     def EraseContents(self
, dc
): 
1184         """Erase the shape contents, that is, the area within the shape's 
1185         minimum bounding box. 
1187         self
.GetEventHandler().OnEraseContents(dc
) 
1189     def AddText(self
, string
): 
1190         """Add a line of text to the shape's default text region.""" 
1191         if not self
._regions
: 
1194         region 
= self
._regions
[0] 
1196         new_line 
= ShapeTextLine(0, 0, string
) 
1197         text 
= region
.GetFormattedText() 
1198         text
.append(new_line
) 
1200         self
._formatted 
= False 
1202     def SetSize(self
, x
, y
, recursive 
= True): 
1203         """Set the shape's size.""" 
1204         self
.SetAttachmentSize(x
, y
) 
1205         self
.SetDefaultRegionSize() 
1207     def SetAttachmentSize(self
, w
, h
): 
1208         width
, height 
= self
.GetBoundingBoxMin() 
1212             scaleX 
= float(w
) / width
 
1216             scaleY 
= float(h
) / height
 
1218         for point 
in self
._attachmentPoints
: 
1219             point
._x 
= point
._x 
* scaleX
 
1220             point
._y 
= point
._y 
* scaleY
 
1222     # Add line FROM this object 
1223     def AddLine(self
, line
, other
, attachFrom 
= 0, attachTo 
= 0, positionFrom 
= -1, positionTo 
= -1): 
1224         """Add a line between this shape and the given other shape, at the 
1225         specified attachment points. 
1227         The position in the list of lines at each end can also be specified, 
1228         so that the line will be drawn at a particular point on its attachment 
1231         if positionFrom 
== -1: 
1232             if not line 
in self
._lines
: 
1233                 self
._lines
.append(line
) 
1235             # Don't preserve old ordering if we have new ordering instructions 
1237                 self
._lines
.remove(line
) 
1240             if positionFrom 
< len(self
._lines
): 
1241                 self
._lines
.insert(positionFrom
, line
) 
1243                 self
._lines
.append(line
) 
1245         if positionTo 
== -1: 
1246             if not other 
in other
._lines
: 
1247                 other
._lines
.append(line
) 
1249             # Don't preserve old ordering if we have new ordering instructions 
1251                 other
._lines
.remove(line
) 
1254             if positionTo 
< len(other
._lines
): 
1255                 other
._lines
.insert(positionTo
, line
) 
1257                 other
._lines
.append(line
) 
1261         line
.SetAttachments(attachFrom
, attachTo
) 
1263         dc 
= wx
.ClientDC(self
._canvas
) 
1264         self
._canvas
.PrepareDC(dc
) 
1267     def RemoveLine(self
, line
): 
1268         """Remove the given line from the shape's list of attached lines.""" 
1269         if line
.GetFrom() == self
: 
1270             line
.GetTo()._lines
.remove(line
) 
1272             line
.GetFrom()._lines
.remove(line
) 
1274         self
._lines
.remove(line
) 
1276     # Default - make 6 control points 
1277     def MakeControlPoints(self
): 
1278         """Make a list of control points (draggable handles) appropriate to 
1281         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1282         minX
, minY 
= self
.GetBoundingBoxMin() 
1284         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1285         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1287         # Offsets from main object 
1288         top 
= -heightMin 
/ 2.0 
1289         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1290         left 
= -widthMin 
/ 2.0 
1291         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1293         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, top
, CONTROL_POINT_DIAGONAL
) 
1294         self
._canvas
.AddShape(control
) 
1295         self
._controlPoints
.append(control
) 
1297         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, top
, CONTROL_POINT_VERTICAL
) 
1298         self
._canvas
.AddShape(control
) 
1299         self
._controlPoints
.append(control
) 
1301         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, top
, CONTROL_POINT_DIAGONAL
) 
1302         self
._canvas
.AddShape(control
) 
1303         self
._controlPoints
.append(control
) 
1305         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, 0, CONTROL_POINT_HORIZONTAL
) 
1306         self
._canvas
.AddShape(control
) 
1307         self
._controlPoints
.append(control
) 
1309         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, right
, bottom
, CONTROL_POINT_DIAGONAL
) 
1310         self
._canvas
.AddShape(control
) 
1311         self
._controlPoints
.append(control
) 
1313         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, 0, bottom
, CONTROL_POINT_VERTICAL
) 
1314         self
._canvas
.AddShape(control
) 
1315         self
._controlPoints
.append(control
) 
1317         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, bottom
, CONTROL_POINT_DIAGONAL
) 
1318         self
._canvas
.AddShape(control
) 
1319         self
._controlPoints
.append(control
) 
1321         control 
= ControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, left
, 0, CONTROL_POINT_HORIZONTAL
) 
1322         self
._canvas
.AddShape(control
) 
1323         self
._controlPoints
.append(control
) 
1325     def MakeMandatoryControlPoints(self
): 
1326         """Make the mandatory control points. 
1328         For example, the control point on a dividing line should appear even 
1329         if the divided rectangle shape's handles should not appear (because 
1330         it is the child of a composite, and children are not resizable). 
1332         for child 
in self
._children
: 
1333             child
.MakeMandatoryControlPoints() 
1335     def ResetMandatoryControlPoints(self
): 
1336         """Reset the mandatory control points.""" 
1337         for child 
in self
._children
: 
1338             child
.ResetMandatoryControlPoints() 
1340     def ResetControlPoints(self
): 
1341         """Reset the positions of the control points (for instance when the 
1342         shape's shape has changed). 
1344         self
.ResetMandatoryControlPoints() 
1346         if len(self
._controlPoints
) == 0: 
1349         maxX
, maxY 
= self
.GetBoundingBoxMax() 
1350         minX
, minY 
= self
.GetBoundingBoxMin() 
1352         widthMin 
= minX 
+ CONTROL_POINT_SIZE 
+ 2 
1353         heightMin 
= minY 
+ CONTROL_POINT_SIZE 
+ 2 
1355         # Offsets from main object 
1356         top 
= -heightMin 
/ 2.0 
1357         bottom 
= heightMin 
/ 2.0 + (maxY 
- minY
) 
1358         left 
= -widthMin 
/ 2.0 
1359         right 
= widthMin 
/ 2.0 + (maxX 
- minX
) 
1361         self
._controlPoints
[0]._xoffset 
= left
 
1362         self
._controlPoints
[0]._yoffset 
= top
 
1364         self
._controlPoints
[1]._xoffset 
= 0 
1365         self
._controlPoints
[1]._yoffset 
= top
 
1367         self
._controlPoints
[2]._xoffset 
= right
 
1368         self
._controlPoints
[2]._yoffset 
= top
 
1370         self
._controlPoints
[3]._xoffset 
= right
 
1371         self
._controlPoints
[3]._yoffset 
= 0 
1373         self
._controlPoints
[4]._xoffset 
= right
 
1374         self
._controlPoints
[4]._yoffset 
= bottom
 
1376         self
._controlPoints
[5]._xoffset 
= 0 
1377         self
._controlPoints
[5]._yoffset 
= bottom
 
1379         self
._controlPoints
[6]._xoffset 
= left
 
1380         self
._controlPoints
[6]._yoffset 
= bottom
 
1382         self
._controlPoints
[7]._xoffset 
= left
 
1383         self
._controlPoints
[7]._yoffset 
= 0 
1385     def DeleteControlPoints(self
, dc 
= None): 
1386         """Delete the control points (or handles) for the shape. 
1388         Does not redraw the shape. 
1390         for control 
in self
._controlPoints
[:]: 
1392                 control
.GetEventHandler().OnErase(dc
) 
1394             self
._controlPoints
.remove(control
) 
1395         self
._controlPoints 
= [] 
1397         # Children of divisions are contained objects, 
1399         if not isinstance(self
, DivisionShape
): 
1400             for child 
in self
._children
: 
1401                 child
.DeleteControlPoints(dc
) 
1403     def OnDrawControlPoints(self
, dc
): 
1404         if not self
._drawHandles
: 
1407         dc
.SetBrush(wx
.BLACK_BRUSH
) 
1408         dc
.SetPen(wx
.BLACK_PEN
) 
1410         for control 
in self
._controlPoints
: 
1413         # Children of divisions are contained objects, 
1415         # This test bypasses the type facility for speed 
1416         # (critical when drawing) 
1418         if not isinstance(self
, DivisionShape
): 
1419             for child 
in self
._children
: 
1420                 child
.GetEventHandler().OnDrawControlPoints(dc
) 
1422     def OnEraseControlPoints(self
, dc
): 
1423         for control 
in self
._controlPoints
: 
1426         if not isinstance(self
, DivisionShape
): 
1427             for child 
in self
._children
: 
1428                 child
.GetEventHandler().OnEraseControlPoints(dc
) 
1430     def Select(self
, select
, dc 
= None): 
1431         """Select or deselect the given shape, drawing or erasing control points 
1432         (handles) as necessary. 
1434         self
._selected 
= select
 
1436             self
.MakeControlPoints() 
1437             # Children of divisions are contained objects, 
1439             if not isinstance(self
, DivisionShape
): 
1440                 for child 
in self
._children
: 
1441                     child
.MakeMandatoryControlPoints() 
1443                 self
.GetEventHandler().OnDrawControlPoints(dc
) 
1445             self
.DeleteControlPoints(dc
) 
1446             if not isinstance(self
, DivisionShape
): 
1447                 for child 
in self
._children
: 
1448                     child
.DeleteControlPoints(dc
) 
1451         """TRUE if the shape is currently selected.""" 
1452         return self
._selected
 
1454     def AncestorSelected(self
): 
1455         """TRUE if the shape's ancestor is currently selected.""" 
1458         if not self
.GetParent(): 
1460         return self
.GetParent().AncestorSelected() 
1462     def GetNumberOfAttachments(self
): 
1463         """Get the number of attachment points for this shape.""" 
1464         # Should return the MAXIMUM attachment point id here, 
1465         # so higher-level functions can iterate through all attachments, 
1466         # even if they're not contiguous. 
1468         if len(self
._attachmentPoints
) == 0: 
1472             for point 
in self
._attachmentPoints
: 
1473                 if point
._id 
> maxN
: 
1477     def AttachmentIsValid(self
, attachment
): 
1478         """TRUE if attachment is a valid attachment point.""" 
1479         if len(self
._attachmentPoints
) == 0: 
1480             return attachment 
in range(4) 
1482         for point 
in self
._attachmentPoints
: 
1483             if point
._id 
== attachment
: 
1487     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1488         """Get the position at which the given attachment point should be drawn. 
1490         If attachment isn't found among the attachment points of the shape, 
1493         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE
: 
1494             return self
._xpos
, self
._ypos
 
1495         elif self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1496             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, nth
) 
1498         elif self
._attachmentMode 
== ATTACHMENT_MODE_EDGE
: 
1499             if len(self
._attachmentPoints
): 
1500                 for point 
in self
._attachmentPoints
: 
1501                     if point
._id 
== attachment
: 
1502                         return self
._xpos 
+ point
._x
, self
._ypos 
+ point
._y
 
1505                 # Assume is rectangular 
1506                 w
, h 
= self
.GetBoundingBoxMax() 
1507                 top 
= self
._ypos 
+ h 
/ 2.0 
1508                 bottom 
= self
._ypos 
- h 
/ 2.0 
1509                 left 
= self
._xpos 
- w 
/ 2.0 
1510                 right 
= self
._xpos 
+ w 
/ 2.0 
1513                 line 
and line
.IsEnd(self
) 
1515                 physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1518                 if physicalAttachment 
== 0: 
1519                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (right
, bottom
), nth
, no_arcs
, line
) 
1520                 elif physicalAttachment 
== 1: 
1521                     pt 
= self
.CalcSimpleAttachment((right
, bottom
), (right
, top
), nth
, no_arcs
, line
) 
1522                 elif physicalAttachment 
== 2: 
1523                     pt 
= self
.CalcSimpleAttachment((left
, top
), (right
, top
), nth
, no_arcs
, line
) 
1524                 elif physicalAttachment 
== 3: 
1525                     pt 
= self
.CalcSimpleAttachment((left
, bottom
), (left
, top
), nth
, no_arcs
, line
) 
1531     def GetBoundingBoxMax(self
): 
1532         """Get the maximum bounding box for the shape, taking into account 
1533         external features such as shadows. 
1535         ww
, hh 
= self
.GetBoundingBoxMin() 
1536         if self
._shadowMode 
!= SHADOW_NONE
: 
1537             ww 
+= self
._shadowOffsetX
 
1538             hh 
+= self
._shadowOffsetY
 
1541     def GetBoundingBoxMin(self
): 
1542         """Get the minimum bounding box for the shape, that defines the area 
1543         available for drawing the contents (such as text). 
1549     def HasDescendant(self
, image
): 
1550         """TRUE if image is a descendant of this composite.""" 
1553         for child 
in self
._children
: 
1554             if child
.HasDescendant(image
): 
1558     # Assuming the attachment lies along a vertical or horizontal line, 
1559     # calculate the position on that point. 
1560     def CalcSimpleAttachment(self
, pt1
, pt2
, nth
, noArcs
, line
): 
1561         """Assuming the attachment lies along a vertical or horizontal line, 
1562         calculate the position on that point. 
1567             The first point of the line repesenting the edge of the shape. 
1570             The second point of the line representing the edge of the shape. 
1573             The position on the edge (for example there may be 6 lines at 
1574             this attachment point, and this may be the 2nd line. 
1577             The number of lines at this edge. 
1584         This function expects the line to be either vertical or horizontal, 
1585         and determines which. 
1587         isEnd 
= line 
and line
.IsEnd(self
) 
1589         # Are we horizontal or vertical? 
1590         isHorizontal 
= RoughlyEqual(pt1
[1], pt2
[1]) 
1600             if self
._spaceAttachments
: 
1601                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1602                     # Align line according to the next handle along 
1603                     point 
= line
.GetNextControlPoint(self
) 
1604                     if point
[0] < firstPoint
[0]: 
1606                     elif point
[0] > secondPoint
[0]: 
1611                     x 
= firstPoint
[0] + (nth 
+ 1) * (secondPoint
[0] - firstPoint
[0]) / (noArcs 
+ 1.0) 
1613                 x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 # Midpoint 
1616             assert RoughlyEqual(pt1
[0], pt2
[0]) 
1625             if self
._spaceAttachments
: 
1626                 if line 
and line
.GetAlignmentType(isEnd
) == LINE_ALIGNMENT_TO_NEXT_HANDLE
: 
1627                     # Align line according to the next handle along 
1628                     point 
= line
.GetNextControlPoint(self
) 
1629                     if point
[1] < firstPoint
[1]: 
1631                     elif point
[1] > secondPoint
[1]: 
1636                     y 
= firstPoint
[1] + (nth 
+ 1) * (secondPoint
[1] - firstPoint
[1]) / (noArcs 
+ 1.0) 
1638                 y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 # Midpoint 
1643     # Return the zero-based position in m_lines of line 
1644     def GetLinePosition(self
, line
): 
1645         """Get the zero-based position of line in the list of lines 
1649             return self
._lines
.index(line
) 
1657     # shoulder1 ->---------<- shoulder2 
1659     #                      <- branching attachment point N-1 
1661     def GetBranchingAttachmentInfo(self
, attachment
): 
1662         """Get information about where branching connections go. 
1664         Returns FALSE if there are no lines at this attachment. 
1666         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1668         # Number of lines at this attachment 
1669         lineCount 
= self
.GetAttachmentLineCount(attachment
) 
1674         totalBranchLength 
= self
._branchSpacing 
* (lineCount 
- 1) 
1675         root 
= self
.GetBranchingAttachmentRoot(attachment
) 
1677         neck 
= wx
.RealPoint() 
1678         shoulder1 
= wx
.RealPoint() 
1679         shoulder2 
= wx
.RealPoint() 
1681         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1682         if physicalAttachment 
== 0: 
1683             neck
[0] = self
.GetX() 
1684             neck
[1] = root
[1] - self
._branchNeckLength
 
1686             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1687             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1689             shoulder1
[1] = neck
[1] 
1690             shoulder2
[1] = neck
[1] 
1691         elif physicalAttachment 
== 1: 
1692             neck
[0] = root
[0] + self
._branchNeckLength
 
1695             shoulder1
[0] = neck
[0] 
1696             shoulder2
[0] = neck
[0] 
1698             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1699             shoulder1
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1700         elif physicalAttachment 
== 2: 
1701             neck
[0] = self
.GetX() 
1702             neck
[1] = root
[1] + self
._branchNeckLength
 
1704             shoulder1
[0] = root
[0] - totalBranchLength 
/ 2.0 
1705             shoulder2
[0] = root
[0] + totalBranchLength 
/ 2.0 
1707             shoulder1
[1] = neck
[1] 
1708             shoulder2
[1] = neck
[1] 
1709         elif physicalAttachment 
== 3: 
1710             neck
[0] = root
[0] - self
._branchNeckLength
 
1713             shoulder1
[0] = neck
[0] 
1714             shoulder2
[0] = neck
[0] 
1716             shoulder1
[1] = neck
[1] - totalBranchLength 
/ 2.0 
1717             shoulder2
[1] = neck
[1] + totalBranchLength 
/ 2.0 
1719             raise "Unrecognised attachment point in GetBranchingAttachmentInfo" 
1720         return root
, neck
, shoulder1
, shoulder2
 
1722     def GetBranchingAttachmentPoint(self
, attachment
, n
): 
1723         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1725         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1727         stemPt 
= wx
.RealPoint() 
1729         if physicalAttachment 
== 0: 
1730             pt
[1] = neck
[1] - self
._branchStemLength
 
1731             pt
[0] = shoulder1
[0] + n 
* self
._branchSpacing
 
1735         elif physicalAttachment 
== 2: 
1736             pt
[1] = neck
[1] + self
._branchStemLength
 
1737             pt
[0] = shoulder1
[0] + n 
* self
._branchStemLength
 
1741         elif physicalAttachment 
== 1: 
1742             pt
[0] = neck
[0] + self
._branchStemLength
 
1743             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1747         elif physicalAttachment 
== 3: 
1748             pt
[0] = neck
[0] - self
._branchStemLength
 
1749             pt
[1] = shoulder1
[1] + n 
* self
._branchSpacing
 
1754             raise "Unrecognised attachment point in GetBranchingAttachmentPoint" 
1758     def GetAttachmentLineCount(self
, attachment
): 
1759         """Get the number of lines at this attachment position.""" 
1761         for lineShape 
in self
._lines
: 
1762             if lineShape
.GetFrom() == self 
and lineShape
.GetAttachmentFrom() == attachment
: 
1764             elif lineShape
.GetTo() == self 
and lineShape
.GetAttachmentTo() == attachment
: 
1768     def GetBranchingAttachmentRoot(self
, attachment
): 
1769         """Get the root point at the given attachment.""" 
1770         physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
1772         root 
= wx
.RealPoint() 
1774         width
, height 
= self
.GetBoundingBoxMax() 
1776         # Assume that we have attachment points 0 to 3: top, right, bottom, left 
1777         if physicalAttachment 
== 0: 
1778             root
[0] = self
.GetX() 
1779             root
[1] = self
.GetY() - height 
/ 2.0 
1780         elif physicalAttachment 
== 1: 
1781             root
[0] = self
.GetX() + width 
/ 2.0 
1782             root
[1] = self
.GetY() 
1783         elif physicalAttachment 
== 2: 
1784             root
[0] = self
.GetX() 
1785             root
[1] = self
.GetY() + height 
/ 2.0 
1786         elif physicalAttachment 
== 3: 
1787             root
[0] = self
.GetX() - width 
/ 2.0 
1788             root
[1] = self
.GetY() 
1790             raise "Unrecognised attachment point in GetBranchingAttachmentRoot" 
1794     # Draw or erase the branches (not the actual arcs though) 
1795     def OnDrawBranchesAttachment(self
, dc
, attachment
, erase 
= False): 
1796         count 
= self
.GetAttachmentLineCount(attachment
) 
1800         root
, neck
, shoulder1
, shoulder2 
= self
.GetBranchingAttachmentInfo(attachment
) 
1803             dc
.SetPen(wx
.WHITE_PEN
) 
1804             dc
.SetBrush(wx
.WHITE_BRUSH
) 
1806             dc
.SetPen(wx
.BLACK_PEN
) 
1807             dc
.SetBrush(wx
.BLACK_BRUSH
) 
1810         dc
.DrawLine(root
[0], root
[1], neck
[0], neck
[1]) 
1813             # Draw shoulder-to-shoulder line 
1814             dc
.DrawLine(shoulder1
[0], shoulder1
[1], shoulder2
[0], shoulder2
[1]) 
1815         # Draw all the little branches 
1816         for i 
in range(count
): 
1817             pt
, stemPt 
= self
.GetBranchingAttachmentPoint(attachment
, i
) 
1818             dc
.DrawLine(stemPt
[0], stemPt
[1], pt
[0], pt
[1]) 
1820             if self
.GetBranchStyle() & BRANCHING_ATTACHMENT_BLOB 
and count 
> 1: 
1822                 dc
.DrawEllipse(stemPt
[0] - blobSize 
/ 2.0, stemPt
[1] - blobSize 
/ 2.0, blobSize
, blobSize
) 
1824     def OnDrawBranches(self
, dc
, erase 
= False): 
1825         if self
._attachmentMode 
!= ATTACHMENT_MODE_BRANCHING
: 
1827         for i 
in range(self
.GetNumberOfAttachments()): 
1828             self
.OnDrawBranchesAttachment(dc
, i
, erase
) 
1830     def GetAttachmentPositionEdge(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
1831         """ Only get the attachment position at the _edge_ of the shape, 
1832         ignoring branching mode. This is used e.g. to indicate the edge of 
1833         interest, not the point on the attachment branch. 
1835         oldMode 
= self
._attachmentMode
 
1837         # Calculate as if to edge, not branch 
1838         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
1839             self
._attachmentMode 
= ATTACHMENT_MODE_EDGE
 
1840         res 
= self
.GetAttachmentPosition(attachment
, nth
, no_arcs
, line
) 
1841         self
._attachmentMode 
= oldMode
 
1845     def PhysicalToLogicalAttachment(self
, physicalAttachment
): 
1846         """ Rotate the standard attachment point from physical 
1847         (0 is always North) to logical (0 -> 1 if rotated by 90 degrees) 
1849         if RoughlyEqual(self
.GetRotation(), 0): 
1850             i 
= physicalAttachment
 
1851         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1852             i 
= physicalAttachment 
- 1 
1853         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1854             i 
= physicalAttachment 
- 2 
1855         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1856             i 
= physicalAttachment 
- 3 
1858             # Can't handle -- assume the same 
1859             return physicalAttachment
 
1866     def LogicalToPhysicalAttachment(self
, logicalAttachment
): 
1867         """Rotate the standard attachment point from logical 
1868         to physical (0 is always North). 
1870         if RoughlyEqual(self
.GetRotation(), 0): 
1871             i 
= logicalAttachment
 
1872         elif RoughlyEqual(self
.GetRotation(), math
.pi 
/ 2.0): 
1873             i 
= logicalAttachment 
+ 1 
1874         elif RoughlyEqual(self
.GetRotation(), math
.pi
): 
1875             i 
= logicalAttachment 
+ 2 
1876         elif RoughlyEqual(self
.GetRotation(), 3 * math
.pi 
/ 2.0): 
1877             i 
= logicalAttachment 
+ 3 
1879             return logicalAttachment
 
1886     def Rotate(self
, x
, y
, theta
): 
1887         """Rotate about the given axis by the given amount in radians.""" 
1888         self
._rotation 
= theta
 
1889         if self
._rotation 
< 0: 
1890             self
._rotation 
+= 2 * math
.pi
 
1891         elif self
._rotation 
> 2 * math
.pi
: 
1892             self
._rotation 
-= 2 * math
.pi
 
1894     def GetBackgroundPen(self
): 
1895         """Return pen of the right colour for the background.""" 
1896         if self
.GetCanvas(): 
1897             return wx
.Pen(self
.GetCanvas().GetBackgroundColour(), 1, wx
.SOLID
) 
1898         return WhiteBackgroundPen
 
1900     def GetBackgroundBrush(self
): 
1901         """Return brush of the right colour for the background.""" 
1902         if self
.GetCanvas(): 
1903             return wx
.Brush(self
.GetCanvas().GetBackgroundColour(), wx
.SOLID
) 
1904         return WhiteBackgroundBrush
 
1907         """Get the x position of the centre of the shape.""" 
1911         """Get the y position of the centre of the shape.""" 
1915         """Set the x position of the shape.""" 
1919         """Set the y position of the shape.""" 
1922     def GetParent(self
): 
1923         """Return the parent of this shape, if it is part of a composite.""" 
1926     def SetParent(self
, p
): 
1929     def GetChildren(self
): 
1930         """Return the list of children for this shape.""" 
1931         return self
._children
 
1933     def GetDrawHandles(self
): 
1934         """Return the list of drawhandles.""" 
1935         return self
._drawHandles
 
1937     def GetEventHandler(self
): 
1938         """Return the event handler for this shape.""" 
1939         return self
._eventHandler
 
1941     def SetEventHandler(self
, handler
): 
1942         """Set the event handler for this shape.""" 
1943         self
._eventHandler 
= handler
 
1945     def Recompute(self
): 
1946         """Recomputes any constraints associated with the shape. 
1948         Normally applicable to CompositeShapes only, but harmless for 
1949         other classes of Shape. 
1953     def IsHighlighted(self
): 
1954         """TRUE if the shape is highlighted. Shape highlighting is unimplemented.""" 
1955         return self
._highlighted
 
1957     def GetSensitivityFilter(self
): 
1958         """Return the sensitivity filter, a bitlist of values. 
1960         See Shape.SetSensitivityFilter. 
1962         return self
._sensitivity
 
1964     def SetFixedSize(self
, x
, y
): 
1965         """Set the shape to be fixed size.""" 
1966         self
._fixedWidth 
= x
 
1967         self
._fixedHeight 
= y
 
1969     def GetFixedSize(self
): 
1970         """Return flags indicating whether the shape is of fixed size in 
1973         return self
._fixedWidth
, self
._fixedHeight
 
1975     def GetFixedWidth(self
): 
1976         """TRUE if the shape cannot be resized in the horizontal plane.""" 
1977         return self
._fixedWidth
 
1979     def GetFixedHeight(self
): 
1980         """TRUE if the shape cannot be resized in the vertical plane.""" 
1981         return self
._fixedHeight
 
1983     def SetSpaceAttachments(self
, sp
): 
1984         """Indicate whether lines should be spaced out evenly at the point 
1985         they touch the node (sp = True), or whether they should join at a single 
1988         self
._spaceAttachments 
= sp
 
1990     def GetSpaceAttachments(self
): 
1991         """Return whether lines should be spaced out evenly at the point they 
1992         touch the node (True), or whether they should join at a single point 
1995         return self
._spaceAttachments
 
1997     def SetCentreResize(self
, cr
): 
1998         """Specify whether the shape is to be resized from the centre (the 
1999         centre stands still) or from the corner or side being dragged (the 
2000         other corner or side stands still). 
2002         self
._centreResize 
= cr
 
2004     def GetCentreResize(self
): 
2005         """TRUE if the shape is to be resized from the centre (the centre stands 
2006         still), or FALSE if from the corner or side being dragged (the other 
2007         corner or side stands still) 
2009         return self
._centreResize
 
2011     def SetMaintainAspectRatio(self
, ar
): 
2012         """Set whether a shape that resizes should not change the aspect ratio 
2013         (width and height should be in the original proportion). 
2015         self
._maintainAspectRatio 
= ar
 
2017     def GetMaintainAspectRatio(self
): 
2018         """TRUE if shape keeps aspect ratio during resize.""" 
2019         return self
._maintainAspectRatio
 
2022         """Return the list of lines connected to this shape.""" 
2025     def SetDisableLabel(self
, flag
): 
2026         """Set flag to TRUE to stop the default region being shown.""" 
2027         self
._disableLabel 
= flag
 
2029     def GetDisableLabel(self
): 
2030         """TRUE if the default region will not be shown, FALSE otherwise.""" 
2031         return self
._disableLabel
 
2033     def SetAttachmentMode(self
, mode
): 
2034         """Set the attachment mode. 
2036         If TRUE, attachment points will be significant when drawing lines to 
2037         and from this shape. 
2038         If FALSE, lines will be drawn as if to the centre of the shape. 
2040         self
._attachmentMode 
= mode
 
2042     def GetAttachmentMode(self
): 
2043         """Return the attachment mode. 
2045         See Shape.SetAttachmentMode. 
2047         return self
._attachmentMode
 
2050         """Set the integer identifier for this shape.""" 
2054         """Return the integer identifier for this shape.""" 
2058         """TRUE if the shape is in a visible state, FALSE otherwise. 
2060         Note that this has nothing to do with whether the window is hidden 
2061         or the shape has scrolled off the canvas; it refers to the internal 
2064         return self
._visible
 
2067         """Return the pen used for drawing the shape's outline.""" 
2071         """Return the brush used for filling the shape.""" 
2074     def GetNumberOfTextRegions(self
): 
2075         """Return the number of text regions for this shape.""" 
2076         return len(self
._regions
) 
2078     def GetRegions(self
): 
2079         """Return the list of ShapeRegions.""" 
2080         return self
._regions
 
2082     # Control points ('handles') redirect control to the actual shape, to 
2083     # make it easier to override sizing behaviour. 
2084     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2085         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2087         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2088         self
.GetCanvas().PrepareDC(dc
) 
2090         dc
.SetLogicalFunction(OGLRBLF
) 
2092         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2093         dc
.SetPen(dottedPen
) 
2094         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2096         if self
.GetCentreResize(): 
2097             # Maintain the same centre point 
2098             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2099             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2101             # Constrain sizing according to what control point you're dragging 
2102             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2103                 if self
.GetMaintainAspectRatio(): 
2104                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2106                     new_height 
= bound_y
 
2107             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2108                 if self
.GetMaintainAspectRatio(): 
2109                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2112             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2113                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2115             if self
.GetFixedWidth(): 
2118             if self
.GetFixedHeight(): 
2119                 new_height 
= bound_y
 
2121             pt
._controlPointDragEndWidth 
= new_width
 
2122             pt
._controlPointDragEndHeight 
= new_height
 
2124             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2126             # Don't maintain the same centre point 
2127             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2128             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2129             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2130             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2131             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2132                 newY1 
= pt
._controlPointDragStartY
 
2133                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2134             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2135                 newX1 
= pt
._controlPointDragStartX
 
2136                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2137             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2138                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2139                 if self
.GetY() > pt
._controlPointDragStartY
: 
2140                     newY2 
= newY1 
+ newH
 
2142                     newY1 
= newY2 
- newH
 
2144             newWidth 
= float(newX2 
- newX1
) 
2145             newHeight 
= float(newY2 
- newY1
) 
2147             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2148                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2150             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2151                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2153             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2154             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2155             if self
.GetFixedWidth(): 
2158             if self
.GetFixedHeight(): 
2161             pt
._controlPointDragEndWidth 
= newWidth
 
2162             pt
._controlPointDragEndHeight 
= newHeight
 
2163             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2165     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2166         self
._canvas
.CaptureMouse() 
2168         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2169         self
.GetCanvas().PrepareDC(dc
) 
2171         dc
.SetLogicalFunction(OGLRBLF
) 
2173         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2174         self
.GetEventHandler().OnEndSize(bound_x
, bound_y
) 
2176         # Choose the 'opposite corner' of the object as the stationary 
2177         # point in case this is non-centring resizing. 
2178         if pt
.GetX() < self
.GetX(): 
2179             pt
._controlPointDragStartX 
= self
.GetX() + bound_x 
/ 2.0 
2181             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2183         if pt
.GetY() < self
.GetY(): 
2184             pt
._controlPointDragStartY 
= self
.GetY() + bound_y 
/ 2.0 
2186             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2188         if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2189             pt
._controlPointDragStartY 
= self
.GetY() - bound_y 
/ 2.0 
2190         elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2191             pt
._controlPointDragStartX 
= self
.GetX() - bound_x 
/ 2.0 
2193         # We may require the old width and height 
2194         pt
._controlPointDragStartWidth 
= bound_x
 
2195         pt
._controlPointDragStartHeight 
= bound_y
 
2197         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2198         dc
.SetPen(dottedPen
) 
2199         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2201         if self
.GetCentreResize(): 
2202             new_width 
= 2.0 * abs(x 
- self
.GetX()) 
2203             new_height 
= 2.0 * abs(y 
- self
.GetY()) 
2205             # Constrain sizing according to what control point you're dragging 
2206             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2207                 if self
.GetMaintainAspectRatio(): 
2208                     new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2210                     new_height 
= bound_y
 
2211             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2212                 if self
.GetMaintainAspectRatio(): 
2213                     new_width 
= bound_x 
* (new_height 
/ bound_y
) 
2216             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT
): 
2217                 new_height 
= bound_y 
* (new_width 
/ bound_x
) 
2219             if self
.GetFixedWidth(): 
2222             if self
.GetFixedHeight(): 
2223                 new_height 
= bound_y
 
2225             pt
._controlPointDragEndWidth 
= new_width
 
2226             pt
._controlPointDragEndHeight 
= new_height
 
2227             self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), new_width
, new_height
) 
2229             # Don't maintain the same centre point 
2230             newX1 
= min(pt
._controlPointDragStartX
, x
) 
2231             newY1 
= min(pt
._controlPointDragStartY
, y
) 
2232             newX2 
= max(pt
._controlPointDragStartX
, x
) 
2233             newY2 
= max(pt
._controlPointDragStartY
, y
) 
2234             if pt
._type 
== CONTROL_POINT_HORIZONTAL
: 
2235                 newY1 
= pt
._controlPointDragStartY
 
2236                 newY2 
= newY1 
+ pt
._controlPointDragStartHeight
 
2237             elif pt
._type 
== CONTROL_POINT_VERTICAL
: 
2238                 newX1 
= pt
._controlPointDragStartX
 
2239                 newX2 
= newX1 
+ pt
._controlPointDragStartWidth
 
2240             elif pt
._type 
== CONTROL_POINT_DIAGONAL 
and (keys 
& KEY_SHIFT 
or self
.GetMaintainAspectRatio()): 
2241                 newH 
= (newX2 
- newX1
) * (float(pt
._controlPointDragStartHeight
) / pt
._controlPointDragStartWidth
) 
2242                 if pt
.GetY() > pt
._controlPointDragStartY
: 
2243                     newY2 
= newY1 
+ newH
 
2245                     newY1 
= newY2 
- newH
 
2247             newWidth 
= float(newX2 
- newX1
) 
2248             newHeight 
= float(newY2 
- newY1
) 
2250             if pt
._type 
== CONTROL_POINT_VERTICAL 
and self
.GetMaintainAspectRatio(): 
2251                 newWidth 
= bound_x 
* (newHeight 
/ bound_y
) 
2253             if pt
._type 
== CONTROL_POINT_HORIZONTAL 
and self
.GetMaintainAspectRatio(): 
2254                 newHeight 
= bound_y 
* (newWidth 
/ bound_x
) 
2256             pt
._controlPointDragPosX 
= newX1 
+ newWidth 
/ 2.0 
2257             pt
._controlPointDragPosY 
= newY1 
+ newHeight 
/ 2.0 
2258             if self
.GetFixedWidth(): 
2261             if self
.GetFixedHeight(): 
2264             pt
._controlPointDragEndWidth 
= newWidth
 
2265             pt
._controlPointDragEndHeight 
= newHeight
 
2266             self
.GetEventHandler().OnDrawOutline(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
, newWidth
, newHeight
) 
2268     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2269         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2270         self
.GetCanvas().PrepareDC(dc
) 
2272         if self
._canvas
.HasCapture(): 
2273             self
._canvas
.ReleaseMouse() 
2274         dc
.SetLogicalFunction(wx
.COPY
) 
2276         self
.ResetControlPoints() 
2280         self
.SetSize(pt
._controlPointDragEndWidth
, pt
._controlPointDragEndHeight
) 
2282         # The next operation could destroy this control point (it does for 
2283         # label objects, via formatting the text), so save all values we're 
2284         # going to use, or we'll be accessing garbage. 
2288         if self
.GetCentreResize(): 
2289             self
.Move(dc
, self
.GetX(), self
.GetY()) 
2291             self
.Move(dc
, pt
._controlPointDragPosX
, pt
._controlPointDragPosY
) 
2293         # Recursively redraw links if we have a composite 
2294         if len(self
.GetChildren()): 
2295             self
.DrawLinks(dc
, -1, True) 
2297         width
, height 
= self
.GetBoundingBoxMax() 
2298         self
.GetEventHandler().OnEndSize(width
, height
) 
2300         if not self
._canvas
.GetQuickEditMode() and pt
._eraseObject
: 
2301             self
._canvas
.Redraw(dc
) 
2305 class RectangleShape(Shape
): 
2307     The wxRectangleShape has rounded or square corners. 
2312     def __init__(self
, w 
= 0.0, h 
= 0.0): 
2313         Shape
.__init
__(self
) 
2316         self
._cornerRadius 
= 0.0 
2317         self
.SetDefaultRegionSize() 
2319     def OnDraw(self
, dc
): 
2320         x1 
= self
._xpos 
- self
._width 
/ 2.0 
2321         y1 
= self
._ypos 
- self
._height 
/ 2.0 
2323         if self
._shadowMode 
!= SHADOW_NONE
: 
2324             if self
._shadowBrush
: 
2325                 dc
.SetBrush(self
._shadowBrush
) 
2326             dc
.SetPen(TransparentPen
) 
2328             if self
._cornerRadius
: 
2329                 dc
.DrawRoundedRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
, self
._cornerRadius
) 
2331                 dc
.DrawRectangle(x1 
+ self
._shadowOffsetX
, y1 
+ self
._shadowOffsetY
, self
._width
, self
._height
) 
2334             if self
._pen
.GetWidth() == 0: 
2335                 dc
.SetPen(TransparentPen
) 
2337                 dc
.SetPen(self
._pen
) 
2339             dc
.SetBrush(self
._brush
) 
2341         if self
._cornerRadius
: 
2342             dc
.DrawRoundedRectangle(x1
, y1
, self
._width
, self
._height
, self
._cornerRadius
) 
2344             dc
.DrawRectangle(x1
, y1
, self
._width
, self
._height
) 
2346     def GetBoundingBoxMin(self
): 
2347         return self
._width
, self
._height
 
2349     def SetSize(self
, x
, y
, recursive 
= False): 
2350         self
.SetAttachmentSize(x
, y
) 
2351         self
._width 
= max(x
, 1) 
2352         self
._height 
= max(y
, 1) 
2353         self
.SetDefaultRegionSize() 
2355     def GetCornerRadius(self
): 
2356         """Get the radius of the rectangle's rounded corners.""" 
2357         return self
._cornerRadius
 
2359     def SetCornerRadius(self
, rad
): 
2360         """Set the radius of the rectangle's rounded corners. 
2362         If the radius is zero, a non-rounded rectangle will be drawn. 
2363         If the radius is negative, the value is the proportion of the smaller 
2364         dimension of the rectangle. 
2366         self
._cornerRadius 
= rad
 
2368     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2369     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2370         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2371         return FindEndForBox(bound_x
, bound_y
, self
._xpos
, self
._ypos
, x2
, y2
) 
2376     def GetHeight(self
): 
2379     def SetWidth(self
, w
): 
2382     def SetHeight(self
, h
): 
2387 class PolygonShape(Shape
): 
2388     """A PolygonShape's shape is defined by a number of points passed to 
2389     the object's constructor. It can be used to create new shapes such as 
2390     diamonds and triangles. 
2393         Shape
.__init
__(self
) 
2396         self
._originalPoints 
= None 
2398     def Create(self
, the_points 
= None): 
2399         """Takes a list of wx.RealPoints or tuples; each point is an offset 
2405             self
._originalPoints 
= [] 
2408             self
._originalPoints 
= the_points
 
2410             # Duplicate the list of points 
2412             for point 
in the_points
: 
2413                 new_point 
= wx
.Point(point
[0], point
[1]) 
2414                 self
._points
.append(new_point
) 
2415             self
.CalculateBoundingBox() 
2416             self
._originalWidth 
= self
._boundWidth
 
2417             self
._originalHeight 
= self
._boundHeight
 
2418             self
.SetDefaultRegionSize() 
2420     def ClearPoints(self
): 
2422         self
._originalPoints 
= [] 
2424     # Width and height. Centre of object is centre of box 
2425     def GetBoundingBoxMin(self
): 
2426         return self
._boundWidth
, self
._boundHeight
 
2428     def GetPoints(self
): 
2429         """Return the internal list of polygon vertices.""" 
2432     def GetOriginalPoints(self
): 
2433         return self
._originalPoints
 
2435     def GetOriginalWidth(self
): 
2436         return self
._originalWidth
 
2438     def GetOriginalHeight(self
): 
2439         return self
._originalHeight
 
2441     def SetOriginalWidth(self
, w
): 
2442         self
._originalWidth 
= w
 
2444     def SetOriginalHeight(self
, h
): 
2445         self
._originalHeight 
= h
 
2447     def CalculateBoundingBox(self
): 
2448         # Calculate bounding box at construction (and presumably resize) time 
2454         for point 
in self
._points
: 
2457             if point
[0] > right
: 
2462             if point
[1] > bottom
: 
2465         self
._boundWidth 
= right 
- left
 
2466         self
._boundHeight 
= bottom 
- top
 
2468     def CalculatePolygonCentre(self
): 
2469         """Recalculates the centre of the polygon, and 
2470         readjusts the point offsets accordingly. 
2471         Necessary since the centre of the polygon 
2472         is expected to be the real centre of the bounding 
2480         for point 
in self
._points
: 
2483             if point
[0] > right
: 
2488             if point
[1] > bottom
: 
2491         bwidth 
= right 
- left
 
2492         bheight 
= bottom 
- top
 
2494         newCentreX 
= left 
+ bwidth 
/ 2.0 
2495         newCentreY 
= top 
+ bheight 
/ 2.0 
2497         for i 
in range(len(self
._points
)): 
2498             self
._points
[i
] = self
._points
[i
][0] - newCentreX
, self
._points
[i
][1] - newCentreY
 
2499         self
._xpos 
+= newCentreX
 
2500         self
._ypos 
+= newCentreY
 
2502     def HitTest(self
, x
, y
): 
2503         # Imagine four lines radiating from this point. If all of these lines 
2504         # hit the polygon, we're inside it, otherwise we're not. Obviously 
2505         # we'd need more radiating lines to be sure of correct results for 
2506         # very strange (concave) shapes. 
2507         endPointsX 
= [x
, x 
+ 1000, x
, x 
- 1000] 
2508         endPointsY 
= [y 
- 1000, y
, y 
+ 1000, y
] 
2513         for point 
in self
._points
: 
2514             xpoints
.append(point
[0] + self
._xpos
) 
2515             ypoints
.append(point
[1] + self
._ypos
) 
2517         # We assume it's inside the polygon UNLESS one or more 
2518         # lines don't hit the outline. 
2522             if not PolylineHitTest(xpoints
, ypoints
, x
, y
, endPointsX
[i
], endPointsY
[i
]): 
2528         nearest_attachment 
= 0 
2530         # If a hit, check the attachment points within the object 
2533         for i 
in range(self
.GetNumberOfAttachments()): 
2534             e 
= self
.GetAttachmentPositionEdge(i
) 
2537                 l 
= math
.sqrt((xp 
- x
) * (xp 
- x
) + (yp 
- y
) * (yp 
- y
)) 
2540                     nearest_attachment 
= i
 
2542         return nearest_attachment
, nearest
 
2544     # Really need to be able to reset the shape! Otherwise, if the 
2545     # points ever go to zero, we've lost it, and can't resize. 
2546     def SetSize(self
, new_width
, new_height
, recursive 
= True): 
2547         self
.SetAttachmentSize(new_width
, new_height
) 
2549         # Multiply all points by proportion of new size to old size 
2550         x_proportion 
= abs(float(new_width
) / self
._originalWidth
) 
2551         y_proportion 
= abs(float(new_height
) / self
._originalHeight
) 
2553         for i 
in range(max(len(self
._points
), len(self
._originalPoints
))): 
2554             self
._points
[i
] = wx
.Point(self
._originalPoints
[i
][0] * x_proportion
, self
._originalPoints
[i
][1] * y_proportion
) 
2556         self
._boundWidth 
= abs(new_width
) 
2557         self
._boundHeight 
= abs(new_height
) 
2558         self
.SetDefaultRegionSize() 
2560     # Make the original points the same as the working points 
2561     def UpdateOriginalPoints(self
): 
2562         """If we've changed the shape, must make the original points match the 
2563         working points with this function. 
2565         self
._originalPoints 
= [] 
2567         for point 
in self
._points
: 
2568             original_point 
= wx
.RealPoint(point
[0], point
[1]) 
2569             self
._originalPoints
.append(original_point
) 
2571         self
.CalculateBoundingBox() 
2572         self
._originalWidth 
= self
._boundWidth
 
2573         self
._originalHeight 
= self
._boundHeight
 
2575     def AddPolygonPoint(self
, pos
): 
2576         """Add a control point after the given point.""" 
2578             firstPoint 
= self
._points
[pos
] 
2580             firstPoint 
= self
._points
[0] 
2583             secondPoint 
= self
._points
[pos 
+ 1] 
2585             secondPoint 
= self
._points
[0] 
2587         x 
= (secondPoint
[0] - firstPoint
[0]) / 2.0 + firstPoint
[0] 
2588         y 
= (secondPoint
[1] - firstPoint
[1]) / 2.0 + firstPoint
[1] 
2589         point 
= wx
.RealPoint(x
, y
) 
2591         if pos 
>= len(self
._points
) - 1: 
2592             self
._points
.append(point
) 
2594             self
._points
.insert(pos 
+ 1, point
) 
2596         self
.UpdateOriginalPoints() 
2599             self
.DeleteControlPoints() 
2600             self
.MakeControlPoints() 
2602     def DeletePolygonPoint(self
, pos
): 
2603         """Delete the given control point.""" 
2604         if pos 
< len(self
._points
): 
2605             del self
._points
[pos
] 
2606             self
.UpdateOriginalPoints() 
2608                 self
.DeleteControlPoints() 
2609                 self
.MakeControlPoints() 
2611     # Assume (x1, y1) is centre of box (most generally, line end at box) 
2612     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2613         # First check for situation where the line is vertical, 
2614         # and we would want to connect to a point on that vertical -- 
2615         # oglFindEndForPolyline can't cope with this (the arrow 
2616         # gets drawn to the wrong place). 
2617         if self
._attachmentMode 
== ATTACHMENT_MODE_NONE 
and x1 
== x2
: 
2618             # Look for the point we'd be connecting to. This is 
2620             for point 
in self
._points
: 
2622                     if y2 
> y1 
and point
[1] > 0: 
2623                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2624                     elif y2 
< y1 
and point
[1] < 0: 
2625                         return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2629         for point 
in self
._points
: 
2630             xpoints
.append(point
[0] + self
._xpos
) 
2631             ypoints
.append(point
[1] + self
._ypos
) 
2633         return FindEndForPolyline(xpoints
, ypoints
, x1
, y1
, x2
, y2
) 
2635     def OnDraw(self
, dc
): 
2636         if self
._shadowMode 
!= SHADOW_NONE
: 
2637             if self
._shadowBrush
: 
2638                 dc
.SetBrush(self
._shadowBrush
) 
2639             dc
.SetPen(TransparentPen
) 
2641             dc
.DrawPolygon(self
._points
, self
._xpos 
+ self
._shadowOffsetX
, self
._ypos
, self
._shadowOffsetY
) 
2644             if self
._pen
.GetWidth() == 0: 
2645                 dc
.SetPen(TransparentPen
) 
2647                 dc
.SetPen(self
._pen
) 
2649             dc
.SetBrush(self
._brush
) 
2650         dc
.DrawPolygon(self
._points
, self
._xpos
, self
._ypos
) 
2652     def OnDrawOutline(self
, dc
, x
, y
, w
, h
): 
2653         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2654         # Multiply all points by proportion of new size to old size 
2655         x_proportion 
= abs(float(w
) / self
._originalWidth
) 
2656         y_proportion 
= abs(float(h
) / self
._originalHeight
) 
2659         for point 
in self
._originalPoints
: 
2660             intPoints
.append(wx
.Point(x_proportion 
* point
[0], y_proportion 
* point
[1])) 
2661         dc
.DrawPolygon(intPoints
, x
, y
) 
2663     # Make as many control points as there are vertices 
2664     def MakeControlPoints(self
): 
2665         for point 
in self
._points
: 
2666             control 
= PolygonControlPoint(self
._canvas
, self
, CONTROL_POINT_SIZE
, point
, point
[0], point
[1]) 
2667             self
._canvas
.AddShape(control
) 
2668             self
._controlPoints
.append(control
) 
2670     def ResetControlPoints(self
): 
2671         for i 
in range(min(len(self
._points
), len(self
._controlPoints
))): 
2672             point 
= self
._points
[i
] 
2673             self
._controlPoints
[i
]._xoffset 
= point
[0] 
2674             self
._controlPoints
[i
]._yoffset 
= point
[1] 
2675             self
._controlPoints
[i
].polygonVertex 
= point
 
2677     def GetNumberOfAttachments(self
): 
2678         maxN 
= max(len(self
._points
) - 1, 0) 
2679         for point 
in self
._attachmentPoints
: 
2680             if point
._id 
> maxN
: 
2684     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2685         if self
._attachmentMode 
== ATTACHMENT_MODE_EDGE 
and self
._points 
and attachment 
< len(self
._points
): 
2686             point 
= self
._points
[0] 
2687             return point
[0] + self
._xpos
, point
[1] + self
._ypos
 
2688         return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2690     def AttachmentIsValid(self
, attachment
): 
2691         if not self
._points
: 
2694         if attachment 
>= 0 and attachment 
< len(self
._points
): 
2697         for point 
in self
._attachmentPoints
: 
2698             if point
._id 
== attachment
: 
2703     # Rotate about the given axis by the given amount in radians 
2704     def Rotate(self
, x
, y
, theta
): 
2705         actualTheta 
= theta 
- self
._rotation
 
2707         # Rotate attachment points 
2708         sinTheta 
= math
.sin(actualTheta
) 
2709         cosTheta 
= math
.cos(actualTheta
) 
2711         for point 
in self
._attachmentPoints
: 
2715             point
._x 
= x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
 
2716             point
._y 
= x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2718         for i 
in range(len(self
._points
)): 
2719             x1
, y1 
= self
._points
[i
] 
2721             self
._points
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2723         for i 
in range(len(self
._originalPoints
)): 
2724             x1
, y1 
= self
._originalPoints
[i
] 
2726             self
._originalPoints
[i
] = x1 
* cosTheta 
- y1 
* sinTheta 
+ x 
* (1 - cosTheta
) + y 
* sinTheta
, x1 
* sinTheta 
+ y1 
* cosTheta 
+ y 
* (1 - cosTheta
) + x 
* sinTheta
 
2728         # Added by Pierre Hjälm. If we don't do this the outline will be 
2729         # the wrong size. Hopefully it won't have any ill effects. 
2730         self
.UpdateOriginalPoints() 
2732         self
._rotation 
= theta
 
2734         self
.CalculatePolygonCentre() 
2735         self
.CalculateBoundingBox() 
2736         self
.ResetControlPoints() 
2738     # Control points ('handles') redirect control to the actual shape, to 
2739     # make it easier to override sizing behaviour. 
2740     def OnSizingDragLeft(self
, pt
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
2741         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2742         self
.GetCanvas().PrepareDC(dc
) 
2744         dc
.SetLogicalFunction(OGLRBLF
) 
2746         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2747         dc
.SetPen(dottedPen
) 
2748         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2750         # Code for CTRL-drag in C++ version commented out 
2752         pt
.CalculateNewSize(x
, y
) 
2754         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2756     def OnSizingBeginDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2757         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2758         self
.GetCanvas().PrepareDC(dc
) 
2762         dc
.SetLogicalFunction(OGLRBLF
) 
2764         bound_x
, bound_y 
= self
.GetBoundingBoxMin() 
2766         dist 
= math
.sqrt((x 
- self
.GetX()) * (x 
- self
.GetX()) + (y 
- self
.GetY()) * (y 
- self
.GetY())) 
2768         pt
._originalDistance 
= dist
 
2769         pt
._originalSize
[0] = bound_x
 
2770         pt
._originalSize
[1] = bound_y
 
2772         if pt
._originalDistance 
== 0: 
2773             pt
._originalDistance 
= 0.0001 
2775         dottedPen 
= wx
.Pen(wx
.Colour(0, 0, 0), 1, wx
.DOT
) 
2776         dc
.SetPen(dottedPen
) 
2777         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
2779         # Code for CTRL-drag in C++ version commented out 
2781         pt
.CalculateNewSize(x
, y
) 
2783         self
.GetEventHandler().OnDrawOutline(dc
, self
.GetX(), self
.GetY(), pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2785         self
._canvas
.CaptureMouse() 
2787     def OnSizingEndDragLeft(self
, pt
, x
, y
, keys 
= 0, attachment 
= 0): 
2788         dc 
= wx
.ClientDC(self
.GetCanvas()) 
2789         self
.GetCanvas().PrepareDC(dc
) 
2791         if self
._canvas
.HasCapture(): 
2792             self
._canvas
.ReleaseMouse() 
2793         dc
.SetLogicalFunction(wx
.COPY
) 
2795         # If we're changing shape, must reset the original points 
2797             self
.CalculateBoundingBox() 
2798             self
.CalculatePolygonCentre() 
2800             self
.SetSize(pt
.GetNewSize()[0], pt
.GetNewSize()[1]) 
2803         self
.ResetControlPoints() 
2804         self
.Move(dc
, self
.GetX(), self
.GetY()) 
2805         if not self
._canvas
.GetQuickEditMode(): 
2806             self
._canvas
.Redraw(dc
) 
2810 class EllipseShape(Shape
): 
2811     """The EllipseShape behaves similarly to the RectangleShape but is 
2817     def __init__(self
, w
, h
): 
2818         Shape
.__init
__(self
) 
2821         self
.SetDefaultRegionSize() 
2823     def GetBoundingBoxMin(self
): 
2824         return self
._width
, self
._height
 
2826     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2827         bound_x
, bound_y 
= self
.GetBoundingBoxMax() 
2829         return DrawArcToEllipse(self
._xpos
, self
._ypos
, bound_x
, bound_y
, x2
, y2
, x1
, y1
) 
2834     def GetHeight(self
): 
2837     def SetWidth(self
, w
): 
2840     def SetHeight(self
, h
): 
2843     def OnDraw(self
, dc
): 
2844         if self
._shadowMode 
!= SHADOW_NONE
: 
2845             if self
._shadowBrush
: 
2846                 dc
.SetBrush(self
._shadowBrush
) 
2847             dc
.SetPen(TransparentPen
) 
2848             dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0 + self
._shadowOffsetX
, 
2849                            self
._ypos 
- self
.GetHeight() / 2.0 + self
._shadowOffsetY
, 
2850                            self
.GetWidth(), self
.GetHeight()) 
2853             if self
._pen
.GetWidth() == 0: 
2854                 dc
.SetPen(TransparentPen
) 
2856                 dc
.SetPen(self
._pen
) 
2858             dc
.SetBrush(self
._brush
) 
2859         dc
.DrawEllipse(self
._xpos 
- self
.GetWidth() / 2.0, self
._ypos 
- self
.GetHeight() / 2.0, self
.GetWidth(), self
.GetHeight()) 
2861     def SetSize(self
, x
, y
, recursive 
= True): 
2862         self
.SetAttachmentSize(x
, y
) 
2865         self
.SetDefaultRegionSize() 
2867     def GetNumberOfAttachments(self
): 
2868         return Shape
.GetNumberOfAttachments(self
) 
2870     # There are 4 attachment points on an ellipse - 0 = top, 1 = right, 
2871     # 2 = bottom, 3 = left. 
2872     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
2873         if self
._attachmentMode 
== ATTACHMENT_MODE_BRANCHING
: 
2874             return Shape
.GetAttachmentPosition(self
, attachment
, nth
, no_arcs
, line
) 
2876         if self
._attachmentMode 
!= ATTACHMENT_MODE_NONE
: 
2877             top 
= self
._ypos 
+ self
._height 
/ 2.0 
2878             bottom 
= self
._ypos 
- self
._height 
/ 2.0 
2879             left 
= self
._xpos 
- self
._width 
/ 2.0 
2880             right 
= self
._xpos 
+ self
._width 
/ 2.0 
2882             physicalAttachment 
= self
.LogicalToPhysicalAttachment(attachment
) 
2884             if physicalAttachment 
== 0: 
2885                 if self
._spaceAttachments
: 
2886                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2890                 # We now have the point on the bounding box: but get the point 
2891                 # on the ellipse by imagining a vertical line from 
2892                 # (x, self._ypos - self._height - 500) to (x, self._ypos) intersecting 
2895                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
- self
._height 
- 500, x
, self
._ypos
) 
2896             elif physicalAttachment 
== 1: 
2898                 if self
._spaceAttachments
: 
2899                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2902                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
+ self
._width 
+ 500, y
, self
._xpos
, y
) 
2903             elif physicalAttachment 
== 2: 
2904                 if self
._spaceAttachments
: 
2905                     x 
= left 
+ (nth 
+ 1) * self
._width 
/ (no_arcs 
+ 1.0) 
2909                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, x
, self
._ypos 
+ self
._height 
+ 500, x
, self
._ypos
) 
2910             elif physicalAttachment 
== 3: 
2912                 if self
._spaceAttachments
: 
2913                     y 
= bottom 
+ (nth 
+ 1) * self
._height 
/ (no_arcs 
+ 1.0) 
2916                 return DrawArcToEllipse(self
._xpos
, self
._ypos
, self
._width
, self
._height
, self
._xpos 
- self
._width 
- 500, y
, self
._xpos
, y
) 
2918                 return Shape
.GetAttachmentPosition(self
, attachment
, x
, y
, nth
, no_arcs
, line
) 
2920             return self
._xpos
, self
._ypos
 
2924 class CircleShape(EllipseShape
): 
2925     """An EllipseShape whose width and height are the same.""" 
2926     def __init__(self
, diameter
): 
2927         EllipseShape
.__init
__(self
, diameter
, diameter
) 
2928         self
.SetMaintainAspectRatio(True) 
2930     def GetPerimeterPoint(self
, x1
, y1
, x2
, y2
): 
2931         return FindEndForCircle(self
._width 
/ 2.0, self
._xpos
, self
._ypos
, x2
, y2
) 
2935 class TextShape(RectangleShape
): 
2936     """As wxRectangleShape, but only the text is displayed.""" 
2937     def __init__(self
, width
, height
): 
2938         RectangleShape
.__init
__(self
, width
, height
) 
2940     def OnDraw(self
, dc
): 
2945 class ShapeRegion(object): 
2946     """Object region.""" 
2947     def __init__(self
, region 
= None): 
2949             self
._regionText 
= region
._regionText
 
2950             self
._regionName 
= region
._regionName
 
2951             self
._textColour 
= region
._textColour
 
2953             self
._font 
= region
._font
 
2954             self
._minHeight 
= region
._minHeight
 
2955             self
._minWidth 
= region
._minWidth
 
2956             self
._width 
= region
._width
 
2957             self
._height 
= region
._height
 
2961             self
._regionProportionX 
= region
._regionProportionX
 
2962             self
._regionProportionY 
= region
._regionProportionY
 
2963             self
._formatMode 
= region
._formatMode
 
2964             self
._actualColourObject 
= region
._actualColourObject
 
2965             self
._actualPenObject 
= None 
2966             self
._penStyle 
= region
._penStyle
 
2967             self
._penColour 
= region
._penColour
 
2970             for line 
in region
._formattedText
: 
2971                 new_line 
= ShapeTextLine(line
.GetX(), line
.GetY(), line
.GetText()) 
2972                 self
._formattedText
.append(new_line
) 
2974             self
._regionText 
= "" 
2975             self
._font 
= NormalFont
 
2976             self
._minHeight 
= 5.0 
2977             self
._minWidth 
= 5.0 
2983             self
._regionProportionX 
= -1.0 
2984             self
._regionProportionY 
= -1.0 
2985             self
._formatMode 
= FORMAT_CENTRE_HORIZ | FORMAT_CENTRE_VERT
 
2986             self
._regionName 
= "" 
2987             self
._textColour 
= "BLACK" 
2988             self
._penColour 
= "BLACK" 
2989             self
._penStyle 
= wx
.SOLID
 
2990             self
._actualColourObject 
= wx
.TheColourDatabase
.Find("BLACK") 
2991             self
._actualPenObject 
= None 
2993         self
._formattedText 
= [] 
2995     def ClearText(self
): 
2996         self
._formattedText 
= [] 
2998     def SetFont(self
, f
): 
3001     def SetMinSize(self
, w
, h
): 
3005     def SetSize(self
, w
, h
): 
3009     def SetPosition(self
, xp
, yp
): 
3013     def SetProportions(self
, xp
, yp
): 
3014         self
._regionProportionX 
= xp
 
3015         self
._regionProportionY 
= yp
 
3017     def SetFormatMode(self
, mode
): 
3018         self
._formatMode 
= mode
 
3020     def SetColour(self
, col
): 
3021         self
._textColour 
= col
 
3022         self
._actualColourObject 
= col
 
3024     def GetActualColourObject(self
): 
3025         self
._actualColourObject 
= wx
.TheColourDatabase
.Find(self
.GetColour()) 
3026         return self
._actualColourObject
 
3028     def SetPenColour(self
, col
): 
3029         self
._penColour 
= col
 
3030         self
._actualPenObject 
= None 
3032     # Returns NULL if the pen is invisible 
3033     # (different to pen being transparent; indicates that 
3034     # region boundary should not be drawn.) 
3035     def GetActualPen(self
): 
3036         if self
._actualPenObject
: 
3037             return self
._actualPenObject
 
3039         if not self
._penColour
: 
3041         if self
._penColour
=="Invisible": 
3043         self
._actualPenObject 
= wx
.ThePenList
.FindOrCreatePen(self
._penColour
, 1, self
._penStyle
) 
3044         return self
._actualPenObject
 
3046     def SetText(self
, s
): 
3047         self
._regionText 
= s
 
3049     def SetName(self
, s
): 
3050         self
._regionName 
= s
 
3053         return self
._regionText
 
3058     def GetMinSize(self
): 
3059         return self
._minWidth
, self
._minHeight
 
3061     def GetProportion(self
): 
3062         return self
._regionProportionX
, self
._regionProportionY
 
3065         return self
._width
, self
._height
 
3067     def GetPosition(self
): 
3068         return self
._x
, self
._y
 
3070     def GetFormatMode(self
): 
3071         return self
._formatMode
 
3074         return self
._regionName
 
3076     def GetColour(self
): 
3077         return self
._textColour
 
3079     def GetFormattedText(self
): 
3080         return self
._formattedText
 
3082     def GetPenColour(self
): 
3083         return self
._penColour
 
3085     def GetPenStyle(self
): 
3086         return self
._penStyle
 
3088     def SetPenStyle(self
, style
): 
3089         self
._penStyle 
= style
 
3090         self
._actualPenObject 
= None 
3095     def GetHeight(self
): 
3100 class ControlPoint(RectangleShape
): 
3101     def __init__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, the_type
): 
3102         RectangleShape
.__init
__(self
, size
, size
) 
3104         self
._canvas 
= theCanvas
 
3105         self
._shape 
= object 
3106         self
._xoffset 
= the_xoffset
 
3107         self
._yoffset 
= the_yoffset
 
3108         self
._type 
= the_type
 
3109         self
.SetPen(BlackForegroundPen
) 
3110         self
.SetBrush(wx
.BLACK_BRUSH
) 
3111         self
._oldCursor 
= None 
3112         self
._visible 
= True 
3113         self
._eraseObject 
= True 
3115     # Don't even attempt to draw any text - waste of time 
3116     def OnDrawContents(self
, dc
): 
3119     def OnDraw(self
, dc
): 
3120         self
._xpos 
= self
._shape
.GetX() + self
._xoffset
 
3121         self
._ypos 
= self
._shape
.GetY() + self
._yoffset
 
3122         RectangleShape
.OnDraw(self
, dc
) 
3124     def OnErase(self
, dc
): 
3125         RectangleShape
.OnErase(self
, dc
) 
3127     # Implement resizing of canvas object 
3128     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3129         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3131     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3132         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3134     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3135         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3137     def GetNumberOfAttachments(self
): 
3140     def GetAttachmentPosition(self
, attachment
, nth 
= 0, no_arcs 
= 1, line 
= None): 
3141         return self
._xpos
, self
._ypos
 
3143     def SetEraseObject(self
, er
): 
3144         self
._eraseObject 
= er
 
3147 class PolygonControlPoint(ControlPoint
): 
3148     def __init__(self
, theCanvas
, object, size
, vertex
, the_xoffset
, the_yoffset
): 
3149         ControlPoint
.__init
__(self
, theCanvas
, object, size
, the_xoffset
, the_yoffset
, 0) 
3150         self
._polygonVertex 
= vertex
 
3151         self
._originalDistance 
= 0.0 
3152         self
._newSize 
= wx
.RealPoint() 
3153         self
._originalSize 
= wx
.RealPoint() 
3155     def GetNewSize(self
): 
3156         return self
._newSize
 
3158     # Calculate what new size would be, at end of resize 
3159     def CalculateNewSize(self
, x
, y
): 
3160         bound_x
, bound_y 
= self
.GetShape().GetBoundingBoxMax() 
3161         dist 
= math
.sqrt((x 
- self
._shape
.GetX()) * (x 
- self
._shape
.GetX()) + (y 
- self
._shape
.GetY()) * (y 
- self
._shape
.GetY())) 
3163         self
._newSize
[0] = dist 
/ self
._originalDistance 
* self
._originalSize
[0] 
3164         self
._newSize
[1] = dist 
/ self
._originalDistance 
* self
._originalSize
[1] 
3166     # Implement resizing polygon or moving the vertex 
3167     def OnDragLeft(self
, draw
, x
, y
, keys 
= 0, attachment 
= 0): 
3168         self
._shape
.GetEventHandler().OnSizingDragLeft(self
, draw
, x
, y
, keys
, attachment
) 
3170     def OnBeginDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3171         self
._shape
.GetEventHandler().OnSizingBeginDragLeft(self
, x
, y
, keys
, attachment
) 
3173     def OnEndDragLeft(self
, x
, y
, keys 
= 0, attachment 
= 0): 
3174         self
._shape
.GetEventHandler().OnSizingEndDragLeft(self
, x
, y
, keys
, attachment
) 
3176 from _canvas 
import * 
3177 from _lines 
import * 
3178 from _composit 
import *